diff --git a/ext/phpbbstudio/aps/acp/main_info.php b/ext/phpbbstudio/aps/acp/main_info.php new file mode 100644 index 0000000..3176a96 --- /dev/null +++ b/ext/phpbbstudio/aps/acp/main_info.php @@ -0,0 +1,47 @@ + '\phpbbstudio\aps\acp\main_module', + 'title' => 'ACP_APS_POINTS', + 'modes' => [ + 'settings' => [ + 'title' => 'ACP_APS_MODE_SETTINGS', + 'auth' => 'ext_phpbbstudio/aps && acl_a_aps_settings', + 'cat' => ['ACP_APS_POINTS'] + ], + 'display' => [ + 'title' => 'ACP_APS_MODE_DISPLAY', + 'auth' => 'ext_phpbbstudio/aps && acl_a_aps_display', + 'cat' => ['ACP_APS_POINTS'], + ], + 'points' => [ + 'title' => 'ACP_APS_MODE_POINTS', + 'auth' => 'ext_phpbbstudio/aps && (acl_a_aps_points || acl_a_aps_reasons)', + 'cat' => ['ACP_APS_POINTS'], + ], + 'logs' => [ + 'title' => 'ACP_APS_MODE_LOGS', + 'auth' => 'ext_phpbbstudio/aps && acl_a_aps_logs', + 'cat' => ['ACP_APS_POINTS'], + ] + ], + ]; + } +} diff --git a/ext/phpbbstudio/aps/acp/main_module.php b/ext/phpbbstudio/aps/acp/main_module.php new file mode 100644 index 0000000..80b6104 --- /dev/null +++ b/ext/phpbbstudio/aps/acp/main_module.php @@ -0,0 +1,192 @@ +container = $phpbb_container; + $this->language = $this->container->get('language'); + + /** @var \phpbbstudio\aps\controller\acp_controller $controller */ + $controller = $this->container->get('phpbbstudio.aps.controller.acp'); + + /** @var \phpbbstudio\aps\core\functions $functions */ + $functions = $this->container->get('phpbbstudio.aps.functions'); + + // Set the page title and template + $this->tpl_name = 'aps_' . $mode; + $this->page_title = $this->language->lang('ACP_APS_POINTS') . ' • ' . $this->language->lang('ACP_APS_MODE_' . utf8_strtoupper($mode), $functions->get_name()); + + // Make the custom form action available in the controller and handle the mode + $controller->set_page_url($this->u_action)->{$mode}(); + } + + /** + * Build configuration template for custom points actions. + * + * @param string $action The custom points action + * @param bool $ajax The AJAX request indicator + * @return string The configuration template + * @access public + */ + public function set_action($action, $ajax = true) + { + return ''; + } + + /** + * Build configuration template for the points separator. + * + * @param string $value The config value + * @return string The HTML formatted select options + * @access public + */ + public function build_separator_select($value) + { + $space = htmlspecialchars(' '); + $narrow = htmlspecialchars(' '); + + $separators = [ + ',' => 'ACP_APS_SEPARATOR_COMMA', + '.' => 'ACP_APS_SEPARATOR_PERIOD', + '-' => 'ACP_APS_SEPARATOR_DASH', + '_' => 'ACP_APS_SEPARATOR_UNDERSCORE', + $space => 'ACP_APS_SEPARATOR_SPACE', + $narrow => 'ACP_APS_SEPARATOR_SPACE_NARROW', + ]; + + return build_select($separators, $value); + } + + /** + * Build configuration template for the points icon image. + * + * @param string $value The config value + * @param string $key The config key + * @return string The configuration template + * @access public + */ + public function build_icon_image_select($value, $key = '') + { + $directory = $this->container->getParameter('core.root_path') . '/images'; + + $files = array_diff(scandir($directory), ['.', '..']); + $images = array_filter($files, function($file) use ($directory) + { + $file = "{$directory}/{$file}"; + + return is_file($file) && filesize($file) && preg_match('#\.gif|jpg|jpeg|png|svg$#i', $file); + }); + + $select = ''; + + return $select; + } + + /** + * Build configuration template for the points icon position. + * + * @param string $value The config value + * @param string $key The config key + * @return string The configuration template + * @access public + */ + public function build_position_radio($value, $key = '') + { + return $this->build_radio($value, $key, [ + 0 => 'ACP_APS_POINTS_ICON_POSITION_LEFT', + 1 => 'ACP_APS_POINTS_ICON_POSITION_RIGHT', + ]); + } + + /** + * Build configuration template for the points decimals. + * + * @param string $value The config value + * @return string The configuration template + * @access public + */ + public function build_decimal_select($value) + { + $options = ''; + + for ($i = 0; $i <= 2; $i++) + { + $options .= ''; + } + + return $options; + } + + public function build_ignore_criteria_radio($value, $key = '') + { + return $this->build_radio($value, $key, array_map(function($constant) + { + return 'ACP_APS_IGNORE_' . utf8_strtoupper($constant); + }, array_flip($this->container->getParameter('phpbbstudio.aps.constants')['ignore']))); + } + + protected function build_radio($value, $key, $options) + { + $html = ''; + $s_id = false; + + foreach ($options as $val => $title) + { + $check = $value === $val ? ' checked' : ''; + $id = $s_id ? ' id="' . $key . '"' : ''; + + $html .= ''; + + $s_id = true; + } + + return $html; + } +} diff --git a/ext/phpbbstudio/aps/actions/manager.php b/ext/phpbbstudio/aps/actions/manager.php new file mode 100644 index 0000000..b51a0e0 --- /dev/null +++ b/ext/phpbbstudio/aps/actions/manager.php @@ -0,0 +1,593 @@ +distributor = $distributor; + $this->functions = $functions; + $this->lang = $lang; + $this->log = $log; + $this->valuator = $valuator; + $this->user = $user; + + $this->actions = $actions; + } + + /** + * Get the APS Distributor object. + * + * @return \phpbbstudio\aps\points\distributor + * @access public + */ + public function get_distributor() + { + return $this->distributor; + } + + /** + * Get the APS Core functions. + * + * @return \phpbbstudio\aps\core\functions + * @access public + */ + public function get_functions() + { + return $this->functions; + } + + /** + * Get the APS Valuator. + * + * @return \phpbbstudio\aps\points\valuator + * @access public + */ + public function get_valuator() + { + return $this->valuator; + } + + /** + * Get the localised points name. + * + * @return string The localised points name + * @access public + */ + public function get_name() + { + return $this->functions->get_name(); + } + + /** + * Clean an array from a listener, turns an object into an array. + * + * @param mixed $event The event to clean + * @return array The event data + * @access public + */ + public function clean_event($event) + { + if ($event instanceof \phpbb\event\data) + { + return (array) $event->get_data(); + } + else + { + return (array) $event; + } + } + + /** + * Get all values for the provided key in an array. + * + * @param array $array The array to retrieve the values from + * @param string $key The keys of which to return the values + * @return array Array of unique integers + * @access public + */ + public function get_identifiers(array $array, $key) + { + return (array) array_map('intval', array_unique(array_filter(array_column($array, $key)))); + } + + /** + * Trigger a point action and calculate users' points. + * + * This is the "main" function for this extension. + * + * $action + * Calling this with an $action will trigger all action type which have their get_action() set to $action. + * + * $user_ids + * User identifiers are for the users who can receive points by this action, this user ($this->user) is + * automatically added to the list. If it was already present in the list, it's filtered out. + * + * $event + * An array with data that was available from the event (or any other occurrence) that triggered this action. + * For instance, phpBB's event object that is available in a listener. If indeed phpBB's event object is + * send it is automatically 'cleaned', which means the object is turned into an array. + * + * $forum_ids + * A list of forum identifiers for which the point values should be retrieved, as those values are necessary + * to require the amount of points for the users. If it's left empty it will assume that the triggered action + * is a "global" action, which means the forum_id = 0. + * + * @param string $action The action to trigger + * @param array|int $user_ids The user identifiers that can receive points + * @param array $event The event data + * @param array|int $forum_ids The forum identifiers (array or single value) + * @return void + * @access public + */ + public function trigger($action, $user_ids = [], $event = [], $forum_ids = 0) + { + // 1. Initialise arrays + $this->initialise_arrays(); + + // 2. Get action types + $this->get_types_and_fields($action); + + // 3. Get values + $this->get_values($forum_ids); + + // 4. Set users + $this->set_users($user_ids); + + // 5. Calculate + $this->calculate($this->clean_event($event)); + + // 6. Distribute + $this->distribute(); + } + + /** + * Initialise the array fields used by this points manager. + * + * Has to be declared per each trigger() call, as otherwise types are carried over in chained calls. + * + * @return void + * @access protected + */ + protected function initialise_arrays() + { + // Array of points fields for the triggered action types + $this->fields = [0 => [], 1 => []]; + + // Array of action types that are triggered + $this->types = []; + + // Array of users that can receive points for the triggered action + $this->users = []; + + // Array of point values required for the triggered action types + $this->values = []; + } + + /** + * Get all action types and their fields for the trigger action. + * + * $types + * While the $this->actions array holds ALL the registered action types, + * only a certain few are required. Only the required once are added to $this->types. + * + * $fields + * Each action type has an array of point value keys with a language string as value. + * Those keys are used for storing the points values set by the Administrator in the database. + * Therefore a list is generated with all the fields that need to be retrieved from the database. + * + * @param string $action The action that is triggered + * @return void + * @access protected + */ + protected function get_types_and_fields($action) + { + /** @var \phpbbstudio\aps\actions\type\action $type */ + foreach ($this->actions as $name => $type) + { + // Only add action types that are listed under this $action + if ($type->get_action() === $action) + { + // Add this service to the action types + $this->types[$name] = $type; + + // Get scope: 0 = local | 1 = global + $key = (int) $type->is_global(); + + // Get the type fields indexed by the scope + $this->fields[$key] = array_merge($type->get_fields(), $this->fields[$key]); + } + } + } + + /** + * Get all point values required by the triggered action types from the database. + * + * $values + * Get all point values from the database that are in the $fields array and + * have their forum identifier set to one provided in the $forum_ids array. + * The values array will contain all point values indexed by the forum identifier, + * if the fields are global, the forum identifier is set to 0. + * + * @param array|int $forum_ids The forum identifiers + * @return void + * @access protected + */ + protected function get_values($forum_ids) + { + // Create array filled with integers + $forum_ids = is_array($forum_ids) ? array_map('intval', $forum_ids) : [(int) $forum_ids]; + + // Make sure there are only unique and non-empty forum identifiers + $forum_ids = array_unique($forum_ids); + + $this->values = $this->valuator->get_points($this->fields, $forum_ids); + } + + /** + * Set all users available for receiving points by the triggered action. + * + * $user_ids + * The array of user identifiers provided from the place where the action is triggered. + * This user's ($this->user) identifier is automatically added. + * + * $users + * The array of users that are able to receive points, with a base array to make sure all keys are set, + * aswell as all the users' current points. + * + * @param array|int $user_ids The user identifiers + * @return void + * @access protected + */ + protected function set_users($user_ids) + { + // Create array filled with integers + $user_ids = is_array($user_ids) ? array_map('intval', $user_ids) : [(int) $user_ids]; + + // Make sure to include this user ($this->user) + $user_ids[] = (int) $this->user->data['user_id']; + + // Make sure only unique users are set + $user_ids = array_unique(array_filter($user_ids)); + + // If there is only one user, that will be this user, so no need to query + if (count($user_ids) === 1) + { + // Build the base user array for this user + $this->users[(int) $this->user->data['user_id']] = $this->user_array($this->user->data['user_points']); + } + else + { + // Grab all the current point values for these users + $user_points = $this->valuator->users($user_ids); + + foreach ($user_ids as $user_id) + { + if (isset($user_points[$user_id])) + { + // Build the base user arrays + $this->users[$user_id] = $this->user_array($user_points[$user_id]); + } + } + } + + // Lets make sure the anonymous user is never used + unset($this->users[ANONYMOUS]); + } + + /** + * Let all the required action types calculate their user points. + * + * @param array $data Array of event data + * @return void + * @access protected + */ + protected function calculate(array $data) + { + /** @var \phpbbstudio\aps\actions\type\action $type */ + foreach ($this->types as $type) + { + // Make the functions object available + $type->set_functions($this->functions); + + // Set the users + $type->set_users($this->users); + + // Check if APS is in Safe Mode + if ($this->functions->safe_mode()) + { + // If so, catch any exceptions and log them + try + { + $type->calculate($data, $this->values); + } + catch (\Exception $e) + { + // Catch any error in the action type and log it! + $this->log->add('critical', $this->user->data['user_id'], $this->user->ip, 'LOG_ACP_APS_CALCULATION_ERROR', time(), [$e->getMessage(), $e->getFile(), $e->getLine(), $this->functions->get_name()]); + } + } + else + { + // If not, calculate and let the exceptions do their thing. + $type->calculate($data, $this->values); + } + + // Iterate over all the users + foreach (array_keys($this->users) as $user_id) + { + // Get all received points for this user from this action type + $this->users[$user_id]['actions'][] = $type->get_points($user_id); + + // Check for logs that need approving + if ($approve = $type->get_approve($user_id)) + { + $this->users[$user_id]['approve'] = array_merge($this->users[$user_id]['approve'], $approve); + } + + // Check for logs that need disapproving + if ($disapprove = $type->get_disapprove($user_id)) + { + $this->users[$user_id]['disapprove'] = array_merge($this->users[$user_id]['disapprove'], $disapprove); + } + } + } + } + + /** + * Distribute the points gained for all the users + * + * @return void + * @access protected + */ + protected function distribute() + { + // Iterate over all the users + foreach ($this->users as $user_id => $user_row) + { + // Iterate over all the action types + foreach ($user_row['actions'] as $actions) + { + // Iterate over the arrays added per action type + foreach ($actions as $action) + { + if ($action['approved']) + { + // Calculate the total points gained for this user + $this->functions->equate_reference($this->users[$user_id]['total'], $action['points']); + } + + // Grab the post identifier, as we group the logs per post id + $post_id = (int) $action['post_id']; + + // Set the logs for this user + $this->set_logs($user_id, $post_id, $action); + } + } + + // And send it off: update user points and add log entries + $this->distributor->distribute( + $user_id, + $this->users[$user_id]['total'], + $this->users[$user_id]['logs'], + $this->users[$user_id]['current'] + ); + + // Approve logs + if ($user_row['approve']) + { + $user_points = $this->functions->equate_points($this->users[$user_id]['total'], $this->users[$user_id]['current']); + + $this->distributor->approve($user_id, array_unique(array_filter($user_row['approve'])), $user_points); + } + + // Disapprove logs + if ($user_row['disapprove']) + { + $this->distributor->disapprove($user_id, array_unique(array_filter($user_row['disapprove']))); + } + } + } + + /** + * Set the log entries for this user and index per post identifier. + * + * @param int $user_id The user identifier + * @param int $post_id The post identifier + * @param array $row The log array + * @return void + * @access protected + */ + protected function set_logs($user_id, $post_id, array $row) + { + // Get the logs in a local variable for easier coding + $logs = $this->users[$user_id]['logs']; + + // Filter out the empty values except the first key + if (empty($logs[$post_id])) + { + $first = array_splice($row['logs'], 0, 1); + $row['logs'] = array_filter($row['logs']); + $row['logs'] = $first + $row['logs']; + } + else + { + $row['logs'] = array_filter($row['logs']); + } + + // If there are no logs entries yet under this post identifier + if (empty($logs[$post_id])) + { + $logs[$post_id] = [ + 'action' => (string) $this->main_log($row['logs']), + 'actions' => (array) $row['logs'], + 'approved' => (bool) $row['approved'], + 'forum_id' => (int) $row['forum_id'], + 'topic_id' => (int) $row['topic_id'], + 'post_id' => (int) $row['post_id'], + 'user_id' => (int) $user_id, + 'reportee_id' => (int) $this->user->data['user_id'], + 'reportee_ip' => (string) $this->user->ip, + 'points_old' => (double) $this->users[$user_id]['current'], + 'points_sum' => (double) $row['points'], + 'points_new' => (double) $this->functions->equate_points($this->users[$user_id]['current'], $row['points']), + ]; + } + else + { + // Else there already exists log entries under this post identifier, so merge this one in + $this->merge_logs($logs[$post_id]['actions'], $row['logs']); + + // Equate (by reference) the points gained ('sum') and the new total ('new'). + $this->functions->equate_reference($logs[$post_id]['points_sum'], $row['points']); + $this->functions->equate_reference($logs[$post_id]['points_new'], $row['points']); + } + + // Set the logs in the global variable again + $this->users[$user_id]['logs'] = $logs; + } + + /** + * Get the "main" log entry, the first key of the array. + * + * @param array $logs The logs array + * @return string The main log entry string + * @access protected + */ + protected function main_log(array $logs) + { + reset($logs); + $action = key($logs); + + return $action; + } + + /** + * Merge a log entry into existing log entries. + * + * Log entries are language strings (key) with point values (value). + * array('APS_SOME_ACTION' => 5.00) + * + * If logs are merged, an array is created which has to be equated. + * array('APS_SOME_ACTION' => array(5.00, 2.00) + * + * @param array $logs The existing log entries + * @param array $array The log entry to merge in + * @return void Passed by reference + * @access protected + */ + protected function merge_logs(array &$logs, array $array) + { + // Merge the array in to the existing entries + $logs = array_merge_recursive($logs, $array); + + // Iterate over the logged actions + foreach ($logs as $key => $value) + { + // If the logged action is no longer a single points value, equate it. + if (is_array($value)) + { + $logs[$key] = $this->functions->equate_array($value); + } + } + } + + /** + * Set up a base array for this user. + * + * 'current + * The user's current points + * + * 'actions' + * Array that will be filled with arrays added by all the action types. + * + * 'points' + * Array that will be filled with points added by all the action types. + * + * 'approve' + * Array that will be filled with post identifiers that need to be approved from the logs table. + * + * 'disapprove' + * Array that will be filled with post identifiers that need to be disapproved from the logs table. + * + * 'logs' + * Array of log entries that are going to be added for this user. + * + * 'total' + * The total points gained for this user, summing up all points per action type. + * + * @param double $points The user's current points + * @return array The user's base array + * @access protected + */ + protected function user_array($points) + { + return [ + 'current' => (double) $points, + 'actions' => [], + 'points' => [], + 'approve' => [], + 'disapprove' => [], + 'logs' => [], + 'total' => 0.00, + ]; + } +} diff --git a/ext/phpbbstudio/aps/actions/type/action.php b/ext/phpbbstudio/aps/actions/type/action.php new file mode 100644 index 0000000..5cde001 --- /dev/null +++ b/ext/phpbbstudio/aps/actions/type/action.php @@ -0,0 +1,116 @@ +get_data()); + } + + /** + * {@inheritdoc} + */ + public function set_functions($functions) + { + $this->functions = $functions; + } + + /** + * {@inheritdoc} + */ + public function set_users($users) + { + $this->users = $users; + } + + /** + * {@inheritdoc} + */ + public function get_points($user_id) + { + return $this->users[(int) $user_id]['points']; + } + + /** + * {@inheritdoc} + */ + public function get_approve($user_id) + { + return $this->users[(int) $user_id]['approve']; + } + + /** + * {@inheritdoc} + */ + public function get_disapprove($user_id) + { + return $this->users[(int) $user_id]['disapprove']; + } + + /** + * Adds a points array from calculation to the provided user id. + * + * @param int $user_id The user identifier + * @param array $points_array The points array to add + * @return void + * @access protected + */ + protected function add($user_id, array $points_array) + { + // Make sure everything is set + $array = array_merge([ + 'approved' => true, + 'forum_id' => 0, + 'topic_id' => 0, + 'post_id' => 0, + 'points' => 0.00, + 'logs' => [], + ], $points_array); + + $this->users[(int) $user_id]['points'][] = $array; + } + + /** + * Adds a post id to the array of logs to approve. + * + * @param int $user_id The user identifier + * @param int $post_id The post identifier + * @return void + * @access protected + */ + protected function approve($user_id, $post_id) + { + $this->users[(int) $user_id]['approve'][] = (int) $post_id; + } + + /** + * Adds a post id to the array of logs to disapprove. + * + * @param int $user_id The user identifier + * @param int $post_id The post identifier + * @return void + * @access protected + */ + protected function disapprove($user_id, $post_id) + { + $this->users[(int) $user_id]['disapprove'][] = (int) $post_id; + } + + /** + * Equate two numbers. + * + * @param double $a The first number + * @param double $b The second number + * @param string $operator The equation operator + * @return double The result of the equation + * @access protected + */ + protected function equate($a, $b, $operator = '+') + { + return $this->functions->equate_points($a, $b, $operator); + } +} diff --git a/ext/phpbbstudio/aps/actions/type/birthday.php b/ext/phpbbstudio/aps/actions/type/birthday.php new file mode 100644 index 0000000..927d184 --- /dev/null +++ b/ext/phpbbstudio/aps/actions/type/birthday.php @@ -0,0 +1,109 @@ +user = $user; + } + + /** + * Get action name. + * + * @return string The name of the action this type belongs to + * @access public + */ + public function get_action() + { + return 'birthday'; + } + + /** + * Get global state. + * + * @return bool If this type is global or local (per-forum basis) + * @access public + */ + public function is_global() + { + return true; + } + + /** + * Get type category under which it will be listed in the ACP. + * + * @return string The name of the category this type belongs to + * @access public + */ + public function get_category() + { + return 'ACP_APS_POINTS_MISC'; + } + + /** + * Get type data. + * + * @return array An array of value names and their language string + * @access public + */ + public function get_data() + { + return [ + 'aps_birthday' => 'APS_POINTS_BIRTHDAY', + ]; + } + + /** + * Calculate points for this type. + * + * @param array $data The data available from the $event that triggered this action + * @param array $values The point values available, indexed per forum_id and 0 for global values + * @retrun void + */ + public function calculate($data, $values) + { + $value = $values[0]; + $logs = $this->get_data(); + + $date = $data['day'] . '-' . $data['month']; + + foreach (array_keys($this->users) as $user_id) + { + // This user is automatically added by the manager, so make sure it's actually their birthday + if ($user_id == $this->user->data['user_id'] && substr($this->user->data['user_birthday'], 0, 5) !== $date) + { + continue; + } + + $points = [ + 'points' => (double) $value['aps_birthday'], + 'logs' => [$logs['aps_birthday'] => $value['aps_birthday']], + ]; + + $this->add($user_id, $points); + } + } +} diff --git a/ext/phpbbstudio/aps/actions/type/change.php b/ext/phpbbstudio/aps/actions/type/change.php new file mode 100644 index 0000000..98733dd --- /dev/null +++ b/ext/phpbbstudio/aps/actions/type/change.php @@ -0,0 +1,101 @@ + 'APS_POINTS_MOD_CHANGE', + 'aps_user_change_from' => 'APS_POINTS_USER_CHANGE_FROM', + 'aps_user_change_to' => 'APS_POINTS_USER_CHANGE_TO', + ]; + } + + /** + * Calculate points for this type. + * + * @param array $data The data available from the $event that triggered this action + * @param array $values The point values available, indexed per forum_id and 0 for global values + * @retrun void + */ + public function calculate($data, $values) + { + // Grab the data we need from the event + $forum_id = (int) $data['post_info']['forum_id']; + $topic_id = (int) $data['post_info']['topic_id']; + $post_id = (int) $data['post_info']['post_id']; + $from_id = (int) $data['post_info']['poster_id']; + $to_id = (int) $data['userdata']['user_id']; + + // Get some base variables + $value = $values[$forum_id]; + $logs = $this->get_data(); + + foreach (array_keys($this->users) as $user_id) + { + $action = in_array($user_id, [$from_id, $to_id]) ? ($user_id == $from_id ? 'aps_user_change_from' : 'aps_user_change_to') : 'aps_mod_change'; + + $points = [ + 'points' => (double) $value[$action], + 'forum_id' => (int) $forum_id, + 'topic_id' => (int) $topic_id, + 'post_id' => (int) $post_id, + 'logs' => [$logs[$action] => $value[$action]], + ]; + + $this->add($user_id, $points); + } + } +} diff --git a/ext/phpbbstudio/aps/actions/type/copy.php b/ext/phpbbstudio/aps/actions/type/copy.php new file mode 100644 index 0000000..837f9d9 --- /dev/null +++ b/ext/phpbbstudio/aps/actions/type/copy.php @@ -0,0 +1,95 @@ + 'APS_POINTS_MOD_COPY', + 'aps_user_copy' => 'APS_POINTS_USER_COPY', + ]; + } + + /** + * Calculate points for this type. + * + * @param array $data The data available from the $event that triggered this action + * @param array $values The point values available, indexed per forum_id and 0 for global values + * @retrun void + */ + public function calculate($data, $values) + { + // Grab the data we need from the event + $forum_id = (int) $data['topic_row']['forum_id']; + $poster_id = (int) $data['topic_row']['topic_poster']; + + // Get some base variables + $value = $values[$forum_id]; + $logs = $this->get_data(); + + foreach (array_keys($this->users) as $user_id) + { + $action = ($user_id == $poster_id) ? 'aps_user_copy' : 'aps_mod_copy'; + + $points = [ + 'points' => (double) $value[$action], + 'forum_id' => (int) $forum_id, + 'logs' => [$logs[$action] => $value[$action]], + ]; + + $this->add($user_id, $points); + } + } +} diff --git a/ext/phpbbstudio/aps/actions/type/delete.php b/ext/phpbbstudio/aps/actions/type/delete.php new file mode 100644 index 0000000..0a67378 --- /dev/null +++ b/ext/phpbbstudio/aps/actions/type/delete.php @@ -0,0 +1,131 @@ +user = $user; + } + + /** + * Get action name. + * + * @return string The name of the action this type belongs to + * @access public + */ + public function get_action() + { + return 'delete'; + } + + /** + * Get global state. + * + * @return bool If this type is global or local (per-forum basis) + * @access public + */ + public function is_global() + { + return false; + } + + /** + * Get type category under which it will be listed in the ACP. + * + * @return string The name of the category this type belongs to + * @access public + */ + public function get_category() + { + return 'MODERATE'; + } + + /** + * Get type data. + * + * @return array An array of value names and their language string + * @access public + */ + public function get_data() + { + return [ + 'aps_mod_delete_topic' => 'APS_POINTS_MOD_DELETE_TOPIC', + 'aps_user_delete_topic' => 'APS_POINTS_USER_DELETE_TOPIC', + 'aps_mod_delete_soft_topic' => 'APS_POINTS_MOD_DELETE_SOFT_TOPIC', + 'aps_user_delete_soft_topic' => 'APS_POINTS_USER_DELETE_SOFT_TOPIC', + 'aps_mod_delete_post' => 'APS_POINTS_MOD_DELETE_POST', + 'aps_user_delete_post' => 'APS_POINTS_USER_DELETE_POST', + 'aps_mod_delete_soft_post' => 'APS_POINTS_MOD_DELETE_SOFT_POST', + 'aps_user_delete_soft_post' => 'APS_POINTS_USER_DELETE_SOFT_POST', + ]; + } + + /** + * Calculate points for this type. + * + * @param array $data The data available from the $event that triggered this action + * @param array $values The point values available, indexed per forum_id and 0 for global values + * @retrun void + */ + public function calculate($data, $values) + { + $action = $data['action']; + $posts = $action === 'topic' ? $data['topics'] : $data['posts']; + $is_soft = isset($data['is_soft']) ? $data['is_soft'] : false; + + $key_user = 'aps_user_delete_' . ($is_soft ? 'soft_' : '') . $action; + $key_mod = 'aps_mod_delete_' . ($is_soft ? 'soft_' : '') . $action; + $strings = $this->get_data(); + + foreach ($posts as $post_data) + { + $forum_id = $post_data['forum_id']; + $topic_id = $post_data['topic_id']; + $post_id = $action === 'topic' ? $post_data['topic_first_post_id'] : $post_data['post_id']; + $user_id = $action === 'topic' ? $post_data['topic_poster'] : $post_data['poster_id']; + + $this->add($user_id, [ + 'forum_id' => $forum_id, + 'topic_id' => $topic_id, + 'post_id' => $post_id, + 'points' => $values[$forum_id][$key_user], + 'logs' => [ + $strings[$key_user] => $values[$forum_id][$key_user], + ], + ]); + + $this->add($this->user->data['user_id'], [ + 'forum_id' => $forum_id, + 'topic_id' => $topic_id, + 'post_id' => $post_id, + 'points' => $values[$forum_id][$key_mod], + 'logs' => [ + $strings[$key_mod] => $values[$forum_id][$key_mod], + ], + ]); + } + } +} diff --git a/ext/phpbbstudio/aps/actions/type/edit.php b/ext/phpbbstudio/aps/actions/type/edit.php new file mode 100644 index 0000000..512aefc --- /dev/null +++ b/ext/phpbbstudio/aps/actions/type/edit.php @@ -0,0 +1,95 @@ + 'APS_POINTS_MOD_EDIT', + 'aps_user_edit' => 'APS_POINTS_USER_EDIT', + ]; + } + + /** + * Calculate points for this type. + * + * @param array $data The data available from the $event that triggered this action + * @param array $values The point values available, indexed per forum_id and 0 for global values + * @retrun void + */ + public function calculate($data, $values) + { + // Grab the data we need from the event + $forum_id = (int) $data['data']['forum_id']; + $poster_id = (int) $data['data']['poster_id']; + + // Get some base variables + $value = $values[$forum_id]; + $logs = $this->get_data(); + + foreach (array_keys($this->users) as $user_id) + { + $action = ($user_id == $poster_id) ? 'aps_user_edit' : 'aps_mod_edit'; + + $points = [ + 'points' => (double) $value[$action], + 'forum_id' => (int) $forum_id, + 'logs' => [$logs[$action] => $value[$action]], + ]; + + $this->add($user_id, $points); + } + } +} diff --git a/ext/phpbbstudio/aps/actions/type/lock.php b/ext/phpbbstudio/aps/actions/type/lock.php new file mode 100644 index 0000000..ec921f8 --- /dev/null +++ b/ext/phpbbstudio/aps/actions/type/lock.php @@ -0,0 +1,131 @@ +user = $user; + } + + /** + * Get action name. + * + * @return string The name of the action this type belongs to + * @access public + */ + public function get_action() + { + return 'lock'; + } + + /** + * Get global state. + * + * @return bool If this type is global or local (per-forum basis) + * @access public + */ + public function is_global() + { + return false; + } + + /** + * Get type category under which it will be listed in the ACP. + * + * @return string The name of the category this type belongs to + * @access public + */ + public function get_category() + { + return 'MODERATE'; + } + + /** + * Get type data. + * + * @return array An array of value names and their language string + * @access public + */ + public function get_data() + { + return [ + 'aps_mod_lock' => 'APS_POINTS_MOD_LOCK', + 'aps_user_lock' => 'APS_POINTS_USER_LOCK', + 'aps_mod_lock_post' => 'APS_POINTS_MOD_LOCK_POST', + 'aps_user_lock_post' => 'APS_POINTS_USER_LOCK_POST', + 'aps_mod_unlock' => 'APS_POINTS_MOD_UNLOCK', + 'aps_user_unlock' => 'APS_POINTS_USER_UNLOCK', + 'aps_mod_unlock_post' => 'APS_POINTS_MOD_UNLOCK_POST', + 'aps_user_unlock_post' => 'APS_POINTS_USER_UNLOCK_POST', + ]; + } + + /** + * Calculate points for this type. + * + * @param array $data The data available from the $event that triggered this action + * @param array $values The point values available, indexed per forum_id and 0 for global values + * @retrun void + */ + public function calculate($data, $values) + { + $action = $data['action']; + $posts = $data['data']; + $s_topic = in_array($action, ['lock', 'unlock']); + + $key_user = 'aps_user_' . $action; + $key_mod = 'aps_mod_' . $action; + $strings = $this->get_data(); + + foreach ($posts as $post_data) + { + $forum_id = $post_data['forum_id']; + $topic_id = $post_data['topic_id']; + $post_id = $s_topic ? $post_data['topic_first_post_id'] : $post_data['post_id']; + $user_id = $s_topic ? $post_data['topic_poster'] : $post_data['poster_id']; + + $this->add($user_id, [ + 'forum_id' => $forum_id, + 'topic_id' => $topic_id, + 'post_id' => $post_id, + 'points' => $values[$forum_id][$key_user], + 'logs' => [ + $strings[$key_user] => $values[$forum_id][$key_user], + ], + ]); + + $this->add($this->user->data['user_id'], [ + 'forum_id' => $forum_id, + 'topic_id' => $topic_id, + 'post_id' => $post_id, + 'points' => $values[$forum_id][$key_mod], + 'logs' => [ + $strings[$key_mod] => $values[$forum_id][$key_mod], + ], + ]); + } + } +} diff --git a/ext/phpbbstudio/aps/actions/type/merge.php b/ext/phpbbstudio/aps/actions/type/merge.php new file mode 100644 index 0000000..2e77b0c --- /dev/null +++ b/ext/phpbbstudio/aps/actions/type/merge.php @@ -0,0 +1,113 @@ +user = $user; + } + + /** + * Get action name. + * + * @return string The name of the action this type belongs to + * @access public + */ + public function get_action() + { + return 'merge'; + } + + /** + * Get global state. + * + * @return bool If this type is global or local (per-forum basis) + * @access public + */ + public function is_global() + { + return false; + } + + /** + * Get type category under which it will be listed in the ACP. + * + * @return string The name of the category this type belongs to + * @access public + */ + public function get_category() + { + return 'MODERATE'; + } + + /** + * Get type data. + * + * @return array An array of value names and their language string + * @access public + */ + public function get_data() + { + return [ + 'aps_mod_merge' => 'APS_POINTS_MOD_MERGE', + 'aps_user_merge' => 'APS_POINTS_USER_MERGE', + ]; + } + + /** + * Calculate points for this type. + * + * @param array $data The data available from the $event that triggered this action + * @param array $values The point values available, indexed per forum_id and 0 for global values + * @retrun void + */ + public function calculate($data, $values) + { + $topics = $data['all_topic_data']; + $topic_id = $data['to_topic_id']; + $forum_id = $topics[$topic_id]['forum_id']; + + $values = $values[$forum_id]; + $strings = $this->get_data(); + + foreach ($topics as $topic) + { + $this->add($topic['topic_poster'], [ + 'forum_id' => (int) $forum_id, + 'topic_id' => (int) $topic_id, + 'points' => $values['aps_user_merge'], + 'logs' => [$strings['aps_user_merge'] => $values['aps_user_merge']], + ]); + + $this->add($this->user->data['user_id'], [ + 'forum_id' => (int) $forum_id, + 'topic_id' => (int) $topic_id, + 'points' => $values['aps_mod_merge'], + 'logs' => [$strings['aps_mod_merge'] => $values['aps_mod_merge']], + ]); + } + } +} diff --git a/ext/phpbbstudio/aps/actions/type/move.php b/ext/phpbbstudio/aps/actions/type/move.php new file mode 100644 index 0000000..09a3248 --- /dev/null +++ b/ext/phpbbstudio/aps/actions/type/move.php @@ -0,0 +1,127 @@ +user = $user; + } + + /** + * Get action name. + * + * @return string The name of the action this type belongs to + * @access public + */ + public function get_action() + { + return 'move'; + } + + /** + * Get global state. + * + * @return bool If this type is global or local (per-forum basis) + * @access public + */ + public function is_global() + { + return false; + } + + /** + * Get type category under which it will be listed in the ACP. + * + * @return string The name of the category this type belongs to + * @access public + */ + public function get_category() + { + return 'MODERATE'; + } + + /** + * Get type data. + * + * @return array An array of value names and their language string + * @access public + */ + public function get_data() + { + return [ + 'aps_mod_move_post' => 'APS_POINTS_MOD_MOVE_POST', + 'aps_user_move_post' => 'APS_POINTS_USER_MOVE_POST', + 'aps_mod_move_topic' => 'APS_POINTS_MOD_MOVE_TOPIC', + 'aps_user_move_topic' => 'APS_POINTS_USER_MOVE_TOPIC', + ]; + } + + /** + * Calculate points for this type. + * + * @param array $data The data available from the $event that triggered this action + * @param array $values The point values available, indexed per forum_id and 0 for global values + * @retrun void + */ + public function calculate($data, $values) + { + $action = $data['action']; + $s_topic = $action === 'topic'; + $posts = $s_topic ? $data['topics'] : $data['posts']; + + $key_user = 'aps_user_move_' . $action; + $key_mod = 'aps_mod_move_' . $action; + $strings = $this->get_data(); + + foreach ($posts as $post) + { + $forum_id = $post['forum_id']; + $topic_id = $post['topic_id']; + $post_id = $s_topic ? $post['topic_first_post_id'] : $post['post_id']; + $user_id = $s_topic ? $post['topic_poster'] : $post['poster_id']; + + $this->add($user_id, [ + 'forum_id' => $forum_id, + 'topic_id' => $topic_id, + 'post_id' => $post_id, + 'points' => $values[$forum_id][$key_user], + 'logs' => [ + $strings[$key_user] => $values[$forum_id][$key_user], + ], + ]); + + $this->add($this->user->data['user_id'], [ + 'forum_id' => $forum_id, + 'topic_id' => $topic_id, + 'post_id' => $post_id, + 'points' => $values[$forum_id][$key_mod], + 'logs' => [ + $strings[$key_mod] => $values[$forum_id][$key_mod], + ], + ]); + } + } +} diff --git a/ext/phpbbstudio/aps/actions/type/pm.php b/ext/phpbbstudio/aps/actions/type/pm.php new file mode 100644 index 0000000..ef4b9fc --- /dev/null +++ b/ext/phpbbstudio/aps/actions/type/pm.php @@ -0,0 +1,202 @@ +config = $config; + $this->utils = $utils; + $this->ignore = $constants['ignore']; + } + + /** + * Get action name. + * + * @return string The name of the action this type belongs to + * @access public + */ + public function get_action() + { + return 'pm'; + } + + /** + * Get global state. + * + * @return bool If this type is global or local (per-forum basis) + * @access public + */ + public function is_global() + { + return true; + } + + /** + * Get type category under which it will be listed in the ACP. + * + * @return string The name of the category this type belongs to + * @access public + */ + public function get_category() + { + return 'PRIVATE_MESSAGE'; + } + + /** + * Get type data. + * + * @return array An array of value names and their language string + * @access public + */ + public function get_data() + { + return [ + // Initial points + 'aps_pm_base' => 'APS_POINTS_PM', + + // Recipients points + 'aps_pm_per_recipient' => 'APS_POINTS_PM_PER_RECIPIENT', + + // Text points + 'aps_pm_per_char' => 'APS_POINTS_PER_CHAR', + 'aps_pm_per_word' => 'APS_POINTS_PER_WORD', + 'aps_pm_per_quote' => 'APS_POINTS_PER_QUOTE', + + // Attachment points + 'aps_pm_has_attach' => 'APS_POINTS_ATTACH_HAS', + 'aps_pm_per_attach' => 'APS_POINTS_ATTACH_PER', + + // Modification points + 'aps_pm_edit' => 'APS_POINTS_EDIT', + ]; + } + + /** + * Calculate points for this type. + * + * @param array $data The data available from the $event that triggered this action + * @param array $values The point values available, indexed per forum_id and 0 for global values + * @retrun void + */ + public function calculate($data, $values) + { + // Grab event data + $mode = $data['mode']; + $message = $data['data']['message']; + $attachments = !empty($data['data']['attachment_data']) ? $data['data']['attachment_data'] : []; + $recipients = $data['pm_data']['recipients']; + + $logs = []; + $values = $values[0]; + $strings = $this->get_data(); + + switch ($mode) + { + case 'edit': + $points = $logs[$strings['aps_pm_' . $mode]] = $values['aps_pm_' . $mode]; + break; + + default: + // Initial points + $points = $logs[$strings['aps_pm_base']] = $values['aps_pm_base']; + + // Recipient points + $points += $logs[$strings['aps_pm_per_recipient']] = $this->equate($values['aps_pm_per_recipient'], count($recipients), '*'); + + // Text points + $quotes = $this->utils->get_outermost_quote_authors($message); + $message = $this->utils->remove_bbcode($message, 'quote'); + $message = $this->utils->remove_bbcode($message, 'attachment'); + $message = $this->utils->clean_formatting($message); + $words = $exclude_words = array_filter(preg_split('/[\s]+/', $message)); + $chars = $exclude_chars = implode('', $words); + + if ($min = $this->config['aps_points_exclude_words']) + { + $exclude_words = array_filter($words, function($word) use ($min) + { + return strlen($word) > $min; + }); + + if ($this->config['aps_points_exclude_chars']) + { + $exclude_chars = implode('', $exclude_words); + } + } + + // Check ignore criteria + if ($this->config['aps_ignore_criteria']) + { + $ignore_words = $this->config['aps_ignore_excluded_words'] ? $exclude_words : $words; + $ignore_chars = $this->config['aps_ignore_excluded_chars'] ? $exclude_chars : $chars; + + $ignore_words = count($ignore_words) < $this->config['aps_ignore_min_words']; + $ignore_chars = strlen($ignore_chars) < $this->config['aps_ignore_min_chars']; + + if (($this->config['aps_ignore_criteria'] == $this->ignore['both'] && $ignore_words && $ignore_chars) + || ($this->config['aps_ignore_criteria'] == $this->ignore['words'] && $ignore_words) + || ($this->config['aps_ignore_criteria'] == $this->ignore['chars'] && $ignore_chars)) + { + $points = 0; + + // Break out of calculation + break; + } + } + + $words = $exclude_words; + $chars = $exclude_chars; + + $points += $logs[$strings['aps_pm_per_quote']] = $this->equate($values['aps_pm_per_quote'], count($quotes), '*'); + $points += $logs[$strings['aps_pm_per_word']] = $this->equate($values['aps_pm_per_word'], count($words), '*'); + $points += $logs[$strings['aps_pm_per_char']] = $this->equate($values['aps_pm_per_char'], strlen($chars), '*'); + + // Attachment points + if (!empty($attachments)) + { + $points += $logs[$strings['aps_pm_has_attach']] = $values['aps_pm_has_attach']; + $points += $logs[$strings['aps_pm_per_attach']] = $this->equate($values['aps_pm_per_attach'], count($attachments), '*'); + } + break; + } + + foreach (array_keys($this->users) as $user_id) + { + $this->add($user_id, [ + 'points' => $points, + 'logs' => $logs, + ]); + } + } +} diff --git a/ext/phpbbstudio/aps/actions/type/post.php b/ext/phpbbstudio/aps/actions/type/post.php new file mode 100644 index 0000000..569bedc --- /dev/null +++ b/ext/phpbbstudio/aps/actions/type/post.php @@ -0,0 +1,211 @@ +config = $config; + $this->utils = $utils; + $this->ignore = $constants['ignore']; + } + + /** + * Get action name. + * + * @return string The name of the action this type belongs to + * @access public + */ + public function get_action() + { + return 'post'; + } + + /** + * Get global state. + * + * @return bool If this type is global or local (per-forum basis) + * @access public + */ + public function is_global() + { + return false; + } + + /** + * Get type category under which it will be listed in the ACP. + * + * @return string The name of the category this type belongs to + * @access public + */ + public function get_category() + { + return 'POST'; + } + + /** + * Get type data. + * + * @return array An array of value names and their language string + * @access public + */ + public function get_data() + { + return [ + // Initial points + 'aps_post_base' => 'APS_POINTS_POST', + + // Text points + 'aps_post_per_char' => 'APS_POINTS_PER_CHAR', + 'aps_post_per_word' => 'APS_POINTS_PER_WORD', + 'aps_post_per_quote' => 'APS_POINTS_PER_QUOTE', + + // Attachment points + 'aps_post_has_attach' => 'APS_POINTS_ATTACH_HAS', + 'aps_post_per_attach' => 'APS_POINTS_ATTACH_PER', + + // Modification points + 'aps_post_edit' => 'APS_POINTS_EDIT', + 'aps_post_delete' => 'APS_POINTS_DELETE', + 'aps_post_delete_soft' => 'APS_POINTS_DELETE_SOFT', + ]; + } + + /** + * Calculate points for this type. + * + * @param array $data The data available from the $event that triggered this action + * @param array $values The point values available, indexed per forum_id and 0 for global values + * @retrun void + */ + public function calculate($data, $values) + { + // Grab event data + $mode = $data['mode']; + $s_delete = in_array($mode, ['delete', 'soft_delete']); + $forum_id = $s_delete ? $data['forum_id'] : $data['data']['forum_id']; + $topic_id = $s_delete ? $data['topic_id'] : $data['data']['topic_id']; + $post_id = $s_delete ? $data['post_id'] : $data['data']['post_id']; + $message = $s_delete ? '' : $data['data']['message']; + $s_approved = !$s_delete ? $data['post_visibility'] == ITEM_APPROVED : true; + $attachments = $s_delete ? [] : $data['data']['attachment_data']; + + $logs = []; + $values = $values[$forum_id]; + $strings = $this->get_data(); + + switch ($mode) + { + /** @noinspection PhpMissingBreakStatementInspection */ + case 'soft_delete': + $mode = 'delete_soft'; + // no break; + case 'edit': + case 'delete': + $points = $logs[$strings['aps_post_' . $mode]] = $values['aps_post_' . $mode]; + break; + + default: + // Initial points + $points = $logs[$strings['aps_post_base']] = $values['aps_post_base']; + + // Text points + $quotes = $this->utils->get_outermost_quote_authors($message); + $message = $this->utils->remove_bbcode($message, 'quote'); + $message = $this->utils->remove_bbcode($message, 'attachment'); + $message = $this->utils->clean_formatting($message); + $words = $exclude_words = array_filter(preg_split('/[\s]+/', $message)); + $chars = $exclude_chars = implode('', $words); + + if ($min = $this->config['aps_points_exclude_words']) + { + $exclude_words = array_filter($words, function($word) use ($min) + { + return strlen($word) > $min; + }); + + if ($this->config['aps_points_exclude_chars']) + { + $exclude_chars = implode('', $exclude_words); + } + } + + // Check ignore criteria + if ($this->config['aps_ignore_criteria']) + { + $ignore_words = $this->config['aps_ignore_excluded_words'] ? $exclude_words : $words; + $ignore_chars = $this->config['aps_ignore_excluded_chars'] ? $exclude_chars : $chars; + + $ignore_words = count($ignore_words) < $this->config['aps_ignore_min_words']; + $ignore_chars = strlen($ignore_chars) < $this->config['aps_ignore_min_chars']; + + if (($this->config['aps_ignore_criteria'] == $this->ignore['both'] && $ignore_words && $ignore_chars) + || ($this->config['aps_ignore_criteria'] == $this->ignore['words'] && $ignore_words) + || ($this->config['aps_ignore_criteria'] == $this->ignore['chars'] && $ignore_chars)) + { + $points = 0; + + // Break out of calculation + break; + } + } + + $words = $exclude_words; + $chars = $exclude_chars; + + $points += $logs[$strings['aps_post_per_quote']] = $this->equate($values['aps_post_per_quote'], count($quotes), '*'); + $points += $logs[$strings['aps_post_per_word']] = $this->equate($values['aps_post_per_word'], count($words), '*'); + $points += $logs[$strings['aps_post_per_char']] = $this->equate($values['aps_post_per_char'], strlen($chars), '*'); + + // Attachment points + if (!empty($attachments)) + { + $points += $logs[$strings['aps_post_has_attach']] = $values['aps_post_has_attach']; + $points += $logs[$strings['aps_post_per_attach']] = $this->equate($values['aps_post_per_attach'], count($attachments), '*'); + } + break; + } + + foreach (array_keys($this->users) as $user_id) + { + $this->add($user_id, [ + 'approved' => $s_approved, + 'forum_id' => $forum_id, + 'topic_id' => $topic_id, + 'post_id' => $post_id, + 'points' => $points, + 'logs' => $logs, + ]); + } + } +} diff --git a/ext/phpbbstudio/aps/actions/type/queue.php b/ext/phpbbstudio/aps/actions/type/queue.php new file mode 100644 index 0000000..71c6b54 --- /dev/null +++ b/ext/phpbbstudio/aps/actions/type/queue.php @@ -0,0 +1,140 @@ +user = $user; + } + + /** + * Get action name. + * + * @return string The name of the action this type belongs to + * @access public + */ + public function get_action() + { + return 'queue'; + } + + /** + * Get global state. + * + * @return bool If this type is global or local (per-forum basis) + * @access public + */ + public function is_global() + { + return false; + } + + /** + * Get type category under which it will be listed in the ACP. + * + * @return string The name of the category this type belongs to + * @access public + */ + public function get_category() + { + return 'MODERATE'; + } + + /** + * Get type data. + * + * @return array An array of value names and their language string + * @access public + */ + public function get_data() + { + return [ + 'aps_mod_restore' => 'APS_POINTS_MOD_RESTORE', + 'aps_mod_approve' => 'APS_POINTS_MOD_APPROVE', + 'aps_mod_disapprove' => 'APS_POINTS_MOD_DISAPPROVE', + 'aps_user_restore' => 'APS_POINTS_USER_RESTORE', + 'aps_user_approve' => 'APS_POINTS_USER_APPROVE', + 'aps_user_disapprove' => 'APS_POINTS_USER_DISAPPROVE', + ]; + } + + /** + * Calculate points for this type. + * + * @param array $data The data available from the $event that triggered this action + * @param array $values The point values available, indexed per forum_id and 0 for global values + * @retrun void + */ + public function calculate($data, $values) + { + $action = $data['mode']; + $s_post = isset($data['post_info']); + $posts = $s_post ? $data['post_info'] : $data['topic_info']; + + $key_user = 'aps_user_' . $action; + $key_mod = 'aps_mod_' . $action; + $strings = $this->get_data(); + + foreach ($posts as $post_id => $post_data) + { + $user_id = $s_post ? $post_data['poster_id'] : $post_data['topic_poster']; + $topic_id = $post_data['topic_id']; + $forum_id = $post_data['forum_id']; + + $logs = []; + + $points = $logs[$strings[$key_user]] = $values[$forum_id][$key_user]; + + switch ($action) + { + case 'approve': + $this->approve($user_id, $post_id); + break; + case 'disapprove': + $this->disapprove($user_id, $post_id); + break; + } + + $this->add($user_id, [ + 'forum_id' => $forum_id, + 'topic_id' => $topic_id, + 'post_id' => $post_id, + 'points' => $points, + 'logs' => $logs, + ]); + + $this->add($this->user->data['user_id'], [ + 'forum_id' => $forum_id, + 'topic_id' => $topic_id, + 'post_id' => $post_id, + 'points' => $values[$forum_id][$key_mod], + 'logs' => [ + $strings[$key_mod] => $values[$forum_id][$key_mod], + ], + ]); + } + } +} diff --git a/ext/phpbbstudio/aps/actions/type/register.php b/ext/phpbbstudio/aps/actions/type/register.php new file mode 100644 index 0000000..cecbf03 --- /dev/null +++ b/ext/phpbbstudio/aps/actions/type/register.php @@ -0,0 +1,91 @@ + 'APS_POINTS_REGISTER', + ]; + } + + /** + * Calculate points for this type. + * + * @param array $data The data available from the $event that triggered this action + * @param array $values The point values available, indexed per forum_id and 0 for global values + * @retrun void + */ + public function calculate($data, $values) + { + $value = $values[0]; + $logs = $this->get_data(); + + foreach (array_keys($this->users) as $user_id) + { + if ($user_id == ANONYMOUS) + { + continue; + } + + $points = [ + 'points' => (double) $value['aps_register'], + 'logs' => [$logs['aps_register'] => $value['aps_register']], + ]; + + $this->add($user_id, $points); + } + } +} diff --git a/ext/phpbbstudio/aps/actions/type/topic.php b/ext/phpbbstudio/aps/actions/type/topic.php new file mode 100644 index 0000000..a924f76 --- /dev/null +++ b/ext/phpbbstudio/aps/actions/type/topic.php @@ -0,0 +1,243 @@ +config = $config; + $this->utils = $utils; + $this->ignore = $constants['ignore']; + } + + /** + * Get action name. + * + * @return string The name of the action this type belongs to + * @access public + */ + public function get_action() + { + return 'topic'; + } + + /** + * Get global state. + * + * @return bool If this type is global or local (per-forum basis) + * @access public + */ + public function is_global() + { + return false; + } + + /** + * Get type category under which it will be listed in the ACP. + * + * @return string The name of the category this type belongs to + * @access public + */ + public function get_category() + { + return 'TOPIC'; + } + + /** + * Get type data. + * + * @return array An array of value names and their language string + * @access public + */ + public function get_data() + { + return [ + // Type points + 'aps_topic_base' => 'APS_POINTS_TOPIC', + 'aps_topic_sticky' => 'APS_POINTS_STICKY', + 'aps_topic_announce' => 'APS_POINTS_ANNOUNCE', + 'aps_topic_global' => 'APS_POINTS_GLOBAL', + + // Text points + 'aps_topic_per_char' => 'APS_POINTS_PER_CHAR', + 'aps_topic_per_word' => 'APS_POINTS_PER_WORD', + 'aps_topic_per_quote' => 'APS_POINTS_PER_QUOTE', + + // Attachment points + 'aps_topic_has_attach' => 'APS_POINTS_ATTACH_HAS', + 'aps_topic_per_attach' => 'APS_POINTS_ATTACH_PER', + + // Poll points + 'aps_topic_has_poll' => 'APS_POINTS_POLL_HAS', + 'aps_topic_per_option' => 'APS_POINTS_POLL_OPTION', + + // Miscellaneous + 'aps_topic_bump' => 'APS_POINTS_BUMP', + 'aps_topic_edit' => 'APS_POINTS_EDIT', + ]; + } + + /** + * Calculate points for this type. + * + * @param array $data The data available from the $event that triggered this action + * @param array $values The point values available, indexed per forum_id and 0 for global values + * @retrun void + */ + public function calculate($data, $values) + { + $mode = $data['mode']; + $s_bump = $mode === 'bump'; + + $type = !$s_bump ? $data['topic_type'] : ''; + $post_data = !$s_bump ? $data['data'] : $data['post_data']; + + $s_approved = isset($data['post_visibility']) ? $data['post_visibility'] == ITEM_APPROVED : true; + $poll = isset($data['poll']['poll_options']) ? $data['poll']['poll_options'] : []; + + $forum_id = $post_data['forum_id']; + $topic_id = $post_data['topic_id']; + $post_id = !$s_bump ? $post_data['post_id'] : 0; + $message = !$s_bump ? $post_data['message'] : ''; + $attachments = !$s_bump ? $post_data['attachment_data'] : []; + + $logs = []; + $values = $values[$forum_id]; + $strings = $this->get_data(); + + switch ($mode) + { + case 'bump': + case 'edit': + $points = $logs[$strings['aps_topic_' . $mode]] = $values['aps_topic_' . $mode]; + break; + + default: + // Initial type points + switch ($type) + { + default: + case POST_NORMAL: + $points = $logs[$strings['aps_topic_base']] = $values['aps_topic_base']; + break; + + case POST_STICKY: + $points = $logs[$strings['aps_topic_sticky']] = $values['aps_topic_sticky']; + break; + + case POST_ANNOUNCE: + $points = $logs[$strings['aps_topic_announce']] = $values['aps_topic_announce']; + break; + + case POST_GLOBAL: + $points = $logs[$strings['aps_topic_global']] = $values['aps_topic_global']; + break; + } + + // Text points + $quotes = $this->utils->get_outermost_quote_authors($message); + $message = $this->utils->remove_bbcode($message, 'quote'); + $message = $this->utils->remove_bbcode($message, 'attachment'); + $message = $this->utils->clean_formatting($message); + $words = $exclude_words = array_filter(preg_split('/[\s]+/', $message)); + $chars = $exclude_chars = implode('', $words); + + if ($min = $this->config['aps_points_exclude_words']) + { + $exclude_words = array_filter($words, function($word) use ($min) + { + return strlen($word) > $min; + }); + + if ($this->config['aps_points_exclude_chars']) + { + $exclude_chars = implode('', $exclude_words); + } + } + + // Check ignore criteria + if ($this->config['aps_ignore_criteria']) + { + $ignore_words = $this->config['aps_ignore_excluded_words'] ? $exclude_words : $words; + $ignore_chars = $this->config['aps_ignore_excluded_chars'] ? $exclude_chars : $chars; + + $ignore_words = count($ignore_words) < $this->config['aps_ignore_min_words']; + $ignore_chars = strlen($ignore_chars) < $this->config['aps_ignore_min_chars']; + + if (($this->config['aps_ignore_criteria'] == $this->ignore['both'] && $ignore_words && $ignore_chars) + || ($this->config['aps_ignore_criteria'] == $this->ignore['words'] && $ignore_words) + || ($this->config['aps_ignore_criteria'] == $this->ignore['chars'] && $ignore_chars)) + { + $points = 0; + + // Break out of calculation + break; + } + } + + $words = $exclude_words; + $chars = $exclude_chars; + + $points += $logs[$strings['aps_topic_per_quote']] = $this->equate($values['aps_topic_per_quote'], count($quotes), '*'); + $points += $logs[$strings['aps_topic_per_word']] = $this->equate($values['aps_topic_per_word'], count($words), '*'); + $points += $logs[$strings['aps_topic_per_char']] = $this->equate($values['aps_topic_per_char'], strlen($chars), '*'); + + // Attachment points + if (!empty($attachments)) + { + $points += $logs[$strings['aps_topic_has_attach']] = $values['aps_topic_has_attach']; + $points += $logs[$strings['aps_topic_per_attach']] = $this->equate($values['aps_topic_per_attach'], count($attachments), '*'); + } + + // Poll points + if ($poll) + { + $points += $logs[$strings['aps_topic_has_poll']] = $values['aps_topic_has_poll']; + $points += $logs[$strings['aps_topic_per_option']] = $this->equate($values['aps_topic_per_option'], count($poll), '*'); + } + break; + } + + foreach (array_keys($this->users) as $user_id) + { + $this->add($user_id, [ + 'approved' => $s_approved, + 'forum_id' => $forum_id, + 'topic_id' => $topic_id, + 'post_id' => $post_id, + 'points' => $points, + 'logs' => $logs, + ]); + } + } +} diff --git a/ext/phpbbstudio/aps/actions/type/topic_type.php b/ext/phpbbstudio/aps/actions/type/topic_type.php new file mode 100644 index 0000000..9eda801 --- /dev/null +++ b/ext/phpbbstudio/aps/actions/type/topic_type.php @@ -0,0 +1,132 @@ + 'APS_POINTS_MOD_NORMAL_STICKY', + 'aps_mod_normal_announce' => 'APS_POINTS_MOD_NORMAL_ANNOUNCE', + 'aps_mod_normal_global' => 'APS_POINTS_MOD_NORMAL_GLOBAL', + 'aps_mod_sticky_normal' => 'APS_POINTS_MOD_STICKY_NORMAL', + 'aps_mod_sticky_announce' => 'APS_POINTS_MOD_STICKY_ANNOUNCE', + 'aps_mod_sticky_global' => 'APS_POINTS_MOD_STICKY_GLOBAL', + 'aps_mod_announce_normal' => 'APS_POINTS_MOD_ANNOUNCE_NORMAL', + 'aps_mod_announce_sticky' => 'APS_POINTS_MOD_ANNOUNCE_STICKY', + 'aps_mod_announce_global' => 'APS_POINTS_MOD_ANNOUNCE_GLOBAL', + 'aps_mod_global_normal' => 'APS_POINTS_MOD_GLOBAL_NORMAL', + 'aps_mod_global_sticky' => 'APS_POINTS_MOD_GLOBAL_STICKY', + 'aps_mod_global_announce' => 'APS_POINTS_MOD_GLOBAL_ANNOUNCE', + + 'aps_user_normal_sticky' => 'APS_POINTS_USER_NORMAL_STICKY', + 'aps_user_normal_announce' => 'APS_POINTS_USER_NORMAL_ANNOUNCE', + 'aps_user_normal_global' => 'APS_POINTS_USER_NORMAL_GLOBAL', + 'aps_user_sticky_normal' => 'APS_POINTS_USER_STICKY_NORMAL', + 'aps_user_sticky_announce' => 'APS_POINTS_USER_STICKY_ANNOUNCE', + 'aps_user_sticky_global' => 'APS_POINTS_USER_STICKY_GLOBAL', + 'aps_user_announce_normal' => 'APS_POINTS_USER_ANNOUNCE_NORMAL', + 'aps_user_announce_sticky' => 'APS_POINTS_USER_ANNOUNCE_STICKY', + 'aps_user_announce_global' => 'APS_POINTS_USER_ANNOUNCE_GLOBAL', + 'aps_user_global_normal' => 'APS_POINTS_USER_GLOBAL_NORMAL', + 'aps_user_global_sticky' => 'APS_POINTS_USER_GLOBAL_STICKY', + 'aps_user_global_announce' => 'APS_POINTS_USER_GLOBAL_ANNOUNCE', + ]; + } + + /** + * Calculate points for this type. + * + * @param array $data The data available from the $event that triggered this action + * @param array $values The point values available, indexed per forum_id and 0 for global values + * @retrun void + */ + public function calculate($data, $values) + { + // Grab the data we need from the event + $forum_id = (int) $data['data']['forum_id']; + $topic_id = (int) $data['data']['topic_id']; + $post_id = (int) $data['data']['post_id']; + $poster_id = (int) $data['post_data']['topic_poster']; + $type_from = (int) $data['type_from']; + $type_to = (int) $data['type_to']; + + $types = [ + POST_NORMAL => 'normal', + POST_STICKY => 'sticky', + POST_ANNOUNCE => 'announce', + POST_GLOBAL => 'global', + ]; + + // Get some base variables + $value = $values[$forum_id]; + $logs = $this->get_data(); + + foreach (array_keys($this->users) as $user_id) + { + $action = ($user_id == $poster_id) ? 'aps_user_' : 'aps_mod_'; + $action .= $types[$type_from] . '_' . $types[$type_to]; + + $points = [ + 'points' => (double) $value[$action], + 'forum_id' => (int) $forum_id, + 'topic_id' => (int) $topic_id, + 'post_id' => (int) $post_id, + 'logs' => [$logs[$action] => $value[$action]], + ]; + + $this->add($user_id, $points); + } + } +} diff --git a/ext/phpbbstudio/aps/actions/type/vote.php b/ext/phpbbstudio/aps/actions/type/vote.php new file mode 100644 index 0000000..e254bb8 --- /dev/null +++ b/ext/phpbbstudio/aps/actions/type/vote.php @@ -0,0 +1,117 @@ + 'APS_POINTS_PER_VOTE', + ]; + } + + /** + * Calculate points for this type. + * + * @param array $data The data available from the $event that triggered this action + * @param array $values The point values available, indexed per forum_id and 0 for global values + * @retrun void + */ + public function calculate($data, $values) + { + $votes = $data['vote_counts']; + $options = $data['poll_info']; + $forum_id = $data['forum_id']; + $topic_id = $data['topic_data']['topic_id']; + $value = $values[$forum_id]['aps_vote']; + + $i = 0; + + foreach ($options as $option) + { + $new = $votes[$option['poll_option_id']]; + $old = $option['poll_option_total']; + + if ($new > $old) + { + $i++; + } + else if ($new < $old) + { + $i--; + } + } + + if ($i !== 0) + { + $points = $this->equate($value, $i, '*'); + + foreach ($this->users as $user_id => $user_data) + { + $string = $points > 0 ? 'APS_POINTS_VOTE_ADDED' : 'APS_POINTS_VOTE_REMOVED'; + + $this->add($user_id, [ + 'forum_id' => $forum_id, + 'topic_id' => $topic_id, + 'post_id' => 0, + 'points' => $points, + 'logs' => [ + $string => $points, + 'APS_POINTS_VOTE_AMOUNT' => $i, + ], + ]); + } + } + } +} diff --git a/ext/phpbbstudio/aps/actions/type/warn.php b/ext/phpbbstudio/aps/actions/type/warn.php new file mode 100644 index 0000000..1edb84b --- /dev/null +++ b/ext/phpbbstudio/aps/actions/type/warn.php @@ -0,0 +1,104 @@ +user = $user; + } + + /** + * Get action name. + * + * @return string The name of the action this type belongs to + * @access public + */ + public function get_action() + { + return 'warn'; + } + + /** + * Get global state. + * + * @return bool If this type is global or local (per-forum basis) + * @access public + */ + public function is_global() + { + return true; + } + + /** + * Get type category under which it will be listed in the ACP. + * + * @return string The name of the category this type belongs to + * @access public + */ + public function get_category() + { + return 'ACP_APS_POINTS_MISC'; + } + + /** + * Get type data. + * + * @return array An array of value names and their language string + * @access public + */ + public function get_data() + { + return [ + 'aps_mod_warn' => 'APS_POINTS_MOD_WARN', + 'aps_user_warn' => 'APS_POINTS_USER_WARN', + ]; + } + + /** + * Calculate points for this type. + * + * @param array $data The data available from the $event that triggered this action + * @param array $values The point values available, indexed per forum_id and 0 for global values + * @retrun void + */ + public function calculate($data, $values) + { + $value = $values[0]; + $logs = $this->get_data(); + + foreach (array_keys($this->users) as $user_id) + { + $mode = $user_id == $this->user->data['user_id'] ? 'mod' : 'user'; + + $points = [ + 'points' => (double) $value['aps_' . $mode . '_warn'], + 'logs' => [$logs['aps_' . $mode . '_warn'] => $value['aps_' . $mode . '_warn']], + ]; + + $this->add($user_id, $points); + } + } +} diff --git a/ext/phpbbstudio/aps/adm/style/aps_display.html b/ext/phpbbstudio/aps/adm/style/aps_display.html new file mode 100644 index 0000000..5039c83 --- /dev/null +++ b/ext/phpbbstudio/aps/adm/style/aps_display.html @@ -0,0 +1,117 @@ +{% include 'overall_header.html' %} + +{% INCLUDECSS '@phpbbstudio_aps/css/aps_form.css' %} + +

{{ PAGE_TITLE }}

+

{{ lang('ACP_APS_DISPLAY_EXPLAIN', aps_name()) }}

+ +{% if S_ERROR %} +
+

{{ lang('WARNING') }}

+

{{ ERROR_MSG }}

+
+{% endif %} + +
+ + {% EVENT phpbbstudio_aps_acp_display_before %} + +
+ {{ lang('GENERAL_SETTINGS') }} + + {% EVENT phpbbstudio_aps_acp_display_prepend %} + +
+
+
+ {{ lang('ACP_APS_DISPLAY_TOP_CHANGE_DESC') }} +
+
+ + +
+
+
+
+
+ {{ lang('ACP_APS_DISPLAY_TOP_COUNT_DESC') }} +
+
+
+
+
+
+ {{ lang('ACP_APS_DISPLAY_ADJUSTMENTS_DESC') }} +
+
+
+
+
+
+ {{ lang('ACP_APS_DISPLAY_GRAPH_TIME_DESC') }} +
+
+
+ + {% EVENT phpbbstudio_aps_acp_display_append %} +
+ + {% EVENT phpbbstudio_aps_acp_display_after %} + +
+ {% for page in aps_pages %} +
+
+
{{ page.TITLE }}
+
+ +
+
+ +
+ {% for block in page.blocks %} +
+
{{ block.TITLE }}
+
+ + + +
+
+ {% endfor %} +
+
+ {% endfor %} +
+ +
+ {{ lang('ACP_SUBMIT_CHANGES') }} + +

+   + + {{ S_FORM_TOKEN }} +

+
+
+ +{% INCLUDEJS '@phpbbstudio_aps/js/jquery-ui-sortable.min.js' %} +{% INCLUDEJS '@phpbbstudio_aps/js/aps_display.js' %} + +{% include 'overall_footer.html' %} diff --git a/ext/phpbbstudio/aps/adm/style/aps_locations.html b/ext/phpbbstudio/aps/adm/style/aps_locations.html new file mode 100644 index 0000000..6157150 --- /dev/null +++ b/ext/phpbbstudio/aps/adm/style/aps_locations.html @@ -0,0 +1,81 @@ +{% macro location(name, status) %} + +{% endmacro %} + +{% from _self import location as location %} + +{% INCLUDECSS '@phpbbstudio_aps/css/aps_locations.css' %} + +
{{ lang('BACK_TO_PREV') }}
+ +
+
+ +

{{ lang('ACP_APS_LOCATIONS_EXPLAIN') }}

+ + +
+ +
+ {{ lang('ACP_SUBMIT_CHANGES') }} +

+ + + +

+
+ +
+ {{ lang('BACK_TO_PREV') }} +
+
diff --git a/ext/phpbbstudio/aps/adm/style/aps_logs.html b/ext/phpbbstudio/aps/adm/style/aps_logs.html new file mode 100644 index 0000000..cb55f9e --- /dev/null +++ b/ext/phpbbstudio/aps/adm/style/aps_logs.html @@ -0,0 +1,42 @@ +{% include 'overall_header.html' %} + +{% INCLUDECSS '@phpbbstudio_aps/css/aps_common.css' %} + +

{{ PAGE_TITLE }}

+

{{ lang('ACP_APS_LOGS_EXPLAIN', aps_name()) }}

+ +
+ + + {% if pagination %} + + {% endif %} + + {% include '@phpbbstudio_aps/aps_logs_list.html' with {'s_include_mark': true} %} + + {% if pagination %} + + {% endif %} + +
+ {{ lang('DISPLAY_LOG') ~ lang('COLON') }} {{ S_LIMIT_DAYS }}   + {{ lang('SORT_BY') ~ lang('COLON') }} {{ S_SORT_KEY }} {{ S_SORT_DIR }} + + {{ S_FORM_TOKEN }} +
+
+ +
+   +
+

{{ lang('MARK_ALL') }}{{ lang('UNMARK_ALL') }}

+
+
+ +{% include 'overall_footer.html' %} diff --git a/ext/phpbbstudio/aps/adm/style/aps_logs_list.html b/ext/phpbbstudio/aps/adm/style/aps_logs_list.html new file mode 100644 index 0000000..a02f768 --- /dev/null +++ b/ext/phpbbstudio/aps/adm/style/aps_logs_list.html @@ -0,0 +1,50 @@ + + + + + + + + + + {% if s_include_mark %} + + {% endif %} + + + + {% for log in logs %} + + + + + + + + {% if s_include_mark %} + + {% endif %} + + {% else %} + + + + {% endfor %} + +
{{ lang('USERNAME') }}{{ lang('TIME') }}{{ lang('APS_POINTS_OLD', aps_name()) }}{{ lang('APS_POINTS_DIFF', aps_name()) }}{{ lang('APS_POINTS_NEW', aps_name()) }}{{ lang('ACTION') }}{{ lang('MARK') }}
{{ log.USER }}{% if not log.S_SELF and log.REPORTEE %}
» {{ lang('FROM') ~ ' ' ~ log.REPORTEE }}{% endif %}
{{ user.format_date(log.TIME) }}{{ aps_display(log.POINTS_OLD, false) }}{{ aps_display(log.POINTS_SUM, false) }}{{ aps_display(log.POINTS_NEW, false) }}
+ + {{ lang(log.ACTION, aps_name()) }} + + {% if log.FORUM_NAME %}» {{ log.FORUM_NAME }}{% endif %} + {% if log.TOPIC_TITLE %}» {{ log.TOPIC_TITLE }}{% endif %} + {% if log.POST_SUBJECT %}» {{ log.POST_SUBJECT }}{% endif %} +
+
+

{{ lang('NO_ENTRIES') }}

+
+
diff --git a/ext/phpbbstudio/aps/adm/style/aps_points.html b/ext/phpbbstudio/aps/adm/style/aps_points.html new file mode 100644 index 0000000..e2d4a84 --- /dev/null +++ b/ext/phpbbstudio/aps/adm/style/aps_points.html @@ -0,0 +1,99 @@ +{% include 'overall_header.html' %} + +{% INCLUDECSS '@phpbbstudio_aps/css/aps_common.css' %} +{% INCLUDECSS '@phpbbstudio_aps/css/aps_form.css' %} + +

{{ APS_TITLE }}

+

{{ lang('ACP_APS_POINTS_EXPLAIN', aps_name()) }}

+ +
+ + {% if S_ERRORS %} +

{{ ERROR_MSG }}

+ {% endif %} + + {% if S_APS_ACTION in ['add', 'edit'] %} + {% if S_APS_REASONS %} +
+
+
+
+
+
+
+ +
+
+
+
+
+
+ {% endif %} + {% else %} + {% if S_APS_REASONS %} + + + + + + + + + + {% for reason in aps_reasons %} + + + + + + {% else %} + + + + {% endfor %} + +
{{ aps_name() }}{{ lang('REASON') }}{{ lang('ACTIONS') }}
+ {{ aps_format(reason.POINTS) }} + + {{ reason.TITLE }}
+ {{ reason.DESC }} +
+ + {{ ICON_MOVE_UP }} + + {{ ICON_MOVE_DOWN }} + {{ ICON_EDIT }} + {{ ICON_DELETE }} +
{{ lang('ACP_NO_ITEMS') }}
+ +
+ +
+ {{ lang('ADD') }} +
+ + +
+ {% endif %} + {% if S_APS_POINTS %} + {{ aps_name() }} + + {% include '@phpbbstudio_aps/aps_points_list.html' %} + + {% endif %} + {% endif %} + +
+ +
+ {{ lang('ACP_SUBMIT_CHANGES') }} + +

+   + + {{ S_FORM_TOKEN }} +

+
+ + +{% include 'overall_footer.html' %} diff --git a/ext/phpbbstudio/aps/adm/style/aps_points_copy.html b/ext/phpbbstudio/aps/adm/style/aps_points_copy.html new file mode 100644 index 0000000..98a4e47 --- /dev/null +++ b/ext/phpbbstudio/aps/adm/style/aps_points_copy.html @@ -0,0 +1,37 @@ +
+
+
+
{{ lang('ACP_APS_POINTS_COPY', aps_name()) }}
+
+ +
+
+
{{ lang('ACP_APS_POINTS_COPY_TO', aps_name()) }}
+
+ +

{{ lang('LOOK_UP_FORUMS_EXPLAIN') }}

+
+
+
+
+ {{ S_FORM_TOKEN }} +   + +
+
+ + diff --git a/ext/phpbbstudio/aps/adm/style/aps_points_list.html b/ext/phpbbstudio/aps/adm/style/aps_points_list.html new file mode 100644 index 0000000..9431158 --- /dev/null +++ b/ext/phpbbstudio/aps/adm/style/aps_points_list.html @@ -0,0 +1,17 @@ +
+ {% for category in aps_categories %} + + +
+ {% for fields in category.blocks %} + {% for field, title in fields %} +
+
{% if lang(title ~ '_DESC') != title ~ '_DESC' %}
{{ lang(title ~ '_DESC', aps_name()) }}{% endif %}
+
+
+ {% endfor %} + {% if not loop.last %}
{% endif %} + {% endfor %} +
+ {% endfor %} +
diff --git a/ext/phpbbstudio/aps/adm/style/aps_settings.html b/ext/phpbbstudio/aps/adm/style/aps_settings.html new file mode 100644 index 0000000..6707228 --- /dev/null +++ b/ext/phpbbstudio/aps/adm/style/aps_settings.html @@ -0,0 +1,54 @@ +{% include 'overall_header.html' %} + +{% INCLUDECSS '@phpbbstudio_aps/css/fontawesome-iconpicker.min.css' %} +{% INCLUDECSS '@phpbbstudio_aps/css/aps_common.css' %} +{% INCLUDECSS '@phpbbstudio_aps/css/aps_form.css' %} +{% INCLUDECSS '@phpbbstudio_aps/css/aps_iconpicker.css' %} + +

{{ PAGE_TITLE }}

+

{{ lang('ACP_APS_SETTINGS_EXPLAIN', aps_name()) }}

+ +{% if S_ERROR %} +
+

{{ lang('WARNING') }}

+

{{ ERROR_MSG }}

+
+{% endif %} + +{% if S_APS_COPY %} + {% include '@phpbbstudio_aps/aps_points_copy.html' %} +{% elseif S_APS_LOCATIONS %} + {% include '@phpbbstudio_aps/aps_locations.html' %} +{% else %} +
+ {% for setting in settings %} + {% if setting.S_LEGEND %} + {% if not loop.first %}{% endif %} + +
+ {{ lang(setting.LEGEND, aps_name()) }} + {% else %} +
+
{% if setting.S_EXPLAIN %}
{{ lang(setting.TITLE ~ '_DESC', aps_name()) }}{% endif %}
+
{{ setting.CONTENT }}
+
+ {% endif %} + {% if loop.last %}
{% endif %} + {% endfor %} + +
+ {{ lang('ACP_SUBMIT_CHANGES') }} + +

+   + + {{ S_FORM_TOKEN }} +

+
+
+{% endif %} + +{% INCLUDEJS '@phpbbstudio_aps/js/fontawesome-iconpicker.min.js' %} +{% INCLUDEJS '@phpbbstudio_aps/js/aps_common.js' %} + +{% include 'overall_footer.html' %} diff --git a/ext/phpbbstudio/aps/adm/style/css/aps_common.css b/ext/phpbbstudio/aps/adm/style/css/aps_common.css new file mode 100644 index 0000000..c22215f --- /dev/null +++ b/ext/phpbbstudio/aps/adm/style/css/aps_common.css @@ -0,0 +1,115 @@ +.nojs .aps-ajax { + display: none; +} + +.aps-tabs { + background: #e5e5e5; + border-top: 1px solid #d7d7d7; + border-right: 1px solid #cccccc; + border-bottom: 1px solid #cccccc; + border-left: 1px solid #d7d7d7; + border-radius: 3px; + display: flex; + flex-wrap: wrap; +} + +.aps-tab-input { + opacity: 0; + position: absolute; +} + +.aps-tab-label { + font-size: 1.2em; + font-weight: bold; + background: #e5e5e5; + border-right: 1px solid transparent; + border-left: 1px solid transparent; + color: #7f7f7f; + width: 100%; + padding: 10px 20px; + cursor: pointer; + transition: all 0.2s ease-in; +} +.aps-tab-label:first-of-type { border-top-left-radius: 3px; } +.aps-tab-label:last-of-type { border-top-right-radius: 3px; } +.aps-tab-label:hover { background: #d8d8d8; } +.aps-tab-label:active { background: #cccccc; } + +.aps-tab-input:checked + .aps-tab-label { + background: #ffffff; + border-color: #cccccc; + color: #000000; +} + +.aps-tab-input:checked:first-of-type + .aps-tab-label { + border-left-color: transparent; +} + +@media (min-width: 600px) { + .aps-tab-label { + width: auto; + } +} + +.aps-tab-panel { + background: #ffffff; + border: none; + border-top-left-radius: 0; + border-top-right-radius: 0; + display: none; + width: 100%; + margin: 0; +} + +@media (min-width: 600px) { + .aps-tab-panel { + order: 99; + } +} + +.aps-tab-input:checked + .aps-tab-label + .aps-tab-panel { + display: block; +} + +.aps-tabs h3 { + font-size: 1.2em; + color: #115098; + margin: 5px 0 10px; +} + +.aps-tabs small { + font-size: 90%; +} + +/* aps logs */ +table th.aps-logs-mark, +table td.aps-logs-mark { width: 50px; } +.aps-logs-user { width: 15%; } +.aps-logs-time { width: 20%; } +.aps-logs-points { width: 10%; } + +.aps-logs-action { + display: block; +} + +.aps-logs-edit { + font-size: 120%; + text-decoration: none; + float: right; + margin-right: 5px; +} + +.aps-logs-mark label { + display: block; + padding: 0; +} + +@media all and (max-width: 700px) { + .aps-logs-edit { + margin-right: 0; + } + + .aps-logs-mark label { + display: inline-block; + } +} diff --git a/ext/phpbbstudio/aps/adm/style/css/aps_form.css b/ext/phpbbstudio/aps/adm/style/css/aps_form.css new file mode 100644 index 0000000..b894371 --- /dev/null +++ b/ext/phpbbstudio/aps/adm/style/css/aps_form.css @@ -0,0 +1,211 @@ +.aps-form *, +.aps-form *:before, +.aps-form *:after { + box-sizing: border-box; +} + +.aps-bool { + display: none; +} + +.aps-radio { display: none; } + +.aps-radio:checked + .aps-button-blue { + background: #12a3eb; + border-color: #12a3eb; + color: #ffffff; +} + +/* Fix for double borders due to border-box */ +.aps-form dt { + border-right: none; +} + +.aps-form dd, +.aps-form dd label { + font-size: 14px; + line-height: 1.42857143; +} + +.aps-form dd label { + display: inline-block; + height: 34px; + padding: 6px 6px 6px 0; +} + +.aps-form dd label input[type="radio"] { + height: initial; + margin-right: 3px; +} + +.aps-button-red, +.aps-button-blue, +.aps-button-green, +.aps-form input:not(.iconpicker-search), +.aps-form select, +.aps-form textarea { + font-size: 14px; + line-height: 1.42857143; + color: #555555; + height: 34px; + padding: 6px 12px; +} + +.aps-button-red, +.aps-button-blue, +.aps-button-green, +.aps-form input[type="text"], +.aps-form input[type="number"], +.aps-form input[type="submit"], +.aps-form input[type="reset"], +.aps-form select, +.aps-form textarea { + background-color: #ffffff; + background-image: none; + border: 1px solid #cccccc; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -webkit-transition: border-color ease-in-out 0.15s, -webkit-box-shadow ease-in-out 0.15s; + -o-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; +} + +.aps-form input:not([type="checkbox"]):not([type="radio"]):focus, +.aps-form select:focus, +.aps-form textarea:focus { + border-color: #66afe9; + outline: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6); +} + +.aps-form select:focus { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} + +.aps-form select[multiple] { + height: auto; + min-height: 170px; +} + +.has-js select[multiple] { + max-height: 340px; +} + +.aps-form textarea { + height: auto; + resize: vertical; +} + +/* Buttons */ +.aps-button-green, +a.aps-button-green, +.aps-form input[type="submit"] { + border-color: #28a745; + color: #28a745; +} + +.aps-bool:checked + .aps-button-green, +.aps-button-green:hover, +a.aps-button-green:hover, +.aps-form input[type="submit"]:hover { + background-color: #28a745; + color: #ffffff; +} + +.aps-button-red, +a.aps-button-red, +.aps-form input[type="reset"] { + border-color: #d31141; + color: #d31141; +} + +.aps-bool:checked + .aps-button-red, +.aps-button-red:hover, +a.aps-button-red:hover, +.aps-form input[type="reset"]:hover { + background-color: #d31141; + color: #ffffff; +} + +.aps-button-blue { + border-color: #12a3eb; + color: #12a3eb; +} + +.aps-button-blue:hover { + background-color: #12a3eb; + color: #ffffff; +} + +.aps-button-red, +.aps-button-blue, +.aps-button-green { + text-decoration: none !important; + cursor: pointer; +} + +/* Point names height toggle */ +.aps-points-names { + overflow: hidden; + max-height: 100px; + padding-bottom: 20px; +} + +.aps-points-names dl:not(:first-of-type) { + opacity: 0; +} + +.aps-points-full { + max-height: 100%; + padding-bottom: 30px; +} + +.aps-points-full dl:not(:first-of-type) { + opacity: 1; +} + +@media all and (max-width: 700px) { + fieldset.aps-points-names:not(.aps-points-full) dl:first-of-type { + border-bottom: none; + } +} + +.aps-names-toggle { + font-weight: bold; + text-decoration: none !important; + border: 1px solid #cccccc; + border-bottom: none; + border-radius: 4px 4px 0 0; + opacity: 1; + position: absolute; + z-index: 1; + bottom: 0; + left: 45%; + padding: 5px 10px; + cursor: pointer; + user-select: none; + transform: translateX(-50%); +} + +/* Multiple select scrollbar */ +.aps-form select[multiple] { + scrollbar-color: #666666 #cccccc; + scrollbar-width: 10px; +} + +.aps-form select[multiple]::-webkit-scrollbar { + width: 10px; +} + +.aps-form select[multiple]::-webkit-scrollbar-thumb { + background: #666666; + border-radius: 0 4px 4px 0; +} + +.aps-form select[multiple]::-webkit-scrollbar-track { + background: #cccccc; + border-radius: 0 4px 4px 0; +} diff --git a/ext/phpbbstudio/aps/adm/style/css/aps_iconpicker.css b/ext/phpbbstudio/aps/adm/style/css/aps_iconpicker.css new file mode 100644 index 0000000..a60ee67 --- /dev/null +++ b/ext/phpbbstudio/aps/adm/style/css/aps_iconpicker.css @@ -0,0 +1,78 @@ +.aps-icon-picker, +#aps_points_icon { + border-top-right-radius: 0 !important; + border-bottom-right-radius: 0 !important; +} + +.aps-icon-picker + i, +#aps_points_icon + i { + font-size: 14px; + line-height: 1.42857143; + vertical-align: -1px; + background-color: #f3f3f3; + border: 1px solid #cccccc; + border-left: none; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + color: #555555; + display: inline-block; + width: auto; + height: 34px; + padding: 6px 12px; + cursor: pointer; +} + +.aps-icon-picker:focus, +#aps_points_icon:focus { + border-right-color: #cccccc; +} + +.aps-icon-picker:focus + i, +#aps_points_icon:focus + i { + border-color: #66afe9; + outline: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6); +} + +.iconpicker-popover.popover { + border: 1px solid #cccccc; + border-radius: 4px; +} + +.iconpicker-popover .arrow:after { + border-bottom-color: #f7f7f7 !important; +} + +.iconpicker-popover .iconpicker-item { + margin: 0 11px 11px 0; +} + +.iconpicker-search, +.iconpicker-search:hover, +.iconpicker-search:focus { + background-color: #ffffff; + border-radius: 4px; + max-width: calc(100% - 16px); + padding: 5px 8px; +} + +.iconpicker-popover ::-webkit-scrollbar { + background: transparent; + width: 5px; + height: 5px; +} + +.iconpicker-popover ::-webkit-scrollbar-corner { + background: transparent; +} + +.iconpicker-popover ::-webkit-scrollbar-thumb { + background: #333333; + border-radius: 10px; +} + +.iconpicker-popover ::-webkit-scrollbar-track { + background: #f3f3f3; + border-radius: 10px; +} diff --git a/ext/phpbbstudio/aps/adm/style/css/aps_locations.css b/ext/phpbbstudio/aps/adm/style/css/aps_locations.css new file mode 100644 index 0000000..a3e00d0 --- /dev/null +++ b/ext/phpbbstudio/aps/adm/style/css/aps_locations.css @@ -0,0 +1,94 @@ +.aps-locations { + background-color: #ffffff; + border: 1px solid #e6e9ed; + border-radius: 8px; + padding: 15px; +} + +.aps-locations > p { + text-align: center; + margin: 5rem 160px; +} + +.aps-location input { + display: none; +} + +.aps-location input ~ span { + line-height: 16px; + text-align: center; + color: #ffffff; + display: none; + min-width: 50px; + height: 30px; +} + +.aps-location input:not(:checked) ~ .aps-button-red, +.aps-location input:checked ~ .aps-button-green { display: inline-block; } +.aps-location .aps-button-green { background-color: #28a745; } +.aps-location .aps-button-red { background-color: #d31141; } + +.navbar { + background-color: #cadceb; + border-radius: 7px; + overflow: visible; + height: 40px; + padding: 3px 10px; +} + +.navbar > ul { + display: block; + margin: 0; + padding: 2px 0; + list-style-type: none; +} + +.navbar > ul > li { + font-size: 1.1em; + line-height: 2.2em; + float: left; + width: auto; + margin-right: 7px; + padding-top: 1px; + list-style-type: none; +} + +.navbar > ul > .right-side { + text-align: right; + float: right; + margin-right: 0; + margin-left: 7px; +} + +.has-dropdown { + position: relative; +} + +.dropdown { + text-align: left; + z-index: 10; + top: 25px; + min-width: 150px; +} + +.dropdown.quick-links { + left: -15px; +} + +.dropdown.user { + right: -10px; + left: unset; +} + +.dropdown .pointer { + border-color: #ffffff transparent; +} + +.dropdown.user .pointer { + right: 10px; + left: unset; +} + +.dropdown .dropdown-contents { + padding: 10px 5px; +} diff --git a/ext/phpbbstudio/aps/adm/style/css/fontawesome-iconpicker.min.css b/ext/phpbbstudio/aps/adm/style/css/fontawesome-iconpicker.min.css new file mode 100644 index 0000000..7c68265 --- /dev/null +++ b/ext/phpbbstudio/aps/adm/style/css/fontawesome-iconpicker.min.css @@ -0,0 +1,10 @@ +/*! + * Font Awesome Icon Picker + * https://farbelous.github.io/fontawesome-iconpicker/ + * + * Originally written by (c) 2016 Javi Aguilar + * Licensed under the MIT License + * https://github.com/farbelous/fontawesome-iconpicker/blob/master/LICENSE + * + */.iconpicker-popover.popover{position:absolute;top:0;left:0;display:none;max-width:none;padding:1px;text-align:left;width:216px;background:#f7f7f7;z-index:9}.iconpicker-popover.popover.top,.iconpicker-popover.popover.topLeftCorner,.iconpicker-popover.popover.topLeft,.iconpicker-popover.popover.topRight,.iconpicker-popover.popover.topRightCorner{margin-top:-10px}.iconpicker-popover.popover.right,.iconpicker-popover.popover.rightTop,.iconpicker-popover.popover.rightBottom{margin-left:10px}.iconpicker-popover.popover.bottom,.iconpicker-popover.popover.bottomRightCorner,.iconpicker-popover.popover.bottomRight,.iconpicker-popover.popover.bottomLeft,.iconpicker-popover.popover.bottomLeftCorner{margin-top:10px}.iconpicker-popover.popover.left,.iconpicker-popover.popover.leftBottom,.iconpicker-popover.popover.leftTop{margin-left:-10px}.iconpicker-popover.popover.inline{margin:0 0 12px 0;position:relative;display:inline-block;opacity:1;top:auto;left:auto;bottom:auto;right:auto;max-width:100%;box-shadow:none;z-index:auto;vertical-align:top}.iconpicker-popover.popover.inline>.arrow{display:none}.dropdown-menu .iconpicker-popover.inline{margin:0;border:none}.dropdown-menu.iconpicker-container{padding:0}.iconpicker-popover.popover .popover-title{padding:12px;font-size:13px;line-height:15px;border-bottom:1px solid #ebebeb;background-color:#f7f7f7}.iconpicker-popover.popover .popover-title input[type=search].iconpicker-search{margin:0 0 2px 0}.iconpicker-popover.popover .popover-title-text~input[type=search].iconpicker-search{margin-top:12px}.iconpicker-popover.popover .popover-content{padding:0px;text-align:center}.iconpicker-popover .popover-footer{float:none;clear:both;padding:12px;text-align:right;margin:0;border-top:1px solid #ebebeb;background-color:#f7f7f7}.iconpicker-popover .popover-footer:before,.iconpicker-popover .popover-footer:after{content:" ";display:table}.iconpicker-popover .popover-footer:after{clear:both}.iconpicker-popover .popover-footer .iconpicker-btn{margin-left:10px}.iconpicker-popover .popover-footer input[type=search].iconpicker-search{margin-bottom:12px}.iconpicker-popover.popover>.arrow,.iconpicker-popover.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.iconpicker-popover.popover>.arrow{border-width:11px}.iconpicker-popover.popover>.arrow:after{border-width:10px;content:""}.iconpicker-popover.popover.top>.arrow,.iconpicker-popover.popover.topLeft>.arrow,.iconpicker-popover.popover.topRight>.arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);bottom:-11px}.iconpicker-popover.popover.top>.arrow:after,.iconpicker-popover.popover.topLeft>.arrow:after,.iconpicker-popover.popover.topRight>.arrow:after{content:" ";bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#fff}.iconpicker-popover.popover.topLeft>.arrow{left:8px;margin-left:0}.iconpicker-popover.popover.topRight>.arrow{left:auto;right:8px;margin-left:0}.iconpicker-popover.popover.right>.arrow,.iconpicker-popover.popover.rightTop>.arrow,.iconpicker-popover.popover.rightBottom>.arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#999;border-right-color:rgba(0,0,0,0.25)}.iconpicker-popover.popover.right>.arrow:after,.iconpicker-popover.popover.rightTop>.arrow:after,.iconpicker-popover.popover.rightBottom>.arrow:after{content:" ";left:1px;bottom:-10px;border-left-width:0;border-right-color:#fff}.iconpicker-popover.popover.rightTop>.arrow{top:auto;bottom:8px;margin-top:0}.iconpicker-popover.popover.rightBottom>.arrow{top:8px;margin-top:0}.iconpicker-popover.popover.bottom>.arrow,.iconpicker-popover.popover.bottomRight>.arrow,.iconpicker-popover.popover.bottomLeft>.arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);top:-11px}.iconpicker-popover.popover.bottom>.arrow:after,.iconpicker-popover.popover.bottomRight>.arrow:after,.iconpicker-popover.popover.bottomLeft>.arrow:after{content:" ";top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff}.iconpicker-popover.popover.bottomLeft>.arrow{left:8px;margin-left:0}.iconpicker-popover.popover.bottomRight>.arrow{left:auto;right:8px;margin-left:0}.iconpicker-popover.popover.left>.arrow,.iconpicker-popover.popover.leftBottom>.arrow,.iconpicker-popover.popover.leftTop>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,0.25)}.iconpicker-popover.popover.left>.arrow:after,.iconpicker-popover.popover.leftBottom>.arrow:after,.iconpicker-popover.popover.leftTop>.arrow:after{content:" ";right:1px;border-right-width:0;border-left-color:#fff;bottom:-10px}.iconpicker-popover.popover.leftBottom>.arrow{top:8px;margin-top:0}.iconpicker-popover.popover.leftTop>.arrow{top:auto;bottom:8px;margin-top:0}.iconpicker{position:relative;text-align:left;text-shadow:none;line-height:0;display:block;margin:0;overflow:hidden}.iconpicker *{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;position:relative}.iconpicker:before,.iconpicker:after{content:" ";display:table}.iconpicker:after{clear:both}.iconpicker .iconpicker-items{position:relative;clear:both;float:none;padding:12px 0 0 12px;background:#fff;margin:0;overflow:hidden;overflow-y:auto;min-height:49px;max-height:246px}.iconpicker .iconpicker-items:before,.iconpicker .iconpicker-items:after{content:" ";display:table}.iconpicker .iconpicker-items:after{clear:both}.iconpicker .iconpicker-item{float:left;width:14px;height:14px;padding:12px;margin:0 12px 12px 0;text-align:center;cursor:pointer;border-radius:3px;font-size:14px;box-shadow:0 0 0 1px #ddd;color:inherit}.iconpicker .iconpicker-item:hover:not(.iconpicker-selected){background-color:#eee}.iconpicker .iconpicker-item.iconpicker-selected{box-shadow:none;color:#fff;background:#28a745}.iconpicker-component{cursor:pointer} +.iconpicker-popover, .iconpicker-popover *, .iconpicker-popover *:before, .iconpicker-popover *:after {box-sizing: content-box !important;} diff --git a/ext/phpbbstudio/aps/adm/style/event/acp_forums_custom_settings.html b/ext/phpbbstudio/aps/adm/style/event/acp_forums_custom_settings.html new file mode 100644 index 0000000..352dd45 --- /dev/null +++ b/ext/phpbbstudio/aps/adm/style/event/acp_forums_custom_settings.html @@ -0,0 +1,47 @@ +{% if S_APS_POINTS %} +
+ {{ aps_name() }} + + {# We can only AJAX copy and reset if the forum already exists #} + {% if not S_ADD_ACTION %} + {# Include the JS file #} + {% INCLUDEJS '@phpbbstudio_aps/js/aps_forum.js' %} + +
+

{{ lang('ACP_APS_POINTS_RESET_EXPLAIN', aps_name()) }}
+
+ + + +
+
+ {% endif %} + +
+

{{ lang('ACP_APS_POINTS_COPY_EXPLAIN', aps_name()) }}
+
+ + {% if not S_ADD_ACTION %} + + {% endif %} +
+
+ + {% INCLUDECSS '@phpbbstudio_aps/css/aps_common.css' %} + {% INCLUDECSS '@phpbbstudio_aps/css/aps_form.css' %} + + {% include '@phpbbstudio_aps/aps_points_list.html' %} +
+{% endif %} diff --git a/ext/phpbbstudio/aps/adm/style/event/acp_users_overview_options_append.html b/ext/phpbbstudio/aps/adm/style/event/acp_users_overview_options_append.html new file mode 100644 index 0000000..f72e50c --- /dev/null +++ b/ext/phpbbstudio/aps/adm/style/event/acp_users_overview_options_append.html @@ -0,0 +1,4 @@ +
+
+
{{ aps_display(APS_POINTS) }}
+
diff --git a/ext/phpbbstudio/aps/adm/style/js/aps_common.js b/ext/phpbbstudio/aps/adm/style/js/aps_common.js new file mode 100644 index 0000000..be9a3ac --- /dev/null +++ b/ext/phpbbstudio/aps/adm/style/js/aps_common.js @@ -0,0 +1,13 @@ +jQuery(function($) { + $('.aps-names-toggle').on('click', function() { + $(this).parents('fieldset').toggleClass('aps-points-full'); + let altText = $(this).data('text'); + $(this).data('text', $(this).text()).text(altText); + }); + + $('#aps_points_icon').iconpicker({ + collision: true, + placement: 'bottomRight', + component: '#aps_points_icon + i', + }); +}); diff --git a/ext/phpbbstudio/aps/adm/style/js/aps_display.js b/ext/phpbbstudio/aps/adm/style/js/aps_display.js new file mode 100644 index 0000000..15fd38d --- /dev/null +++ b/ext/phpbbstudio/aps/adm/style/js/aps_display.js @@ -0,0 +1,12 @@ +jQuery(function($) { + $('[data-aps-sortable]').sortable({ + axis: 'y', + containment: $(this).selector, + cursor: 'move', + delay: 150, + handle: '.aps-button-blue', + forcePlaceholderSize: true, + placeholder: 'panel', + tolerance: 'pointer', + }); +}); diff --git a/ext/phpbbstudio/aps/adm/style/js/aps_forum.js b/ext/phpbbstudio/aps/adm/style/js/aps_forum.js new file mode 100644 index 0000000..9af5d6f --- /dev/null +++ b/ext/phpbbstudio/aps/adm/style/js/aps_forum.js @@ -0,0 +1,83 @@ +jQuery(function($) { + let $button = $('#aps_points_copy_ajax'), + $select = $('#aps_points_copy'), + $form = $button.parents('form'), + action = $form.attr('action').replace('&', '&'); + + let callback = 'aps_points_copy', + $dark = $('#darkenwrapper'); + + $button.on('click', function(e) { + /** + * 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(); + let 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); + } + + let request = $.ajax({ + url: action, + type: 'post', + data: {'aps_action': 'copy', 'aps_points_copy': $select.val()}, + success: function(response) { + /** + * @param {string} response.MESSAGE_TITLE + * @param {string} response.MESSAGE_TEXT + */ + phpbb.alert(response.MESSAGE_TITLE, response.MESSAGE_TEXT); + + if (typeof phpbb.ajaxCallbacks[callback] === 'function') { + phpbb.ajaxCallbacks[callback].call(this, response); + } + }, + error: errorHandler, + cache: false + }); + + request.always(function() { + let $loadingIndicator = phpbb.loadingIndicator(); + + if ($loadingIndicator && $loadingIndicator.is(':visible')) { + $loadingIndicator.fadeOut(phpbb.alertTime); + } + }); + + e.preventDefault(); + }); + + phpbb.addAjaxCallback('aps_points_copy', function(response) { + $select.val(0); + + /** + * @param {array} response.APS_VALUES + */ + $.each(response.APS_VALUES, function(name, value) { + $('#' + name).val(value); + }); + }); + + phpbb.addAjaxCallback('aps_points_reset', function() { + $('[name*="aps_values"]').each(function() { + $(this).val(0); + }); + }); +}); diff --git a/ext/phpbbstudio/aps/adm/style/js/fontawesome-iconpicker.min.js b/ext/phpbbstudio/aps/adm/style/js/fontawesome-iconpicker.min.js new file mode 100644 index 0000000..2d3eb89 --- /dev/null +++ b/ext/phpbbstudio/aps/adm/style/js/fontawesome-iconpicker.min.js @@ -0,0 +1 @@ +!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a(jQuery)}(function(a){a.ui=a.ui||{};a.ui.version="1.12.1";!function(){function b(a,b,c){return[parseFloat(a[0])*(l.test(a[0])?b/100:1),parseFloat(a[1])*(l.test(a[1])?c/100:1)]}function c(b,c){return parseInt(a.css(b,c),10)||0}function d(b){var c=b[0];return 9===c.nodeType?{width:b.width(),height:b.height(),offset:{top:0,left:0}}:a.isWindow(c)?{width:b.width(),height:b.height(),offset:{top:b.scrollTop(),left:b.scrollLeft()}}:c.preventDefault?{width:0,height:0,offset:{top:c.pageY,left:c.pageX}}:{width:b.outerWidth(),height:b.outerHeight(),offset:b.offset()}}var e,f=Math.max,g=Math.abs,h=/left|center|right/,i=/top|center|bottom/,j=/[\+\-]\d+(\.[\d]+)?%?/,k=/^\w+/,l=/%$/,m=a.fn.pos;a.pos={scrollbarWidth:function(){if(void 0!==e)return e;var b,c,d=a("
"),f=d.children()[0];return a("body").append(d),b=f.offsetWidth,d.css("overflow","scroll"),c=f.offsetWidth,b===c&&(c=d[0].clientWidth),d.remove(),e=b-c},getScrollInfo:function(b){var c=b.isWindow||b.isDocument?"":b.element.css("overflow-x"),d=b.isWindow||b.isDocument?"":b.element.css("overflow-y"),e="scroll"===c||"auto"===c&&b.width0?"right":"center",vertical:h<0?"top":d>0?"bottom":"middle"};nf(g(d),g(h))?l.important="horizontal":l.important="vertical",e.using.call(this,a,l)}),i.offset(a.extend(z,{using:h}))})},a.ui.pos={_trigger:function(a,b,c,d){b.elem&&b.elem.trigger({type:c,position:a,positionData:b,triggered:d})},fit:{left:function(b,c){a.ui.pos._trigger(b,c,"posCollide","fitLeft");var d,e=c.within,g=e.isWindow?e.scrollLeft:e.offset.left,h=e.width,i=b.left-c.collisionPosition.marginLeft,j=g-i,k=i+c.collisionWidth-h-g;c.collisionWidth>h?j>0&&k<=0?(d=b.left+j+c.collisionWidth-h-g,b.left+=j-d):b.left=k>0&&j<=0?g:j>k?g+h-c.collisionWidth:g:j>0?b.left+=j:k>0?b.left-=k:b.left=f(b.left-i,b.left),a.ui.pos._trigger(b,c,"posCollided","fitLeft")},top:function(b,c){a.ui.pos._trigger(b,c,"posCollide","fitTop");var d,e=c.within,g=e.isWindow?e.scrollTop:e.offset.top,h=c.within.height,i=b.top-c.collisionPosition.marginTop,j=g-i,k=i+c.collisionHeight-h-g;c.collisionHeight>h?j>0&&k<=0?(d=b.top+j+c.collisionHeight-h-g,b.top+=j-d):b.top=k>0&&j<=0?g:j>k?g+h-c.collisionHeight:g:j>0?b.top+=j:k>0?b.top-=k:b.top=f(b.top-i,b.top),a.ui.pos._trigger(b,c,"posCollided","fitTop")}},flip:{left:function(b,c){a.ui.pos._trigger(b,c,"posCollide","flipLeft");var d,e,f=c.within,h=f.offset.left+f.scrollLeft,i=f.width,j=f.isWindow?f.scrollLeft:f.offset.left,k=b.left-c.collisionPosition.marginLeft,l=k-j,m=k+c.collisionWidth-i-j,n="left"===c.my[0]?-c.elemWidth:"right"===c.my[0]?c.elemWidth:0,o="left"===c.at[0]?c.targetWidth:"right"===c.at[0]?-c.targetWidth:0,p=-2*c.offset[0];l<0?((d=b.left+n+o+p+c.collisionWidth-i-h)<0||d0&&((e=b.left-c.collisionPosition.marginLeft+n+o+p-j)>0||g(e)0&&((d=b.top-c.collisionPosition.marginTop+o+p+q-j)>0||g(d)10&&e<11,b.innerHTML="",c.removeChild(b)}()}();a.ui.position}),function(a){"use strict";"function"==typeof define&&define.amd?define(["jquery"],a):window.jQuery&&!window.jQuery.fn.iconpicker&&a(window.jQuery)}(function(a){"use strict";var b={isEmpty:function(a){return!1===a||""===a||null===a||void 0===a},isEmptyObject:function(a){return!0===this.isEmpty(a)||0===a.length},isElement:function(b){return a(b).length>0},isString:function(a){return"string"==typeof a||a instanceof String},isArray:function(b){return a.isArray(b)},inArray:function(b,c){return-1!==a.inArray(b,c)},throwError:function(a){throw"Font Awesome Icon Picker Exception: "+a}},c=function(d,e){this._id=c._idCounter++,this.element=a(d).addClass("iconpicker-element"),this._trigger("iconpickerCreate"),this.options=a.extend({},c.defaultOptions,this.element.data(),e),this.options.templates=a.extend({},c.defaultOptions.templates,this.options.templates),this.options.originalPlacement=this.options.placement,this.container=!!b.isElement(this.options.container)&&a(this.options.container),!1===this.container&&(this.element.is(".dropdown-toggle")?this.container=a("~ .dropdown-menu:first",this.element):this.container=this.element.is("input,textarea,button,.btn")?this.element.parent():this.element),this.container.addClass("iconpicker-container"),this.isDropdownMenu()&&(this.options.templates.search=!1,this.options.templates.buttons=!1,this.options.placement="inline"),this.input=!!this.element.is("input,textarea")&&this.element.addClass("iconpicker-input"),!1===this.input&&(this.input=this.container.find(this.options.input),this.input.is("input,textarea")||(this.input=!1)),this.component=this.isDropdownMenu()?this.container.parent().find(this.options.component):this.container.find(this.options.component),0===this.component.length?this.component=!1:this.component.find("i").addClass("iconpicker-component"),this._createPopover(),this._createIconpicker(),0===this.getAcceptButton().length&&(this.options.mustAccept=!1),this.isInputGroup()?this.container.parent().append(this.popover):this.container.append(this.popover),this._bindElementEvents(),this._bindWindowEvents(),this.update(this.options.selected),this.isInline()&&this.show(),this._trigger("iconpickerCreated")};c._idCounter=0,c.defaultOptions={title:!1,selected:!1,defaultValue:!1,placement:"bottom",collision:"none",animation:!0,hideOnSelect:!1,showFooter:!1,searchInFooter:!1,mustAccept:!1,selectedCustomClass:"bg-primary",icons:[],fullClassFormatter:function(a){return"fa "+a},input:"input,.iconpicker-input",inputSearch:!1,container:!1,component:".input-group-addon,.iconpicker-component",templates:{popover:'
',footer:'',buttons:' ',search:'',iconpicker:'
',iconpickerItem:''}},c.batch=function(b,c){var d=Array.prototype.slice.call(arguments,2);return a(b).each(function(){var b=a(this).data("iconpicker");b&&b[c].apply(b,d)})},c.prototype={constructor:c,options:{},_id:0,_trigger:function(b,c){c=c||{},this.element.trigger(a.extend({type:b,iconpickerInstance:this},c))},_createPopover:function(){this.popover=a(this.options.templates.popover);var c=this.popover.find(".popover-title");if(this.options.title&&c.append(a('
'+this.options.title+"
")),this.hasSeparatedSearchInput()&&!this.options.searchInFooter?c.append(this.options.templates.search):this.options.title||c.remove(),this.options.showFooter&&!b.isEmpty(this.options.templates.footer)){var d=a(this.options.templates.footer);this.hasSeparatedSearchInput()&&this.options.searchInFooter&&d.append(a(this.options.templates.search)),b.isEmpty(this.options.templates.buttons)||d.append(a(this.options.templates.buttons)),this.popover.append(d)}return!0===this.options.animation&&this.popover.addClass("fade"),this.popover},_createIconpicker:function(){var b=this;this.iconpicker=a(this.options.templates.iconpicker);var c=function(c){var d=a(this);return d.is("i")&&(d=d.parent()),b._trigger("iconpickerSelect",{iconpickerItem:d,iconpickerValue:b.iconpickerValue}),!1===b.options.mustAccept?(b.update(d.data("iconpickerValue")),b._trigger("iconpickerSelected",{iconpickerItem:this,iconpickerValue:b.iconpickerValue})):b.update(d.data("iconpickerValue"),!0),b.options.hideOnSelect&&!1===b.options.mustAccept&&b.hide(),c.preventDefault(),!1};for(var d in this.options.icons)if("string"==typeof this.options.icons[d]){var e=a(this.options.templates.iconpickerItem);e.find("i").addClass(this.options.fullClassFormatter(this.options.icons[d])),e.data("iconpickerValue",this.options.icons[d]).on("click.iconpicker",c),this.iconpicker.find(".iconpicker-items").append(e.attr("title","."+this.options.icons[d]))}return this.popover.find(".popover-content").append(this.iconpicker),this.iconpicker},_isEventInsideIconpicker:function(b){var c=a(b.target);return!((!c.hasClass("iconpicker-element")||c.hasClass("iconpicker-element")&&!c.is(this.element))&&0===c.parents(".iconpicker-popover").length)},_bindElementEvents:function(){var c=this;this.getSearchInput().on("keyup.iconpicker",function(){c.filter(a(this).val().toLowerCase())}),this.getAcceptButton().on("click.iconpicker",function(){var a=c.iconpicker.find(".iconpicker-selected").get(0);c.update(c.iconpickerValue),c._trigger("iconpickerSelected",{iconpickerItem:a,iconpickerValue:c.iconpickerValue}),c.isInline()||c.hide()}),this.getCancelButton().on("click.iconpicker",function(){c.isInline()||c.hide()}),this.element.on("focus.iconpicker",function(a){c.show(),a.stopPropagation()}),this.hasComponent()&&this.component.on("click.iconpicker",function(){c.toggle()}),this.hasInput()&&this.input.on("keyup.iconpicker",function(d){b.inArray(d.keyCode,[38,40,37,39,16,17,18,9,8,91,93,20,46,186,190,46,78,188,44,86])?c._updateFormGroupStatus(!1!==c.getValid(this.value)):c.update(),!0===c.options.inputSearch&&c.filter(a(this).val().toLowerCase())})},_bindWindowEvents:function(){var b=a(window.document),c=this,d=".iconpicker.inst"+this._id;return a(window).on("resize.iconpicker"+d+" orientationchange.iconpicker"+d,function(a){c.popover.hasClass("in")&&c.updatePlacement()}),c.isInline()||b.on("mouseup"+d,function(a){return c._isEventInsideIconpicker(a)||c.isInline()||c.hide(),a.stopPropagation(),a.preventDefault(),!1}),!1},_unbindElementEvents:function(){this.popover.off(".iconpicker"),this.element.off(".iconpicker"),this.hasInput()&&this.input.off(".iconpicker"),this.hasComponent()&&this.component.off(".iconpicker"),this.hasContainer()&&this.container.off(".iconpicker")},_unbindWindowEvents:function(){a(window).off(".iconpicker.inst"+this._id),a(window.document).off(".iconpicker.inst"+this._id)},updatePlacement:function(b,c){b=b||this.options.placement,this.options.placement=b,c=c||this.options.collision,c=!0===c?"flip":c;var d={at:"right bottom",my:"right top",of:this.hasInput()&&!this.isInputGroup()?this.input:this.container,collision:!0===c?"flip":c,within:window};if(this.popover.removeClass("inline topLeftCorner topLeft top topRight topRightCorner rightTop right rightBottom bottomRight bottomRightCorner bottom bottomLeft bottomLeftCorner leftBottom left leftTop"),"object"==typeof b)return this.popover.pos(a.extend({},d,b));switch(b){case"inline":d=!1;break;case"topLeftCorner":d.my="right bottom",d.at="left top";break;case"topLeft":d.my="left bottom",d.at="left top";break;case"top":d.my="center bottom",d.at="center top";break;case"topRight":d.my="right bottom",d.at="right top";break;case"topRightCorner":d.my="left bottom",d.at="right top";break;case"rightTop":d.my="left bottom",d.at="right center";break;case"right":d.my="left center",d.at="right center";break;case"rightBottom":d.my="left top",d.at="right center";break;case"bottomRightCorner":d.my="left top",d.at="right bottom";break;case"bottomRight":d.my="right top",d.at="right bottom";break;case"bottom":d.my="center top",d.at="center bottom";break;case"bottomLeft":d.my="left top",d.at="left bottom";break;case"bottomLeftCorner":d.my="right top",d.at="left bottom";break;case"leftBottom":d.my="right top",d.at="left center";break;case"left":d.my="right center",d.at="left center";break;case"leftTop":d.my="right bottom",d.at="left center";break;default:return!1}return this.popover.css({display:"inline"===this.options.placement?"":"block"}),!1!==d?this.popover.pos(d).css("maxWidth",a(window).width()-this.container.offset().left-5):this.popover.css({top:"auto",right:"auto",bottom:"auto",left:"auto",maxWidth:"none"}),this.popover.addClass(this.options.placement),!0},_updateComponents:function(){if(this.iconpicker.find(".iconpicker-item.iconpicker-selected").removeClass("iconpicker-selected "+this.options.selectedCustomClass),this.iconpickerValue&&this.iconpicker.find("."+this.options.fullClassFormatter(this.iconpickerValue).replace(/ /g,".")).parent().addClass("iconpicker-selected "+this.options.selectedCustomClass),this.hasComponent()){var a=this.component.find("i");a.length>0?a.attr("class",this.options.fullClassFormatter(this.iconpickerValue)):this.component.html(this.getHtml())}},_updateFormGroupStatus:function(a){return!!this.hasInput()&&(!1!==a?this.input.parents(".form-group:first").removeClass("has-error"):this.input.parents(".form-group:first").addClass("has-error"),!0)},getValid:function(c){b.isString(c)||(c="");var d=""===c;return c=a.trim(c),!(!b.inArray(c,this.options.icons)&&!d)&&c},setValue:function(a){var b=this.getValid(a);return!1!==b?(this.iconpickerValue=b,this._trigger("iconpickerSetValue",{iconpickerValue:b}),this.iconpickerValue):(this._trigger("iconpickerInvalid",{iconpickerValue:a}),!1)},getHtml:function(){return''},setSourceValue:function(a){return a=this.setValue(a),!1!==a&&""!==a&&(this.hasInput()?this.input.val(this.iconpickerValue):this.element.data("iconpickerValue",this.iconpickerValue),this._trigger("iconpickerSetSourceValue",{iconpickerValue:a})),a},getSourceValue:function(a){a=a||this.options.defaultValue;var b=a;return b=this.hasInput()?this.input.val():this.element.data("iconpickerValue"),void 0!==b&&""!==b&&null!==b&&!1!==b||(b=a),b},hasInput:function(){return!1!==this.input},isInputSearch:function(){return this.hasInput()&&!0===this.options.inputSearch},isInputGroup:function(){return this.container.is(".input-group")},isDropdownMenu:function(){return this.container.is(".dropdown-menu")},hasSeparatedSearchInput:function(){return!1!==this.options.templates.search&&!this.isInputSearch()},hasComponent:function(){return!1!==this.component},hasContainer:function(){return!1!==this.container},getAcceptButton:function(){return this.popover.find(".iconpicker-btn-accept")},getCancelButton:function(){return this.popover.find(".iconpicker-btn-cancel")},getSearchInput:function(){return this.popover.find(".iconpicker-search")},filter:function(c){if(b.isEmpty(c))return this.iconpicker.find(".iconpicker-item").show(),a(!1);var d=[];return this.iconpicker.find(".iconpicker-item").each(function(){var b=a(this),e=b.attr("title").toLowerCase(),f=!1;try{f=new RegExp(c,"g")}catch(a){f=!1}!1!==f&&e.match(f)?(d.push(b),b.show()):b.hide()}),d},show:function(){if(this.popover.hasClass("in"))return!1;a.iconpicker.batch(a(".iconpicker-popover.in:not(.inline)").not(this.popover),"hide"),this._trigger("iconpickerShow"),this.updatePlacement(),this.popover.addClass("in"),setTimeout(a.proxy(function(){this.popover.css("display",this.isInline()?"":"block"),this._trigger("iconpickerShown")},this),this.options.animation?300:1)},hide:function(){if(!this.popover.hasClass("in"))return!1;this._trigger("iconpickerHide"),this.popover.removeClass("in"),setTimeout(a.proxy(function(){this.popover.css("display","none"),this.getSearchInput().val(""),this.filter(""),this._trigger("iconpickerHidden")},this),this.options.animation?300:1)},toggle:function(){this.popover.is(":visible")?this.hide():this.show(!0)},update:function(a,b){return a=a||this.getSourceValue(this.iconpickerValue),this._trigger("iconpickerUpdate"),!0===b?a=this.setValue(a):(a=this.setSourceValue(a),this._updateFormGroupStatus(!1!==a)),!1!==a&&this._updateComponents(),this._trigger("iconpickerUpdated"),a},destroy:function(){this._trigger("iconpickerDestroy"),this.element.removeData("iconpicker").removeData("iconpickerValue").removeClass("iconpicker-element"),this._unbindElementEvents(),this._unbindWindowEvents(),a(this.popover).remove(),this._trigger("iconpickerDestroyed")},disable:function(){return!!this.hasInput()&&(this.input.prop("disabled",!0),!0)},enable:function(){return!!this.hasInput()&&(this.input.prop("disabled",!1),!0)},isDisabled:function(){return!!this.hasInput()&&!0===this.input.prop("disabled")},isInline:function(){return"inline"===this.options.placement||this.popover.hasClass("inline")}},a.iconpicker=c,a.fn.iconpicker=function(b){return this.each(function(){var d=a(this);d.data("iconpicker")||d.data("iconpicker",new c(this,"object"==typeof b?b:{}))})},c.defaultOptions.icons=["fa-500px","fa-address-book","fa-address-book-o","fa-address-card","fa-address-card-o","fa-adjust","fa-adn","fa-align-center","fa-align-justify","fa-align-left","fa-align-right","fa-amazon","fa-ambulance","fa-american-sign-language-interpreting","fa-anchor","fa-android","fa-angellist","fa-angle-double-down","fa-angle-double-left","fa-angle-double-right","fa-angle-double-up","fa-angle-down","fa-angle-left","fa-angle-right","fa-angle-up","fa-apple","fa-archive","fa-area-chart","fa-arrow-circle-down","fa-arrow-circle-left","fa-arrow-circle-o-down","fa-arrow-circle-o-left","fa-arrow-circle-o-right","fa-arrow-circle-o-up","fa-arrow-circle-right","fa-arrow-circle-up","fa-arrow-down","fa-arrow-left","fa-arrow-right","fa-arrow-up","fa-arrows","fa-arrows-alt","fa-arrows-h","fa-arrows-v","fa-asl-interpreting","fa-assistive-listening-systems","fa-asterisk","fa-at","fa-audio-description","fa-automobile","fa-backward","fa-balance-scale","fa-ban","fa-bandcamp","fa-bank","fa-bar-chart","fa-bar-chart-o","fa-barcode","fa-bars","fa-bath","fa-bathtub","fa-battery","fa-battery-0","fa-battery-1","fa-battery-2","fa-battery-3","fa-battery-4","fa-battery-empty","fa-battery-full","fa-battery-half","fa-battery-quarter","fa-battery-three-quarters","fa-bed","fa-beer","fa-behance","fa-behance-square","fa-bell","fa-bell-o","fa-bell-slash","fa-bell-slash-o","fa-bicycle","fa-binoculars","fa-birthday-cake","fa-bitbucket","fa-bitbucket-square","fa-bitcoin","fa-black-tie","fa-blind","fa-bluetooth","fa-bluetooth-b","fa-bold","fa-bolt","fa-bomb","fa-book","fa-bookmark","fa-bookmark-o","fa-braille","fa-briefcase","fa-btc","fa-bug","fa-building","fa-building-o","fa-bullhorn","fa-bullseye","fa-bus","fa-buysellads","fa-cab","fa-calculator","fa-calendar","fa-calendar-check-o","fa-calendar-minus-o","fa-calendar-o","fa-calendar-plus-o","fa-calendar-times-o","fa-camera","fa-camera-retro","fa-car","fa-caret-down","fa-caret-left","fa-caret-right","fa-caret-square-o-down","fa-caret-square-o-left","fa-caret-square-o-right","fa-caret-square-o-up","fa-caret-up","fa-cart-arrow-down","fa-cart-plus","fa-cc","fa-cc-amex","fa-cc-diners-club","fa-cc-discover","fa-cc-jcb","fa-cc-mastercard","fa-cc-paypal","fa-cc-stripe","fa-cc-visa","fa-certificate","fa-chain","fa-chain-broken","fa-check","fa-check-circle","fa-check-circle-o","fa-check-square","fa-check-square-o","fa-chevron-circle-down","fa-chevron-circle-left","fa-chevron-circle-right","fa-chevron-circle-up","fa-chevron-down","fa-chevron-left","fa-chevron-right","fa-chevron-up","fa-child","fa-chrome","fa-circle","fa-circle-o","fa-circle-o-notch","fa-circle-thin","fa-clipboard","fa-clock-o","fa-clone","fa-close","fa-cloud","fa-cloud-download","fa-cloud-upload","fa-cny","fa-code","fa-code-fork","fa-codepen","fa-codiepie","fa-coffee","fa-cog","fa-cogs","fa-columns","fa-comment","fa-comment-o","fa-commenting","fa-commenting-o","fa-comments","fa-comments-o","fa-compass","fa-compress","fa-connectdevelop","fa-contao","fa-copy","fa-copyright","fa-creative-commons","fa-credit-card","fa-credit-card-alt","fa-crop","fa-crosshairs","fa-css3","fa-cube","fa-cubes","fa-cut","fa-cutlery","fa-dashboard","fa-dashcube","fa-database","fa-deaf","fa-deafness","fa-dedent","fa-delicious","fa-desktop","fa-deviantart","fa-diamond","fa-digg","fa-dollar","fa-dot-circle-o","fa-download","fa-dribbble","fa-drivers-license","fa-drivers-license-o","fa-dropbox","fa-drupal","fa-edge","fa-edit","fa-eercast","fa-eject","fa-ellipsis-h","fa-ellipsis-v","fa-empire","fa-envelope","fa-envelope-o","fa-envelope-open","fa-envelope-open-o","fa-envelope-square","fa-envira","fa-eraser","fa-etsy","fa-eur","fa-euro","fa-exchange","fa-exclamation","fa-exclamation-circle","fa-exclamation-triangle","fa-expand","fa-expeditedssl","fa-external-link","fa-external-link-square","fa-eye","fa-eye-slash","fa-eyedropper","fa-fa","fa-facebook","fa-facebook-f","fa-facebook-official","fa-facebook-square","fa-fast-backward","fa-fast-forward","fa-fax","fa-feed","fa-female","fa-fighter-jet","fa-file","fa-file-archive-o","fa-file-audio-o","fa-file-code-o","fa-file-excel-o","fa-file-image-o","fa-file-movie-o","fa-file-o","fa-file-pdf-o","fa-file-photo-o","fa-file-picture-o","fa-file-powerpoint-o","fa-file-sound-o","fa-file-text","fa-file-text-o","fa-file-video-o","fa-file-word-o","fa-file-zip-o","fa-files-o","fa-film","fa-filter","fa-fire","fa-fire-extinguisher","fa-firefox","fa-first-order","fa-flag","fa-flag-checkered","fa-flag-o","fa-flash","fa-flask","fa-flickr","fa-floppy-o","fa-folder","fa-folder-o","fa-folder-open","fa-folder-open-o","fa-font","fa-font-awesome","fa-fonticons","fa-fort-awesome","fa-forumbee","fa-forward","fa-foursquare","fa-free-code-camp","fa-frown-o","fa-futbol-o","fa-gamepad","fa-gavel","fa-gbp","fa-ge","fa-gear","fa-gears","fa-genderless","fa-get-pocket","fa-gg","fa-gg-circle","fa-gift","fa-git","fa-git-square","fa-github","fa-github-alt","fa-github-square","fa-gitlab","fa-gittip","fa-glass","fa-glide","fa-glide-g","fa-globe","fa-google","fa-google-plus","fa-google-plus-circle","fa-google-plus-official","fa-google-plus-square","fa-google-wallet","fa-graduation-cap","fa-gratipay","fa-grav","fa-group","fa-h-square","fa-hacker-news","fa-hand-grab-o","fa-hand-lizard-o","fa-hand-o-down","fa-hand-o-left","fa-hand-o-right","fa-hand-o-up","fa-hand-paper-o","fa-hand-peace-o","fa-hand-pointer-o","fa-hand-rock-o","fa-hand-scissors-o","fa-hand-spock-o","fa-hand-stop-o","fa-handshake-o","fa-hard-of-hearing","fa-hashtag","fa-hdd-o","fa-header","fa-headphones","fa-heart","fa-heart-o","fa-heartbeat","fa-history","fa-home","fa-hospital-o","fa-hotel","fa-hourglass","fa-hourglass-1","fa-hourglass-2","fa-hourglass-3","fa-hourglass-end","fa-hourglass-half","fa-hourglass-o","fa-hourglass-start","fa-houzz","fa-html5","fa-i-cursor","fa-id-badge","fa-id-card","fa-id-card-o","fa-ils","fa-image","fa-imdb","fa-inbox","fa-indent","fa-industry","fa-info","fa-info-circle","fa-inr","fa-instagram","fa-institution","fa-internet-explorer","fa-intersex","fa-ioxhost","fa-italic","fa-joomla","fa-jpy","fa-jsfiddle","fa-key","fa-keyboard-o","fa-krw","fa-language","fa-laptop","fa-lastfm","fa-lastfm-square","fa-leaf","fa-leanpub","fa-legal","fa-lemon-o","fa-level-down","fa-level-up","fa-life-bouy","fa-life-buoy","fa-life-ring","fa-life-saver","fa-lightbulb-o","fa-line-chart","fa-link","fa-linkedin","fa-linkedin-square","fa-linode","fa-linux","fa-list","fa-list-alt","fa-list-ol","fa-list-ul","fa-location-arrow","fa-lock","fa-long-arrow-down","fa-long-arrow-left","fa-long-arrow-right","fa-long-arrow-up","fa-low-vision","fa-magic","fa-magnet","fa-mail-forward","fa-mail-reply","fa-mail-reply-all","fa-male","fa-map","fa-map-marker","fa-map-o","fa-map-pin","fa-map-signs","fa-mars","fa-mars-double","fa-mars-stroke","fa-mars-stroke-h","fa-mars-stroke-v","fa-maxcdn","fa-meanpath","fa-medium","fa-medkit","fa-meetup","fa-meh-o","fa-mercury","fa-microchip","fa-microphone","fa-microphone-slash","fa-minus","fa-minus-circle","fa-minus-square","fa-minus-square-o","fa-mixcloud","fa-mobile","fa-mobile-phone","fa-modx","fa-money","fa-moon-o","fa-mortar-board","fa-motorcycle","fa-mouse-pointer","fa-music","fa-navicon","fa-neuter","fa-newspaper-o","fa-object-group","fa-object-ungroup","fa-odnoklassniki","fa-odnoklassniki-square","fa-opencart","fa-openid","fa-opera","fa-optin-monster","fa-outdent","fa-pagelines","fa-paint-brush","fa-paper-plane","fa-paper-plane-o","fa-paperclip","fa-paragraph","fa-paste","fa-pause","fa-pause-circle","fa-pause-circle-o","fa-paw","fa-paypal","fa-pencil","fa-pencil-square","fa-pencil-square-o","fa-percent","fa-phone","fa-phone-square","fa-photo","fa-picture-o","fa-pie-chart","fa-pied-piper","fa-pied-piper-alt","fa-pied-piper-pp","fa-pinterest","fa-pinterest-p","fa-pinterest-square","fa-plane","fa-play","fa-play-circle","fa-play-circle-o","fa-plug","fa-plus","fa-plus-circle","fa-plus-square","fa-plus-square-o","fa-podcast","fa-power-off","fa-print","fa-product-hunt","fa-puzzle-piece","fa-qq","fa-qrcode","fa-question","fa-question-circle","fa-question-circle-o","fa-quora","fa-quote-left","fa-quote-right","fa-ra","fa-random","fa-ravelry","fa-rebel","fa-recycle","fa-reddit","fa-reddit-alien","fa-reddit-square","fa-refresh","fa-registered","fa-remove","fa-renren","fa-reorder","fa-repeat","fa-reply","fa-reply-all","fa-resistance","fa-retweet","fa-rmb","fa-road","fa-rocket","fa-rotate-left","fa-rotate-right","fa-rouble","fa-rss","fa-rss-square","fa-rub","fa-ruble","fa-rupee","fa-s15","fa-safari","fa-save","fa-scissors","fa-scribd","fa-search","fa-search-minus","fa-search-plus","fa-sellsy","fa-send","fa-send-o","fa-server","fa-share","fa-share-alt","fa-share-alt-square","fa-share-square","fa-share-square-o","fa-shekel","fa-sheqel","fa-shield","fa-ship","fa-shirtsinbulk","fa-shopping-bag","fa-shopping-basket","fa-shopping-cart","fa-shower","fa-sign-in","fa-sign-language","fa-sign-out","fa-signal","fa-signing","fa-simplybuilt","fa-sitemap","fa-skyatlas","fa-skype","fa-slack","fa-sliders","fa-slideshare","fa-smile-o","fa-snapchat","fa-snapchat-ghost","fa-snapchat-square","fa-snowflake-o","fa-soccer-ball-o","fa-sort","fa-sort-alpha-asc","fa-sort-alpha-desc","fa-sort-amount-asc","fa-sort-amount-desc","fa-sort-asc","fa-sort-desc","fa-sort-down","fa-sort-numeric-asc","fa-sort-numeric-desc","fa-sort-up","fa-soundcloud","fa-space-shuttle","fa-spinner","fa-spoon","fa-spotify","fa-square","fa-square-o","fa-stack-exchange","fa-stack-overflow","fa-star","fa-star-half","fa-star-half-empty","fa-star-half-full","fa-star-half-o","fa-star-o","fa-steam","fa-steam-square","fa-step-backward","fa-step-forward","fa-stethoscope","fa-sticky-note","fa-sticky-note-o","fa-stop","fa-stop-circle","fa-stop-circle-o","fa-street-view","fa-strikethrough","fa-stumbleupon","fa-stumbleupon-circle","fa-subscript","fa-subway","fa-suitcase","fa-sun-o","fa-superpowers","fa-superscript","fa-support","fa-table","fa-tablet","fa-tachometer","fa-tag","fa-tags","fa-tasks","fa-taxi","fa-telegram","fa-television","fa-tencent-weibo","fa-terminal","fa-text-height","fa-text-width","fa-th","fa-th-large","fa-th-list","fa-themeisle","fa-thermometer","fa-thermometer-0","fa-thermometer-1","fa-thermometer-2","fa-thermometer-3","fa-thermometer-4","fa-thermometer-empty","fa-thermometer-full","fa-thermometer-half","fa-thermometer-quarter","fa-thermometer-three-quarters","fa-thumb-tack","fa-thumbs-down","fa-thumbs-o-down","fa-thumbs-o-up","fa-thumbs-up","fa-ticket","fa-times","fa-times-circle","fa-times-circle-o","fa-times-rectangle","fa-times-rectangle-o","fa-tint","fa-toggle-down","fa-toggle-left","fa-toggle-off","fa-toggle-on","fa-toggle-right","fa-toggle-up","fa-trademark","fa-train","fa-transgender","fa-transgender-alt","fa-trash","fa-trash-o","fa-tree","fa-trello","fa-tripadvisor","fa-trophy","fa-truck","fa-try","fa-tty","fa-tumblr","fa-tumblr-square","fa-turkish-lira","fa-tv","fa-twitch","fa-twitter","fa-twitter-square","fa-umbrella","fa-underline","fa-undo","fa-universal-access","fa-university","fa-unlink","fa-unlock","fa-unlock-alt","fa-unsorted","fa-upload","fa-usb","fa-usd","fa-user","fa-user-circle","fa-user-circle-o","fa-user-md","fa-user-o","fa-user-plus","fa-user-secret","fa-user-times","fa-users","fa-vcard","fa-vcard-o","fa-venus","fa-venus-double","fa-venus-mars","fa-viacoin","fa-viadeo","fa-viadeo-square","fa-video-camera","fa-vimeo","fa-vimeo-square","fa-vine","fa-vk","fa-volume-control-phone","fa-volume-down","fa-volume-off","fa-volume-up","fa-warning","fa-wechat","fa-weibo","fa-weixin","fa-whatsapp","fa-wheelchair","fa-wheelchair-alt","fa-wifi","fa-wikipedia-w","fa-window-close","fa-window-close-o","fa-window-maximize","fa-window-minimize","fa-window-restore","fa-windows","fa-won","fa-wordpress","fa-wpbeginner","fa-wpexplorer","fa-wpforms","fa-wrench","fa-xing","fa-xing-square","fa-y-combinator","fa-y-combinator-square","fa-yahoo","fa-yc","fa-yc-square","fa-yelp","fa-yen","fa-yoast","fa-youtube","fa-youtube-play","fa-youtube-square"]}); \ No newline at end of file diff --git a/ext/phpbbstudio/aps/adm/style/js/jquery-ui-sortable.min.js b/ext/phpbbstudio/aps/adm/style/js/jquery-ui-sortable.min.js new file mode 100644 index 0000000..d434204 --- /dev/null +++ b/ext/phpbbstudio/aps/adm/style/js/jquery-ui-sortable.min.js @@ -0,0 +1,7 @@ +/*! jQuery UI - v1.12.1 - 2019-01-17 +* http://jqueryui.com +* Includes: widget.js, data.js, scroll-parent.js, widgets/sortable.js, widgets/mouse.js +* Copyright jQuery Foundation and other contributors; Licensed MIT */ + +(function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)})(function(t){t.ui=t.ui||{},t.ui.version="1.12.1";var e=0,i=Array.prototype.slice;t.cleanData=function(e){return function(i){var s,n,o;for(o=0;null!=(n=i[o]);o++)try{s=t._data(n,"events"),s&&s.remove&&t(n).triggerHandler("remove")}catch(a){}e(i)}}(t.cleanData),t.widget=function(e,i,s){var n,o,a,r={},l=e.split(".")[0];e=e.split(".")[1];var h=l+"-"+e;return s||(s=i,i=t.Widget),t.isArray(s)&&(s=t.extend.apply(null,[{}].concat(s))),t.expr[":"][h.toLowerCase()]=function(e){return!!t.data(e,h)},t[l]=t[l]||{},n=t[l][e],o=t[l][e]=function(t,e){return this._createWidget?(arguments.length&&this._createWidget(t,e),void 0):new o(t,e)},t.extend(o,n,{version:s.version,_proto:t.extend({},s),_childConstructors:[]}),a=new i,a.options=t.widget.extend({},a.options),t.each(s,function(e,s){return t.isFunction(s)?(r[e]=function(){function t(){return i.prototype[e].apply(this,arguments)}function n(t){return i.prototype[e].apply(this,t)}return function(){var e,i=this._super,o=this._superApply;return this._super=t,this._superApply=n,e=s.apply(this,arguments),this._super=i,this._superApply=o,e}}(),void 0):(r[e]=s,void 0)}),o.prototype=t.widget.extend(a,{widgetEventPrefix:n?a.widgetEventPrefix||e:e},r,{constructor:o,namespace:l,widgetName:e,widgetFullName:h}),n?(t.each(n._childConstructors,function(e,i){var s=i.prototype;t.widget(s.namespace+"."+s.widgetName,o,i._proto)}),delete n._childConstructors):i._childConstructors.push(o),t.widget.bridge(e,o),o},t.widget.extend=function(e){for(var s,n,o=i.call(arguments,1),a=0,r=o.length;r>a;a++)for(s in o[a])n=o[a][s],o[a].hasOwnProperty(s)&&void 0!==n&&(e[s]=t.isPlainObject(n)?t.isPlainObject(e[s])?t.widget.extend({},e[s],n):t.widget.extend({},n):n);return e},t.widget.bridge=function(e,s){var n=s.prototype.widgetFullName||e;t.fn[e]=function(o){var a="string"==typeof o,r=i.call(arguments,1),l=this;return a?this.length||"instance"!==o?this.each(function(){var i,s=t.data(this,n);return"instance"===o?(l=s,!1):s?t.isFunction(s[o])&&"_"!==o.charAt(0)?(i=s[o].apply(s,r),i!==s&&void 0!==i?(l=i&&i.jquery?l.pushStack(i.get()):i,!1):void 0):t.error("no such method '"+o+"' for "+e+" widget instance"):t.error("cannot call methods on "+e+" prior to initialization; "+"attempted to call method '"+o+"'")}):l=void 0:(r.length&&(o=t.widget.extend.apply(null,[o].concat(r))),this.each(function(){var e=t.data(this,n);e?(e.option(o||{}),e._init&&e._init()):t.data(this,n,new s(o,this))})),l}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"
",options:{classes:{},disabled:!1,create:null},_createWidget:function(i,s){s=t(s||this.defaultElement||this)[0],this.element=t(s),this.uuid=e++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=t(),this.hoverable=t(),this.focusable=t(),this.classesElementLookup={},s!==this&&(t.data(s,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===s&&this.destroy()}}),this.document=t(s.style?s.ownerDocument:s.document||s),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this.options=t.widget.extend({},this.options,this._getCreateOptions(),i),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){var e=this;this._destroy(),t.each(this.classesElementLookup,function(t,i){e._removeClass(i,t)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:t.noop,widget:function(){return this.element},option:function(e,i){var s,n,o,a=e;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof e)if(a={},s=e.split("."),e=s.shift(),s.length){for(n=a[e]=t.widget.extend({},this.options[e]),o=0;s.length-1>o;o++)n[s[o]]=n[s[o]]||{},n=n[s[o]];if(e=s.pop(),1===arguments.length)return void 0===n[e]?null:n[e];n[e]=i}else{if(1===arguments.length)return void 0===this.options[e]?null:this.options[e];a[e]=i}return this._setOptions(a),this},_setOptions:function(t){var e;for(e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return"classes"===t&&this._setOptionClasses(e),this.options[t]=e,"disabled"===t&&this._setOptionDisabled(e),this},_setOptionClasses:function(e){var i,s,n;for(i in e)n=this.classesElementLookup[i],e[i]!==this.options.classes[i]&&n&&n.length&&(s=t(n.get()),this._removeClass(n,i),s.addClass(this._classes({element:s,keys:i,classes:e,add:!0})))},_setOptionDisabled:function(t){this._toggleClass(this.widget(),this.widgetFullName+"-disabled",null,!!t),t&&(this._removeClass(this.hoverable,null,"ui-state-hover"),this._removeClass(this.focusable,null,"ui-state-focus"))},enable:function(){return this._setOptions({disabled:!1})},disable:function(){return this._setOptions({disabled:!0})},_classes:function(e){function i(i,o){var a,r;for(r=0;i.length>r;r++)a=n.classesElementLookup[i[r]]||t(),a=e.add?t(t.unique(a.get().concat(e.element.get()))):t(a.not(e.element).get()),n.classesElementLookup[i[r]]=a,s.push(i[r]),o&&e.classes[i[r]]&&s.push(e.classes[i[r]])}var s=[],n=this;return e=t.extend({element:this.element,classes:this.options.classes||{}},e),this._on(e.element,{remove:"_untrackClassesElement"}),e.keys&&i(e.keys.match(/\S+/g)||[],!0),e.extra&&i(e.extra.match(/\S+/g)||[]),s.join(" ")},_untrackClassesElement:function(e){var i=this;t.each(i.classesElementLookup,function(s,n){-1!==t.inArray(e.target,n)&&(i.classesElementLookup[s]=t(n.not(e.target).get()))})},_removeClass:function(t,e,i){return this._toggleClass(t,e,i,!1)},_addClass:function(t,e,i){return this._toggleClass(t,e,i,!0)},_toggleClass:function(t,e,i,s){s="boolean"==typeof s?s:i;var n="string"==typeof t||null===t,o={extra:n?e:i,keys:n?t:e,element:n?this.element:t,add:s};return o.element.toggleClass(this._classes(o),s),this},_on:function(e,i,s){var n,o=this;"boolean"!=typeof e&&(s=i,i=e,e=!1),s?(i=n=t(i),this.bindings=this.bindings.add(i)):(s=i,i=this.element,n=this.widget()),t.each(s,function(s,a){function r(){return e||o.options.disabled!==!0&&!t(this).hasClass("ui-state-disabled")?("string"==typeof a?o[a]:a).apply(o,arguments):void 0}"string"!=typeof a&&(r.guid=a.guid=a.guid||r.guid||t.guid++);var l=s.match(/^([\w:-]*)\s*(.*)$/),h=l[1]+o.eventNamespace,c=l[2];c?n.on(h,c,r):i.on(h,r)})},_off:function(e,i){i=(i||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,e.off(i).off(i),this.bindings=t(this.bindings.not(e).get()),this.focusable=t(this.focusable.not(e).get()),this.hoverable=t(this.hoverable.not(e).get())},_delay:function(t,e){function i(){return("string"==typeof t?s[t]:t).apply(s,arguments)}var s=this;return setTimeout(i,e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){this._addClass(t(e.currentTarget),null,"ui-state-hover")},mouseleave:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-hover")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){this._addClass(t(e.currentTarget),null,"ui-state-focus")},focusout:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-focus")}})},_trigger:function(e,i,s){var n,o,a=this.options[e];if(s=s||{},i=t.Event(i),i.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],o=i.originalEvent)for(n in o)n in i||(i[n]=o[n]);return this.element.trigger(i,s),!(t.isFunction(a)&&a.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},t.each({show:"fadeIn",hide:"fadeOut"},function(e,i){t.Widget.prototype["_"+e]=function(s,n,o){"string"==typeof n&&(n={effect:n});var a,r=n?n===!0||"number"==typeof n?i:n.effect||i:e;n=n||{},"number"==typeof n&&(n={duration:n}),a=!t.isEmptyObject(n),n.complete=o,n.delay&&s.delay(n.delay),a&&t.effects&&t.effects.effect[r]?s[e](n):r!==e&&s[r]?s[r](n.duration,n.easing,o):s.queue(function(i){t(this)[e](),o&&o.call(s[0]),i()})}}),t.widget,t.extend(t.expr[":"],{data:t.expr.createPseudo?t.expr.createPseudo(function(e){return function(i){return!!t.data(i,e)}}):function(e,i,s){return!!t.data(e,s[3])}}),t.fn.scrollParent=function(e){var i=this.css("position"),s="absolute"===i,n=e?/(auto|scroll|hidden)/:/(auto|scroll)/,o=this.parents().filter(function(){var e=t(this);return s&&"static"===e.css("position")?!1:n.test(e.css("overflow")+e.css("overflow-y")+e.css("overflow-x"))}).eq(0);return"fixed"!==i&&o.length?o:t(this[0].ownerDocument||document)},t.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase());var s=!1;t(document).on("mouseup",function(){s=!1}),t.widget("ui.mouse",{version:"1.12.1",options:{cancel:"input, textarea, button, select, option",distance:1,delay:0},_mouseInit:function(){var e=this;this.element.on("mousedown."+this.widgetName,function(t){return e._mouseDown(t)}).on("click."+this.widgetName,function(i){return!0===t.data(i.target,e.widgetName+".preventClickEvent")?(t.removeData(i.target,e.widgetName+".preventClickEvent"),i.stopImmediatePropagation(),!1):void 0}),this.started=!1},_mouseDestroy:function(){this.element.off("."+this.widgetName),this._mouseMoveDelegate&&this.document.off("mousemove."+this.widgetName,this._mouseMoveDelegate).off("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(e){if(!s){this._mouseMoved=!1,this._mouseStarted&&this._mouseUp(e),this._mouseDownEvent=e;var i=this,n=1===e.which,o="string"==typeof this.options.cancel&&e.target.nodeName?t(e.target).closest(this.options.cancel).length:!1;return n&&!o&&this._mouseCapture(e)?(this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){i.mouseDelayMet=!0},this.options.delay)),this._mouseDistanceMet(e)&&this._mouseDelayMet(e)&&(this._mouseStarted=this._mouseStart(e)!==!1,!this._mouseStarted)?(e.preventDefault(),!0):(!0===t.data(e.target,this.widgetName+".preventClickEvent")&&t.removeData(e.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(t){return i._mouseMove(t)},this._mouseUpDelegate=function(t){return i._mouseUp(t)},this.document.on("mousemove."+this.widgetName,this._mouseMoveDelegate).on("mouseup."+this.widgetName,this._mouseUpDelegate),e.preventDefault(),s=!0,!0)):!0}},_mouseMove:function(e){if(this._mouseMoved){if(t.ui.ie&&(!document.documentMode||9>document.documentMode)&&!e.button)return this._mouseUp(e);if(!e.which)if(e.originalEvent.altKey||e.originalEvent.ctrlKey||e.originalEvent.metaKey||e.originalEvent.shiftKey)this.ignoreMissingWhich=!0;else if(!this.ignoreMissingWhich)return this._mouseUp(e)}return(e.which||e.button)&&(this._mouseMoved=!0),this._mouseStarted?(this._mouseDrag(e),e.preventDefault()):(this._mouseDistanceMet(e)&&this._mouseDelayMet(e)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,e)!==!1,this._mouseStarted?this._mouseDrag(e):this._mouseUp(e)),!this._mouseStarted)},_mouseUp:function(e){this.document.off("mousemove."+this.widgetName,this._mouseMoveDelegate).off("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,e.target===this._mouseDownEvent.target&&t.data(e.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(e)),this._mouseDelayTimer&&(clearTimeout(this._mouseDelayTimer),delete this._mouseDelayTimer),this.ignoreMissingWhich=!1,s=!1,e.preventDefault()},_mouseDistanceMet:function(t){return Math.max(Math.abs(this._mouseDownEvent.pageX-t.pageX),Math.abs(this._mouseDownEvent.pageY-t.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}}),t.widget("ui.sortable",t.ui.mouse,{version:"1.12.1",widgetEventPrefix:"sort",ready:!1,options:{appendTo:"parent",axis:!1,connectWith:!1,containment:!1,cursor:"auto",cursorAt:!1,dropOnEmpty:!0,forcePlaceholderSize:!1,forceHelperSize:!1,grid:!1,handle:!1,helper:"original",items:"> *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3,activate:null,beforeStop:null,change:null,deactivate:null,out:null,over:null,receive:null,remove:null,sort:null,start:null,stop:null,update:null},_isOverAxis:function(t,e,i){return t>=e&&e+i>t},_isFloating:function(t){return/left|right/.test(t.css("float"))||/inline|table-cell/.test(t.css("display"))},_create:function(){this.containerCache={},this._addClass("ui-sortable"),this.refresh(),this.offset=this.element.offset(),this._mouseInit(),this._setHandleClassName(),this.ready=!0},_setOption:function(t,e){this._super(t,e),"handle"===t&&this._setHandleClassName()},_setHandleClassName:function(){var e=this;this._removeClass(this.element.find(".ui-sortable-handle"),"ui-sortable-handle"),t.each(this.items,function(){e._addClass(this.instance.options.handle?this.item.find(this.instance.options.handle):this.item,"ui-sortable-handle")})},_destroy:function(){this._mouseDestroy();for(var t=this.items.length-1;t>=0;t--)this.items[t].item.removeData(this.widgetName+"-item");return this},_mouseCapture:function(e,i){var s=null,n=!1,o=this;return this.reverting?!1:this.options.disabled||"static"===this.options.type?!1:(this._refreshItems(e),t(e.target).parents().each(function(){return t.data(this,o.widgetName+"-item")===o?(s=t(this),!1):void 0}),t.data(e.target,o.widgetName+"-item")===o&&(s=t(e.target)),s?!this.options.handle||i||(t(this.options.handle,s).find("*").addBack().each(function(){this===e.target&&(n=!0)}),n)?(this.currentItem=s,this._removeCurrentsFromItems(),!0):!1:!1)},_mouseStart:function(e,i,s){var n,o,a=this.options;if(this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(e),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},t.extend(this.offset,{click:{left:e.pageX-this.offset.left,top:e.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.helper.css("position","absolute"),this.cssPosition=this.helper.css("position"),this.originalPosition=this._generatePosition(e),this.originalPageX=e.pageX,this.originalPageY=e.pageY,a.cursorAt&&this._adjustOffsetFromHelper(a.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!==this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),a.containment&&this._setContainment(),a.cursor&&"auto"!==a.cursor&&(o=this.document.find("body"),this.storedCursor=o.css("cursor"),o.css("cursor",a.cursor),this.storedStylesheet=t("").appendTo(o)),a.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",a.opacity)),a.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",a.zIndex)),this.scrollParent[0]!==this.document[0]&&"HTML"!==this.scrollParent[0].tagName&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",e,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions(),!s)for(n=this.containers.length-1;n>=0;n--)this.containers[n]._trigger("activate",e,this._uiHash(this));return t.ui.ddmanager&&(t.ui.ddmanager.current=this),t.ui.ddmanager&&!a.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e),this.dragging=!0,this._addClass(this.helper,"ui-sortable-helper"),this._mouseDrag(e),!0},_mouseDrag:function(e){var i,s,n,o,a=this.options,r=!1;for(this.position=this._generatePosition(e),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs),this.options.scroll&&(this.scrollParent[0]!==this.document[0]&&"HTML"!==this.scrollParent[0].tagName?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-e.pageY=0;i--)if(s=this.items[i],n=s.item[0],o=this._intersectsWithPointer(s),o&&s.instance===this.currentContainer&&n!==this.currentItem[0]&&this.placeholder[1===o?"next":"prev"]()[0]!==n&&!t.contains(this.placeholder[0],n)&&("semi-dynamic"===this.options.type?!t.contains(this.element[0],n):!0)){if(this.direction=1===o?"down":"up","pointer"!==this.options.tolerance&&!this._intersectsWithSides(s))break;this._rearrange(e,s),this._trigger("change",e,this._uiHash());break}return this._contactContainers(e),t.ui.ddmanager&&t.ui.ddmanager.drag(this,e),this._trigger("sort",e,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(e,i){if(e){if(t.ui.ddmanager&&!this.options.dropBehaviour&&t.ui.ddmanager.drop(this,e),this.options.revert){var s=this,n=this.placeholder.offset(),o=this.options.axis,a={};o&&"x"!==o||(a.left=n.left-this.offset.parent.left-this.margins.left+(this.offsetParent[0]===this.document[0].body?0:this.offsetParent[0].scrollLeft)),o&&"y"!==o||(a.top=n.top-this.offset.parent.top-this.margins.top+(this.offsetParent[0]===this.document[0].body?0:this.offsetParent[0].scrollTop)),this.reverting=!0,t(this.helper).animate(a,parseInt(this.options.revert,10)||500,function(){s._clear(e)})}else this._clear(e,i);return!1}},cancel:function(){if(this.dragging){this._mouseUp(new t.Event("mouseup",{target:null})),"original"===this.options.helper?(this.currentItem.css(this._storedCSS),this._removeClass(this.currentItem,"ui-sortable-helper")):this.currentItem.show();for(var e=this.containers.length-1;e>=0;e--)this.containers[e]._trigger("deactivate",null,this._uiHash(this)),this.containers[e].containerCache.over&&(this.containers[e]._trigger("out",null,this._uiHash(this)),this.containers[e].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),"original"!==this.options.helper&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),t.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?t(this.domPosition.prev).after(this.currentItem):t(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},t(i).each(function(){var i=(t(e.item||this).attr(e.attribute||"id")||"").match(e.expression||/(.+)[\-=_](.+)/);i&&s.push((e.key||i[1]+"[]")+"="+(e.key&&e.expression?i[1]:i[2]))}),!s.length&&e.key&&s.push(e.key+"="),s.join("&")},toArray:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},i.each(function(){s.push(t(e.item||this).attr(e.attribute||"id")||"")}),s},_intersectsWith:function(t){var e=this.positionAbs.left,i=e+this.helperProportions.width,s=this.positionAbs.top,n=s+this.helperProportions.height,o=t.left,a=o+t.width,r=t.top,l=r+t.height,h=this.offset.click.top,c=this.offset.click.left,u="x"===this.options.axis||s+h>r&&l>s+h,d="y"===this.options.axis||e+c>o&&a>e+c,p=u&&d;return"pointer"===this.options.tolerance||this.options.forcePointerForContainers||"pointer"!==this.options.tolerance&&this.helperProportions[this.floating?"width":"height"]>t[this.floating?"width":"height"]?p:e+this.helperProportions.width/2>o&&a>i-this.helperProportions.width/2&&s+this.helperProportions.height/2>r&&l>n-this.helperProportions.height/2},_intersectsWithPointer:function(t){var e,i,s="x"===this.options.axis||this._isOverAxis(this.positionAbs.top+this.offset.click.top,t.top,t.height),n="y"===this.options.axis||this._isOverAxis(this.positionAbs.left+this.offset.click.left,t.left,t.width),o=s&&n;return o?(e=this._getDragVerticalDirection(),i=this._getDragHorizontalDirection(),this.floating?"right"===i||"down"===e?2:1:e&&("down"===e?2:1)):!1},_intersectsWithSides:function(t){var e=this._isOverAxis(this.positionAbs.top+this.offset.click.top,t.top+t.height/2,t.height),i=this._isOverAxis(this.positionAbs.left+this.offset.click.left,t.left+t.width/2,t.width),s=this._getDragVerticalDirection(),n=this._getDragHorizontalDirection();return this.floating&&n?"right"===n&&i||"left"===n&&!i:s&&("down"===s&&e||"up"===s&&!e)},_getDragVerticalDirection:function(){var t=this.positionAbs.top-this.lastPositionAbs.top;return 0!==t&&(t>0?"down":"up")},_getDragHorizontalDirection:function(){var t=this.positionAbs.left-this.lastPositionAbs.left;return 0!==t&&(t>0?"right":"left")},refresh:function(t){return this._refreshItems(t),this._setHandleClassName(),this.refreshPositions(),this},_connectWith:function(){var t=this.options;return t.connectWith.constructor===String?[t.connectWith]:t.connectWith},_getItemsAsjQuery:function(e){function i(){r.push(this)}var s,n,o,a,r=[],l=[],h=this._connectWith();if(h&&e)for(s=h.length-1;s>=0;s--)for(o=t(h[s],this.document[0]),n=o.length-1;n>=0;n--)a=t.data(o[n],this.widgetFullName),a&&a!==this&&!a.options.disabled&&l.push([t.isFunction(a.options.items)?a.options.items.call(a.element):t(a.options.items,a.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),a]);for(l.push([t.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):t(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]),s=l.length-1;s>=0;s--)l[s][0].each(i);return t(r)},_removeCurrentsFromItems:function(){var e=this.currentItem.find(":data("+this.widgetName+"-item)");this.items=t.grep(this.items,function(t){for(var i=0;e.length>i;i++)if(e[i]===t.item[0])return!1;return!0})},_refreshItems:function(e){this.items=[],this.containers=[this];var i,s,n,o,a,r,l,h,c=this.items,u=[[t.isFunction(this.options.items)?this.options.items.call(this.element[0],e,{item:this.currentItem}):t(this.options.items,this.element),this]],d=this._connectWith();if(d&&this.ready)for(i=d.length-1;i>=0;i--)for(n=t(d[i],this.document[0]),s=n.length-1;s>=0;s--)o=t.data(n[s],this.widgetFullName),o&&o!==this&&!o.options.disabled&&(u.push([t.isFunction(o.options.items)?o.options.items.call(o.element[0],e,{item:this.currentItem}):t(o.options.items,o.element),o]),this.containers.push(o));for(i=u.length-1;i>=0;i--)for(a=u[i][1],r=u[i][0],s=0,h=r.length;h>s;s++)l=t(r[s]),l.data(this.widgetName+"-item",a),c.push({item:l,instance:a,width:0,height:0,left:0,top:0})},refreshPositions:function(e){this.floating=this.items.length?"x"===this.options.axis||this._isFloating(this.items[0].item):!1,this.offsetParent&&this.helper&&(this.offset.parent=this._getParentOffset());var i,s,n,o;for(i=this.items.length-1;i>=0;i--)s=this.items[i],s.instance!==this.currentContainer&&this.currentContainer&&s.item[0]!==this.currentItem[0]||(n=this.options.toleranceElement?t(this.options.toleranceElement,s.item):s.item,e||(s.width=n.outerWidth(),s.height=n.outerHeight()),o=n.offset(),s.left=o.left,s.top=o.top);if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(i=this.containers.length-1;i>=0;i--)o=this.containers[i].element.offset(),this.containers[i].containerCache.left=o.left,this.containers[i].containerCache.top=o.top,this.containers[i].containerCache.width=this.containers[i].element.outerWidth(),this.containers[i].containerCache.height=this.containers[i].element.outerHeight();return this},_createPlaceholder:function(e){e=e||this;var i,s=e.options;s.placeholder&&s.placeholder.constructor!==String||(i=s.placeholder,s.placeholder={element:function(){var s=e.currentItem[0].nodeName.toLowerCase(),n=t("<"+s+">",e.document[0]);return e._addClass(n,"ui-sortable-placeholder",i||e.currentItem[0].className)._removeClass(n,"ui-sortable-helper"),"tbody"===s?e._createTrPlaceholder(e.currentItem.find("tr").eq(0),t("",e.document[0]).appendTo(n)):"tr"===s?e._createTrPlaceholder(e.currentItem,n):"img"===s&&n.attr("src",e.currentItem.attr("src")),i||n.css("visibility","hidden"),n},update:function(t,n){(!i||s.forcePlaceholderSize)&&(n.height()||n.height(e.currentItem.innerHeight()-parseInt(e.currentItem.css("paddingTop")||0,10)-parseInt(e.currentItem.css("paddingBottom")||0,10)),n.width()||n.width(e.currentItem.innerWidth()-parseInt(e.currentItem.css("paddingLeft")||0,10)-parseInt(e.currentItem.css("paddingRight")||0,10)))}}),e.placeholder=t(s.placeholder.element.call(e.element,e.currentItem)),e.currentItem.after(e.placeholder),s.placeholder.update(e,e.placeholder)},_createTrPlaceholder:function(e,i){var s=this;e.children().each(function(){t(" ",s.document[0]).attr("colspan",t(this).attr("colspan")||1).appendTo(i)})},_contactContainers:function(e){var i,s,n,o,a,r,l,h,c,u,d=null,p=null;for(i=this.containers.length-1;i>=0;i--)if(!t.contains(this.currentItem[0],this.containers[i].element[0]))if(this._intersectsWith(this.containers[i].containerCache)){if(d&&t.contains(this.containers[i].element[0],d.element[0]))continue;d=this.containers[i],p=i}else this.containers[i].containerCache.over&&(this.containers[i]._trigger("out",e,this._uiHash(this)),this.containers[i].containerCache.over=0);if(d)if(1===this.containers.length)this.containers[p].containerCache.over||(this.containers[p]._trigger("over",e,this._uiHash(this)),this.containers[p].containerCache.over=1);else{for(n=1e4,o=null,c=d.floating||this._isFloating(this.currentItem),a=c?"left":"top",r=c?"width":"height",u=c?"pageX":"pageY",s=this.items.length-1;s>=0;s--)t.contains(this.containers[p].element[0],this.items[s].item[0])&&this.items[s].item[0]!==this.currentItem[0]&&(l=this.items[s].item.offset()[a],h=!1,e[u]-l>this.items[s][r]/2&&(h=!0),n>Math.abs(e[u]-l)&&(n=Math.abs(e[u]-l),o=this.items[s],this.direction=h?"up":"down"));if(!o&&!this.options.dropOnEmpty)return;if(this.currentContainer===this.containers[p])return this.currentContainer.containerCache.over||(this.containers[p]._trigger("over",e,this._uiHash()),this.currentContainer.containerCache.over=1),void 0;o?this._rearrange(e,o,null,!0):this._rearrange(e,null,this.containers[p].element,!0),this._trigger("change",e,this._uiHash()),this.containers[p]._trigger("change",e,this._uiHash(this)),this.currentContainer=this.containers[p],this.options.placeholder.update(this.currentContainer,this.placeholder),this.containers[p]._trigger("over",e,this._uiHash(this)),this.containers[p].containerCache.over=1}},_createHelper:function(e){var i=this.options,s=t.isFunction(i.helper)?t(i.helper.apply(this.element[0],[e,this.currentItem])):"clone"===i.helper?this.currentItem.clone():this.currentItem;return s.parents("body").length||t("parent"!==i.appendTo?i.appendTo:this.currentItem[0].parentNode)[0].appendChild(s[0]),s[0]===this.currentItem[0]&&(this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")}),(!s[0].style.width||i.forceHelperSize)&&s.width(this.currentItem.width()),(!s[0].style.height||i.forceHelperSize)&&s.height(this.currentItem.height()),s},_adjustOffsetFromHelper:function(e){"string"==typeof e&&(e=e.split(" ")),t.isArray(e)&&(e={left:+e[0],top:+e[1]||0}),"left"in e&&(this.offset.click.left=e.left+this.margins.left),"right"in e&&(this.offset.click.left=this.helperProportions.width-e.right+this.margins.left),"top"in e&&(this.offset.click.top=e.top+this.margins.top),"bottom"in e&&(this.offset.click.top=this.helperProportions.height-e.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var e=this.offsetParent.offset();return"absolute"===this.cssPosition&&this.scrollParent[0]!==this.document[0]&&t.contains(this.scrollParent[0],this.offsetParent[0])&&(e.left+=this.scrollParent.scrollLeft(),e.top+=this.scrollParent.scrollTop()),(this.offsetParent[0]===this.document[0].body||this.offsetParent[0].tagName&&"html"===this.offsetParent[0].tagName.toLowerCase()&&t.ui.ie)&&(e={top:0,left:0}),{top:e.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:e.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"===this.cssPosition){var t=this.currentItem.position();return{top:t.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:t.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e,i,s,n=this.options;"parent"===n.containment&&(n.containment=this.helper[0].parentNode),("document"===n.containment||"window"===n.containment)&&(this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,"document"===n.containment?this.document.width():this.window.width()-this.helperProportions.width-this.margins.left,("document"===n.containment?this.document.height()||document.body.parentNode.scrollHeight:this.window.height()||this.document[0].body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]),/^(document|window|parent)$/.test(n.containment)||(e=t(n.containment)[0],i=t(n.containment).offset(),s="hidden"!==t(e).css("overflow"),this.containment=[i.left+(parseInt(t(e).css("borderLeftWidth"),10)||0)+(parseInt(t(e).css("paddingLeft"),10)||0)-this.margins.left,i.top+(parseInt(t(e).css("borderTopWidth"),10)||0)+(parseInt(t(e).css("paddingTop"),10)||0)-this.margins.top,i.left+(s?Math.max(e.scrollWidth,e.offsetWidth):e.offsetWidth)-(parseInt(t(e).css("borderLeftWidth"),10)||0)-(parseInt(t(e).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,i.top+(s?Math.max(e.scrollHeight,e.offsetHeight):e.offsetHeight)-(parseInt(t(e).css("borderTopWidth"),10)||0)-(parseInt(t(e).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top])},_convertPositionTo:function(e,i){i||(i=this.position);var s="absolute"===e?1:-1,n="absolute"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,o=/(html|body)/i.test(n[0].tagName);return{top:i.top+this.offset.relative.top*s+this.offset.parent.top*s-("fixed"===this.cssPosition?-this.scrollParent.scrollTop():o?0:n.scrollTop())*s,left:i.left+this.offset.relative.left*s+this.offset.parent.left*s-("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():o?0:n.scrollLeft())*s} +},_generatePosition:function(e){var i,s,n=this.options,o=e.pageX,a=e.pageY,r="absolute"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,l=/(html|body)/i.test(r[0].tagName);return"relative"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&this.scrollParent[0]!==this.offsetParent[0]||(this.offset.relative=this._getRelativeOffset()),this.originalPosition&&(this.containment&&(e.pageX-this.offset.click.leftthis.containment[2]&&(o=this.containment[2]+this.offset.click.left),e.pageY-this.offset.click.top>this.containment[3]&&(a=this.containment[3]+this.offset.click.top)),n.grid&&(i=this.originalPageY+Math.round((a-this.originalPageY)/n.grid[1])*n.grid[1],a=this.containment?i-this.offset.click.top>=this.containment[1]&&i-this.offset.click.top<=this.containment[3]?i:i-this.offset.click.top>=this.containment[1]?i-n.grid[1]:i+n.grid[1]:i,s=this.originalPageX+Math.round((o-this.originalPageX)/n.grid[0])*n.grid[0],o=this.containment?s-this.offset.click.left>=this.containment[0]&&s-this.offset.click.left<=this.containment[2]?s:s-this.offset.click.left>=this.containment[0]?s-n.grid[0]:s+n.grid[0]:s)),{top:a-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.scrollParent.scrollTop():l?0:r.scrollTop()),left:o-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():l?0:r.scrollLeft())}},_rearrange:function(t,e,i,s){i?i[0].appendChild(this.placeholder[0]):e.item[0].parentNode.insertBefore(this.placeholder[0],"down"===this.direction?e.item[0]:e.item[0].nextSibling),this.counter=this.counter?++this.counter:1;var n=this.counter;this._delay(function(){n===this.counter&&this.refreshPositions(!s)})},_clear:function(t,e){function i(t,e,i){return function(s){i._trigger(t,s,e._uiHash(e))}}this.reverting=!1;var s,n=[];if(!this._noFinalSort&&this.currentItem.parent().length&&this.placeholder.before(this.currentItem),this._noFinalSort=null,this.helper[0]===this.currentItem[0]){for(s in this._storedCSS)("auto"===this._storedCSS[s]||"static"===this._storedCSS[s])&&(this._storedCSS[s]="");this.currentItem.css(this._storedCSS),this._removeClass(this.currentItem,"ui-sortable-helper")}else this.currentItem.show();for(this.fromOutside&&!e&&n.push(function(t){this._trigger("receive",t,this._uiHash(this.fromOutside))}),!this.fromOutside&&this.domPosition.prev===this.currentItem.prev().not(".ui-sortable-helper")[0]&&this.domPosition.parent===this.currentItem.parent()[0]||e||n.push(function(t){this._trigger("update",t,this._uiHash())}),this!==this.currentContainer&&(e||(n.push(function(t){this._trigger("remove",t,this._uiHash())}),n.push(function(t){return function(e){t._trigger("receive",e,this._uiHash(this))}}.call(this,this.currentContainer)),n.push(function(t){return function(e){t._trigger("update",e,this._uiHash(this))}}.call(this,this.currentContainer)))),s=this.containers.length-1;s>=0;s--)e||n.push(i("deactivate",this,this.containers[s])),this.containers[s].containerCache.over&&(n.push(i("out",this,this.containers[s])),this.containers[s].containerCache.over=0);if(this.storedCursor&&(this.document.find("body").css("cursor",this.storedCursor),this.storedStylesheet.remove()),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex","auto"===this._storedZIndex?"":this._storedZIndex),this.dragging=!1,e||this._trigger("beforeStop",t,this._uiHash()),this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.cancelHelperRemoval||(this.helper[0]!==this.currentItem[0]&&this.helper.remove(),this.helper=null),!e){for(s=0;n.length>s;s++)n[s].call(this,t);this._trigger("stop",t,this._uiHash())}return this.fromOutside=!1,!this.cancelHelperRemoval},_trigger:function(){t.Widget.prototype._trigger.apply(this,arguments)===!1&&this.cancel()},_uiHash:function(e){var i=e||this;return{helper:i.helper,placeholder:i.placeholder||t([]),position:i.position,originalPosition:i.originalPosition,offset:i.positionAbs,item:i.currentItem,sender:e?e.element:null}}})}); \ No newline at end of file diff --git a/ext/phpbbstudio/aps/composer.json b/ext/phpbbstudio/aps/composer.json new file mode 100644 index 0000000..3036f0f --- /dev/null +++ b/ext/phpbbstudio/aps/composer.json @@ -0,0 +1,32 @@ +{ + "name": "phpbbstudio/aps", + "type": "phpbb-extension", + "description": "A fully integrated and extendable points extension for the phpBB Forum Software.", + "homepage": "https://www.phpbbstudio.com", + "version": "1.0.6-RC", + "time": "2020-03-11", + "license": "GPL-2.0-only", + "authors": [ + { + "name": "phpBB Studio", + "email": "info@phpbbstudio.com", + "homepage": "https://www.phpbbstudio.com", + "role": "Lead Developer" + } + ], + "require": { + "php": ">=5.5.0", + "composer/installers": "~1.0" + }, + "extra": { + "display-name": "phpBB Studio - Advanced Points System", + "soft-require": { + "phpbb/phpbb": ">=3.2.8,<4.0.0@dev" + }, + "version-check": { + "host": "3d-i.github.io", + "directory": "/site/vchecks", + "filename": "advpoints.json" + } + } +} diff --git a/ext/phpbbstudio/aps/config/actions.yml b/ext/phpbbstudio/aps/config/actions.yml new file mode 100644 index 0000000..284842e --- /dev/null +++ b/ext/phpbbstudio/aps/config/actions.yml @@ -0,0 +1,139 @@ +services: + # Service collection + phpbbstudio.aps.actions_collection: + class: phpbb\di\ordered_service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: phpbbstudio.aps.action } + + # Base class + phpbbstudio.aps.action.base: + abstract: true + + # GLOBAL + phpbbstudio.aps.action.pm: + class: phpbbstudio\aps\actions\type\pm + shared: false + arguments: + - '@config' + - '@text_formatter.utils' + - '%phpbbstudio.aps.constants%' + tags: + - { name: phpbbstudio.aps.action, order: -4 } + + phpbbstudio.aps.action.birthday: + class: phpbbstudio\aps\actions\type\birthday + shared: false + arguments: + - '@user' + tags: + - { name: phpbbstudio.aps.action, order: -3 } + + phpbbstudio.aps.action.register: + class: phpbbstudio\aps\actions\type\register + shared: false + tags: + - { name: phpbbstudio.aps.action, order: -2 } + + phpbbstudio.aps.action.warn: + class: phpbbstudio\aps\actions\type\warn + shared: false + arguments: + - '@user' + tags: + - { name: phpbbstudio.aps.action, order: -1 } + + # LOCAL - Topic + phpbbstudio.aps.action.topic: + class: phpbbstudio\aps\actions\type\topic + shared: false + arguments: + - '@config' + - '@text_formatter.utils' + - '%phpbbstudio.aps.constants%' + tags: + - { name: phpbbstudio.aps.action, order: -4 } + + # LOCAL - Post + phpbbstudio.aps.action.post: + class: phpbbstudio\aps\actions\type\post + shared: false + arguments: + - '@config' + - '@text_formatter.utils' + - '%phpbbstudio.aps.constants%' + tags: + - { name: phpbbstudio.aps.action, order: -3 } + + # LOCAL - Moderate + phpbbstudio.aps.action.copy: + class: phpbbstudio\aps\actions\type\copy + shared: false + tags: + - { name: phpbbstudio.aps.action, order: -2 } + + phpbbstudio.aps.action.change: + class: phpbbstudio\aps\actions\type\change + shared: false + tags: + - { name: phpbbstudio.aps.action, order: -2 } + + phpbbstudio.aps.action.delete: + class: phpbbstudio\aps\actions\type\delete + shared: false + arguments: + - '@user' + tags: + - { name: phpbbstudio.aps.action, order: -2 } + + phpbbstudio.aps.action.edit: + class: phpbbstudio\aps\actions\type\edit + shared: false + tags: + - { name: phpbbstudio.aps.action, order: -2 } + + phpbbstudio.aps.action.lock: + class: phpbbstudio\aps\actions\type\lock + shared: false + arguments: + - '@user' + tags: + - { name: phpbbstudio.aps.action, order: -2 } + + phpbbstudio.aps.action.merge: + class: phpbbstudio\aps\actions\type\merge + shared: false + arguments: + - '@user' + tags: + - { name: phpbbstudio.aps.action, order: -2 } + + phpbbstudio.aps.action.move: + class: phpbbstudio\aps\actions\type\move + shared: false + arguments: + - '@user' + tags: + - { name: phpbbstudio.aps.action, order: -1 } + + phpbbstudio.aps.action.queue: + class: phpbbstudio\aps\actions\type\queue + shared: false + arguments: + - '@user' + tags: + - { name: phpbbstudio.aps.action, order: -1 } + + phpbbstudio.aps.action.topic_type: + class: phpbbstudio\aps\actions\type\topic_type + shared: false + tags: + - { name: phpbbstudio.aps.action, order: -1 } + + # LOCAL - Misc. + phpbbstudio.aps.action.vote: + class: phpbbstudio\aps\actions\type\vote + shared: false + tags: + - { name: phpbbstudio.aps.action, order: 0 } diff --git a/ext/phpbbstudio/aps/config/constants.yml b/ext/phpbbstudio/aps/config/constants.yml new file mode 100644 index 0000000..b13b998 --- /dev/null +++ b/ext/phpbbstudio/aps/config/constants.yml @@ -0,0 +1,21 @@ +parameters: + phpbbstudio.aps.constants: + ignore: + none: 0 + both: 1 + words: 2 + chars: 3 + locations: + navbar_header_quick_links_before: 1 + navbar_header_quick_links_after: 2 + navbar_header_user_profile_prepend: 4 + navbar_header_user_profile_append: 8 + navbar_header_profile_list_before: 16 + navbar_header_profile_list_after: 32 + overall_header_navigation_prepend: 64 + overall_header_navigation_append: 128 + overall_footer_breadcrumb_append: 256 + overall_footer_timezone_before: 512 + overall_footer_timezone_after: 1024 + overall_footer_teamlink_before: 2048 + overall_footer_teamlink_after: 4096 diff --git a/ext/phpbbstudio/aps/config/parameters.yml b/ext/phpbbstudio/aps/config/parameters.yml new file mode 100644 index 0000000..524814e --- /dev/null +++ b/ext/phpbbstudio/aps/config/parameters.yml @@ -0,0 +1,5 @@ +parameters: + phpbbstudio.aps.tables.display: '%core.table_prefix%aps_display' + phpbbstudio.aps.tables.logs: '%core.table_prefix%aps_logs' + phpbbstudio.aps.tables.points: '%core.table_prefix%aps_points' + phpbbstudio.aps.tables.reasons: '%core.table_prefix%aps_reasons' diff --git a/ext/phpbbstudio/aps/config/routing.yml b/ext/phpbbstudio/aps/config/routing.yml new file mode 100644 index 0000000..65020ed --- /dev/null +++ b/ext/phpbbstudio/aps/config/routing.yml @@ -0,0 +1,17 @@ +phpbbstudio_aps_display_pagination: + path: /aps/{page}/page-{pagination} + defaults: + _controller: phpbbstudio.aps.controller.main:display + page: overview + requirements: + page: "^((?!shop|inventory|purchase).)*$" + pagination: \d+ + +phpbbstudio_aps_display: + path: /aps/{page} + defaults: + _controller: phpbbstudio.aps.controller.main:display + page: overview + pagination: 1 + requirements: + page: "^((?!shop|inventory|purchase).)*$" diff --git a/ext/phpbbstudio/aps/config/services.yml b/ext/phpbbstudio/aps/config/services.yml new file mode 100644 index 0000000..588a9de --- /dev/null +++ b/ext/phpbbstudio/aps/config/services.yml @@ -0,0 +1,73 @@ +imports: + - { resource: actions.yml } + - { resource: constants.yml } + - { resource: parameters.yml } + - { resource: services_controllers.yml } + - { resource: services_core.yml } + - { resource: services_listeners.yml } + +services: + phpbbstudio.aps.manager: + class: phpbbstudio\aps\actions\manager + arguments: + - '@phpbbstudio.aps.actions_collection' + - '@phpbbstudio.aps.distributor' + - '@phpbbstudio.aps.functions' + - '@language' + - '@log' + - '@phpbbstudio.aps.valuator' + - '@user' + + phpbbstudio.aps.blockader: + class: phpbbstudio\aps\points\blockader + arguments: + - '@dbal.conn' + - '%phpbbstudio.aps.tables.display%' + + phpbbstudio.aps.distributor: + class: phpbbstudio\aps\points\distributor + arguments: + - '@config' + - '@dbal.conn' + - '@dispatcher' + - '@phpbbstudio.aps.functions' + - '@phpbbstudio.aps.log' + - '@user' + - '@phpbbstudio.aps.valuator' + + phpbbstudio.aps.reasoner: + class: phpbbstudio\aps\points\reasoner + arguments: + - '@dbal.conn' + - '%phpbbstudio.aps.tables.reasons%' + + phpbbstudio.aps.valuator: + class: phpbbstudio\aps\points\valuator + arguments: + - '@dbal.conn' + - '@phpbbstudio.aps.functions' + - '@user' + - '%phpbbstudio.aps.tables.points%' + + phpbbstudio.aps.birthday: + class: phpbbstudio\aps\cron\task\birthday + arguments: + - '@config' + - '@dbal.conn' + - '@phpbbstudio.aps.functions' + - '@phpbbstudio.aps.manager' + calls: + - [set_name, [phpbbstudio.aps.cron.task.birthday]] + tags: + - { name: cron.task } + + phpbbstudio.aps.notification.type.adjust: + class: phpbbstudio\aps\notification\type\adjust + shared: false # service MUST not be shared for this to work! + parent: notification.type.base + calls: + - [set_auth, ['@auth']] + - [set_controller_helper, ['@controller.helper']] + - [set_user_loader, ['@user_loader']] + tags: + - { name: notification.type } diff --git a/ext/phpbbstudio/aps/config/services_controllers.yml b/ext/phpbbstudio/aps/config/services_controllers.yml new file mode 100644 index 0000000..1ce02d7 --- /dev/null +++ b/ext/phpbbstudio/aps/config/services_controllers.yml @@ -0,0 +1,58 @@ +services: + phpbbstudio.aps.controller.acp: + class: phpbbstudio\aps\controller\acp_controller + arguments: + - '@phpbbstudio.aps.acp' + - '@auth' + - '@phpbbstudio.aps.blockader' + - '@config' + - '@phpbbstudio.aps.controller.main' + - '@dbal.conn' + - '@dispatcher' + - '@phpbbstudio.aps.functions' + - '@language' + - '@log' + - '@phpbbstudio.aps.log' + - '@pagination' + - '@phpbbstudio.aps.reasoner' + - '@request' + - '@template' + - '@user' + + phpbbstudio.aps.controller.mcp: + class: phpbbstudio\aps\controller\mcp_controller + arguments: + - '@auth' + - '@config' + - '@dbal.conn' + - '@dispatcher' + - '@phpbbstudio.aps.distributor' + - '@phpbbstudio.aps.functions' + - '@group_helper' + - '@language' + - '@phpbbstudio.aps.log' + - '@notification_manager' + - '@pagination' + - '@phpbbstudio.aps.reasoner' + - '@request' + - '@template' + - '@user' + - '@phpbbstudio.aps.valuator' + - '%core.root_path%' + - '%core.php_ext%' + + phpbbstudio.aps.controller.main: + class: phpbbstudio\aps\controller\main_controller + arguments: + - '@auth' + - '@phpbbstudio.aps.blockader' + - '@phpbbstudio.aps.blocks' + - '@dispatcher' + - '@phpbbstudio.aps.functions' + - '@controller.helper' + - '@language' + - '@request' + - '@template' + - '@user' + - '%core.root_path%' + - '%core.php_ext%' diff --git a/ext/phpbbstudio/aps/config/services_core.yml b/ext/phpbbstudio/aps/config/services_core.yml new file mode 100644 index 0000000..6dd6266 --- /dev/null +++ b/ext/phpbbstudio/aps/config/services_core.yml @@ -0,0 +1,78 @@ +services: + phpbbstudio.aps.acp: + class: phpbbstudio\aps\core\acp + arguments: + - '@phpbbstudio.aps.functions' + - '@template' + - '@phpbbstudio.aps.actions_collection' + - '@phpbbstudio.aps.valuator' + + phpbbstudio.aps.blocks: + class: phpbbstudio\aps\core\blocks + arguments: + - '@auth' + - '@config' + - '@dbal.conn' + - '@phpbbstudio.aps.dbal' + - '@phpbbstudio.aps.functions' + - '@group_helper' + - '@controller.helper' + - '@language' + - '@phpbbstudio.aps.log' + - '@pagination' + - '@request' + - '@template' + - '@user' + - '%core.root_path%' + - '%core.php_ext%' + - '%phpbbstudio.aps.tables.logs%' + + phpbbstudio.aps.dbal: + class: phpbbstudio\aps\core\dbal + arguments: + - '@dbal.conn' + + phpbbstudio.aps.functions: + class: phpbbstudio\aps\core\functions + arguments: + - '@auth' + - '@config' + - '@dbal.conn' + - '@ext.manager' + - '@language' + - '@path_helper' + - '@request' + - '@user' + - '%core.table_prefix%' + - '%phpbbstudio.aps.constants%' + + phpbbstudio.aps.language: + class: phpbbstudio\aps\core\language + arguments: + - '@config' + - '@language' + - '@ext.manager' + - '@user' + - '%core.php_ext%' + + phpbbstudio.aps.log: + class: phpbbstudio\aps\core\log + arguments: + - '@auth' + - '@config' + - '@dbal.conn' + - '@phpbbstudio.aps.functions' + - '@language' + - '@phpbbstudio.aps.language' + - '@user' + - '%phpbbstudio.aps.tables.logs%' + - '%core.root_path%' + - '%core.adm_relative_path%' + - '%core.php_ext%' + + phpbbstudio.aps.template: + class: phpbbstudio\aps\core\template + arguments: + - '@phpbbstudio.aps.functions' + tags: + - { name: twig.extension } diff --git a/ext/phpbbstudio/aps/config/services_listeners.yml b/ext/phpbbstudio/aps/config/services_listeners.yml new file mode 100644 index 0000000..026e4a9 --- /dev/null +++ b/ext/phpbbstudio/aps/config/services_listeners.yml @@ -0,0 +1,67 @@ +services: + phpbbstudio.aps.listener.acp: + class: phpbbstudio\aps\event\acp + arguments: + - '@phpbbstudio.aps.acp' + - '@auth' + - '@config' + - '@phpbbstudio.aps.functions' + - '@controller.helper' + - '@language' + - '@log' + - '@phpbbstudio.aps.log' + - '@request' + - '@template' + - '@user' + tags: + - { name: event.listener } + + phpbbstudio.aps.listener.actions: + class: phpbbstudio\aps\event\actions + arguments: + - '@auth' + - '@config' + - '@phpbbstudio.aps.functions' + - '@phpbbstudio.aps.manager' + - '@request' + - '@user' + - '%core.root_path%' + - '%core.php_ext%' + tags: + - { name: event.listener } + + phpbbstudio.aps.listener.check: + class: phpbbstudio\aps\event\check + arguments: + - '@config' + - '@phpbbstudio.aps.functions' + - '@language' + - '@template' + - '@user' + - '@phpbbstudio.aps.valuator' + tags: + - { name: event.listener } + + phpbbstudio.aps.listener.display: + class: phpbbstudio\aps\event\display + arguments: + - '@phpbbstudio.aps.functions' + - '@controller.helper' + - '@language' + - '@template' + - '%core.php_ext%' + tags: + - { name: event.listener } + + phpbbstudio.aps.listener.modules: + class: phpbbstudio\aps\event\modules + arguments: + - '@phpbbstudio.aps.functions' + - '@language' + tags: + - { name: event.listener } + + phpbbstudio.aps.listener.permissions: + class: phpbbstudio\aps\event\permissions + tags: + - { name: event.listener } diff --git a/ext/phpbbstudio/aps/controller/acp_controller.php b/ext/phpbbstudio/aps/controller/acp_controller.php new file mode 100644 index 0000000..5e228f1 --- /dev/null +++ b/ext/phpbbstudio/aps/controller/acp_controller.php @@ -0,0 +1,940 @@ +acp = $acp; + $this->auth = $auth; + $this->blockader = $blockader; + $this->config = $config; + $this->controller = $controller; + $this->db = $db; + $this->dispatcher = $dispatcher; + $this->functions = $functions; + $this->language = $language; + $this->log_phpbb = $log_phpbb; + $this->log = $log; + $this->pagination = $pagination; + $this->reasoner = $reasoner; + $this->request = $request; + $this->template = $template; + $this->user = $user; + } + + /** + * Handle ACP settings. + * + * @return void + * @access public + */ + public function settings() + { + $this->log->load_lang(); + $this->language->add_lang('aps_acp_common', 'phpbbstudio/aps'); + + $errors = []; + $submit = $this->request->is_set_post('submit'); + $form_key = 'aps_settings'; + add_form_key($form_key); + + if ($action = $this->request->variable('action', '', true)) + { + switch ($action) + { + case 'copy': + $errors = $this->copy_points(); + break; + + case 'clean': + $this->clean_points(); + break; + + case 'locations': + $this->link_locations(); + break; + } + } + + $settings = [ + 'legend1' => 'GENERAL_OPTIONS', + 'aps_points_copy' => ['lang' => 'ACP_APS_POINTS_COPY_TITLE', 'type' => 'custom', 'method' => 'set_action', 'params' => ['copy']], + 'aps_points_clean' => ['lang' => 'ACP_APS_POINTS_CLEAN', 'type' => 'custom', 'method' => 'set_action', 'params' => ['clean']], + 'aps_points_safe_mode' => ['lang' => 'ACP_APS_POINTS_SAFE_MODE', 'type' => 'radio:enabled_disabled', 'validate' => 'bool', 'explain' => true], + 'legend2' => 'ACP_APS_POINTS_NAMES', + // Later inserted + 'legend3' => 'ACP_APS_FORMATTING', + 'aps_points_icon' => ['lang' => 'ACP_APS_POINTS_ICON', 'type' => 'text:0:100', 'append' => ''], + 'aps_points_icon_img' => ['lang' => 'ACP_APS_POINTS_ICON_IMG', 'validate' => 'string:0:255', 'type' => 'custom', 'method' => 'build_icon_image_select', 'explain' => true], + 'aps_points_icon_position' => ['lang' => 'ACP_APS_POINTS_ICON_POSITION', 'validate' => 'bool', 'type' => 'custom', 'method' => 'build_position_radio'], + 'aps_points_decimals' => ['lang' => 'ACP_APS_POINTS_DECIMALS', 'validate' => 'string', 'type' => 'select', 'method' => 'build_decimal_select'], + 'aps_points_separator_dec' => ['lang' => 'ACP_APS_SEPARATOR_DEC', 'validate' => 'string', 'type' => 'select', 'method' => 'build_separator_select', 'params' => ['{CONFIG_VALUE}']], + 'aps_points_separator_thou' => ['lang' => 'ACP_APS_SEPARATOR_THOU', 'validate' => 'string', 'type' => 'select', 'method' => 'build_separator_select'], + 'legend4' => 'GENERAL_SETTINGS', + 'aps_link_locations' => ['lang' => 'ACP_APS_LOCATIONS', 'type' => 'custom', 'method' => 'set_action', 'params' => ['locations', false], 'explain' => true], + 'aps_points_display_profile' => ['lang' => 'ACP_APS_POINTS_DISPLAY_PROFILE', 'type' => 'radio:yes_no', 'validate' => 'bool', 'explain' => true], + 'aps_points_display_post' => ['lang' => 'ACP_APS_POINTS_DISPLAY_POST', 'type' => 'radio:yes_no', 'validate' => 'bool', 'explain' => true], + 'aps_points_display_pm' => ['lang' => 'ACP_APS_POINTS_DISPLAY_PM', 'type' => 'radio:yes_no', 'validate' => 'bool', 'explain' => true], + 'aps_points_min' => ['lang' => 'ACP_APS_POINTS_MIN', 'type' => 'number', 'validate' => 'string', 'explain' => true], // Validate as string to make sure it does not default to 0 + 'aps_points_max' => ['lang' => 'ACP_APS_POINTS_MAX', 'type' => 'number', 'validate' => 'string', 'explain' => true], + 'aps_actions_per_page' => ['lang' => 'ACP_APS_POINTS_PER_PAGE', 'type' => 'number:10:100', 'validate' => 'number:10:100', 'explain' => true], + 'aps_points_exclude_words' => ['lang' => 'ACP_APS_POINTS_EXCLUDE_WORDS', 'type' => 'number:0:10', 'validate' => 'number:0:10', 'explain' => true, 'append' => ' ' . $this->language->lang('ACP_APS_CHARACTERS')], + 'aps_points_exclude_chars' => ['lang' => 'ACP_APS_POINTS_EXCLUDE_CHARS', 'type' => 'radio:yes_no', 'validate' => 'bool', 'explain' => true], + 'legend5' => 'ACP_APS_IGNORE_SETTINGS', + 'aps_ignore_criteria' => ['lang' => 'ACP_APS_IGNORE_CRITERIA', 'validate' => 'int:0:4', 'type' => 'custom', 'method' => 'build_ignore_criteria_radio', 'explain' => true], + 'aps_ignore_min_words' => ['lang' => 'ACP_APS_IGNORE_MIN_WORDS', 'type' => 'number:0:100', 'validate' => 'number:0:100', 'explain' => true], + 'aps_ignore_min_chars' => ['lang' => 'ACP_APS_IGNORE_MIN_CHARS', 'type' => 'number:0:200', 'validate' => 'number:0:200', 'explain' => true], + 'aps_ignore_excluded_words' => ['lang' => 'ACP_APS_IGNORE_EXCLUDED_WORDS', 'type' => 'radio:yes_no', 'validate' => 'bool', 'explain' => true], + 'aps_ignore_excluded_chars' => ['lang' => 'ACP_APS_IGNORE_EXCLUDED_CHARS', 'type' => 'radio:yes_no', 'validate' => 'bool', 'explain' => true], + 'legend6' => 'ACP_APS_CHAIN_SETTINGS', + 'aps_chain_merge_delete' => ['lang' => 'ACP_APS_CHAIN_MERGE_DELETE', 'type' => 'radio:enabled_disabled', 'validate' => 'bool', 'explain' => true], + 'aps_chain_merge_move' => ['lang' => 'ACP_APS_CHAIN_MERGE_MOVE', 'type' => 'radio:enabled_disabled', 'validate' => 'bool', 'explain' => true], + 'aps_chain_warn_pm' => ['lang' => 'ACP_APS_CHAIN_WARN_PM', 'type' => 'radio:enabled_disabled', 'validate' => 'bool', 'explain' => true], + ]; + + $settings = phpbb_insert_config_array($settings, $this->build_point_names(), ['after' => 'legend2']); + + /** + * Event to add additional settings to the APS ACP settings page. + * + * @event phpbbstudio.aps.acp_settings + * @var array settings Available settings + * @since 1.0.0 + */ + $vars = ['settings']; + extract($this->dispatcher->trigger_event('phpbbstudio.aps.acp_settings', compact($vars))); + + $this->config_new = clone $this->config; + $settings_array = $submit ? $this->request->variable('config', ['' => '']) : $this->config_new; + + validate_config_vars($settings, $settings_array, $errors); + + if ($submit && !check_form_key($form_key)) + { + $errors[] = $this->language->lang('FORM_INVALID'); + } + + if (!empty($errors)) + { + $submit = false; + } + + foreach ($settings as $config_name => $data) + { + if (!isset($settings_array[$config_name]) || strpos($config_name, 'legend') !== false) + { + continue; + } + + $this->config_new[$config_name] = $config_value = $settings_array[$config_name]; + + if ($submit) + { + $this->config->set($config_name, $config_value); + } + } + + if ($submit) + { + // Log the action + $this->log_phpbb->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_ACP_APS_SETTINGS'); + + // Show success message + trigger_error($this->language->lang('ACP_APS_SETTINGS_SUCCESS') . adm_back_link($this->u_action)); + } + + foreach ($settings as $config_key => $setting) + { + if (!is_array($setting) && strpos($config_key, 'legend') === false) + { + continue; + } + + if (strpos($config_key, 'legend') !== false) + { + $this->template->assign_block_vars('settings', [ + 'CLASS' => str_replace(['acp_', '_'], ['', '-'], utf8_strtolower($setting)), + 'LEGEND' => $setting, + 'S_LEGEND' => true, + ]); + + continue; + } + + $type = explode(':', $setting['type']); + $content = build_cfg_template($type, $config_key, $this->config_new, $config_key, $setting); + + if (empty($content)) + { + continue; + } + + $booleans = ['yes_no', 'no_yes', 'enabled_disabled', 'disabled_enabled']; + if ($type[0] === 'radio' && !empty($type[1]) && in_array($type[1], $booleans)) + { + $yes = [$this->language->lang('YES'), $this->language->lang('ENABLED')]; + $no = [$this->language->lang('NO'), $this->language->lang('DISABLED')]; + $content = preg_replace( + ['/(' . implode('|', $yes) . ')/', '/(' . implode('|', $no) . ')/', '/class="radio"/'], + ['$1', '$1', 'class="radio aps-bool"'], + $content + ); + } + + $this->template->assign_block_vars('settings', [ + 'KEY' => $config_key, + 'CONTENT' => $content, + 'TITLE' => $setting['lang'], + 'S_EXPLAIN' => isset($setting['explain']) ? $setting['explain'] : false, + ]); + } + + $this->template->assign_vars([ + 'S_ERROR' => !empty($errors), + 'ERROR_MSG' => !empty($errors) ? implode('
', $errors) : '', + + 'U_ACTION' => $this->u_action, + ]); + } + + /** + * Handle ACP display. + * + * @return void + * @access public + */ + public function display() + { + $this->log->load_lang(); + $this->language->add_lang(['aps_acp_common', 'aps_display'], 'phpbbstudio/aps'); + + $errors = []; + + add_form_key('aps_display'); + + $blocks = $this->controller->get_page_blocks(); + $admin_blocks = $this->blockader->row($this->blockader->get_admin_id()); + $insert = empty($admin_blocks); + + if ($insert) + { + foreach ($blocks as $slug => $data) + { + $admin_blocks[$slug] = array_keys($data['blocks']); + } + } + + $admin_pages = array_keys($admin_blocks); + + foreach ($admin_pages as $slug) + { + if (empty($blocks[$slug])) + { + continue; + } + + $data = $blocks[$slug]; + + $this->template->assign_block_vars('aps_pages', [ + 'ID' => $slug, + 'TITLE' => $data['title'], + 'S_ACTIVE' => in_array($slug, $admin_pages), + ]); + + foreach ($data['blocks'] as $block_id => $block) + { + $this->template->assign_block_vars('aps_pages.blocks', [ + 'ID' => $block_id, + 'TITLE' => $block['title'], + 'S_ACTIVE' => isset($admin_blocks[$slug]) && in_array($block_id, $admin_blocks[$slug]), + ]); + } + } + + $submit = $this->request->is_set_post('submit'); + + $settings = [ + 'aps_display_top_change' => $this->request->variable('aps_display_top_change', (int) $this->config['aps_display_top_change']), + 'aps_display_top_count' => $this->request->variable('aps_display_top_count', (int) $this->config['aps_display_top_count']), + 'aps_display_adjustments' => $this->request->variable('aps_display_adjustments', (int) $this->config['aps_display_adjustments']), + 'aps_display_graph_time' => $this->request->variable('aps_display_graph_time', (int) $this->config['aps_display_graph_time']), + ]; + + /** + * Event to handle additional settings for the APS ACP display page. + * + * @event phpbbstudio.aps.acp_display + * @var array settings Available settings + * @var array errors Any errors that may have occurred + * @var bool submit Whether or not the form was submitted + * @since 1.0.2 + */ + $vars = ['settings', 'errors', 'submit']; + extract($this->dispatcher->trigger_event('phpbbstudio.aps.acp_display', compact($vars))); + + if ($submit) + { + if (!check_form_key('aps_display')) + { + $errors[] = $this->language->lang('FORM_INVALID'); + } + + $display_blocks = $this->request->variable('aps_blocks', ['' => ['']]); + + if (empty($errors)) + { + // Set the settings + foreach ($settings as $name => $value) + { + if ($this->config[$name] != $value) + { + $this->config->set($name, $value); + } + } + + foreach ($display_blocks as $key => $array) + { + $display_blocks[$key] = array_filter($array); + } + + // Set the blocks + $this->blockader->set_blocks($this->blockader->get_admin_id(), $display_blocks, $insert); + + // Log the action + $this->log_phpbb->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_ACP_APS_DISPLAY'); + + // Show success message + trigger_error($this->language->lang('ACP_APS_DISPLAY_SUCCESS') . adm_back_link($this->u_action)); + } + } + + foreach ($settings as $name => $value) + { + $this->template->assign_var(utf8_strtoupper($name), $value); + } + + $this->template->assign_vars([ + 'S_ERROR' => !empty($errors), + 'ERROR_MSG' => !empty($errors) ? implode('
', $errors) : '', + + 'U_ACTION' => $this->u_action, + ]); + } + + /** + * Handle ACP points and reasons. + * + * @return void + * @access public + */ + public function points() + { + $this->log->load_lang(); + $this->language->add_lang('aps_acp_common', 'phpbbstudio/aps'); + + $errors = []; + $action = $this->request->variable('action', ''); + $submit = $this->request->is_set_post('submit'); + + $form_name = 'acp_aps_points'; + add_form_key($form_name); + + if ( + (!$this->auth->acl_get('a_aps_points') && !$this->auth->acl_get('a_aps_reasons')) + || (!empty($action) && !$this->auth->acl_get('a_aps_reasons')) + ) + { + trigger_error('NOT_AUTHORISED', E_USER_WARNING); + } + + switch ($action) + { + case 'add': + case 'edit': + $reason_id = (int) $this->request->variable('r', 0); + + $reason = $this->reasoner->row($reason_id); + $reason = $this->reasoner->fill($reason); + + $reason['reason_title'] = $this->request->variable('title', (string) $reason['reason_title'] , true); + $reason['reason_desc'] = $this->request->variable('description', (string) $reason['reason_desc'], true); + $reason['reason_points'] = $this->request->variable('points', (double) $reason['reason_points']); + + if ($submit) + { + if (!check_form_key($form_name)) + { + $errors[] = $this->language->lang('FORM_INVALID'); + } + + if (empty($reason['reason_title']) || strlen($reason['reason_title']) > 255) + { + $errors[] = $this->language->lang('ACP_APS_REASON_EMPTY_SUBJECT'); + } + + $reason_points_to_check = round($reason['reason_points'], 2); + + if (empty($reason_points_to_check)) + { + $errors[] = $this->language->lang('ACP_APS_REASON_EMPTY_POINTS', $this->functions->get_name()); + } + + if (empty($errors)) + { + if ($action === 'add') + { + $this->reasoner->insert($reason); + } + else + { + $this->reasoner->update($reason, $reason_id); + } + + $this->log_phpbb->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_ACP_APS_REASON_' . utf8_strtoupper($action)); + + trigger_error($this->language->lang('ACP_APS_REASON_SAVED') . adm_back_link($this->u_action)); + } + } + + $this->template->assign_vars([ + 'REASON_TITLE' => $reason['reason_title'], + 'REASON_DESC' => $reason['reason_desc'], + 'REASON_POINTS' => $reason['reason_points'], + ]); + break; + + case 'delete': + $reason_id = (int) $this->request->variable('r', 0); + + if (confirm_box(true)) + { + $this->reasoner->delete($reason_id); + + $json_response = new \phpbb\json_response; + $json_response->send([ + 'SUCCESS' => true, + 'MESSAGE_TITLE' => $this->language->lang('INFORMATION'), + 'MESSAGE_TEXT' => $this->language->lang('ACP_APS_REASON_DELETE_SUCCESS'), + ]); + } + else + { + confirm_box(false, 'ACP_APS_REASON_DELETE', build_hidden_fields([ + 'submit' => $submit, + 'action' => $action, + ])); + } + break; + + case 'move': + $reason_id = $this->request->variable('r', 0); + $dir = $this->request->variable('dir', ''); + + $this->reasoner->order($reason_id, $dir); + + $json_response = new \phpbb\json_response; + $json_response->send([ + 'success' => true, + ]); + break; + + default: + if ($this->auth->acl_get('a_aps_points')) + { + $this->acp->build(); + + if ($submit) + { + if (!check_form_key($form_name)) + { + $errors[] = $this->language->lang('FORM_INVALID'); + } + + if (empty($errors)) + { + $values = $this->request->variable('aps_values', ['' => 0.00]); + + $this->acp->set_points($values); + + $this->log_phpbb->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_ACP_APS_POINTS', time(), [$this->functions->get_name()]); + + trigger_error($this->language->lang('ACP_APS_POINTS_SUCCESS', $this->functions->get_name()) . adm_back_link($this->u_action)); + } + } + } + + if ($this->auth->acl_get('a_aps_points')) + { + $rowset = $this->reasoner->rowset(); + + foreach ($rowset as $row) + { + $this->template->assign_block_vars('aps_reasons', [ + 'ID' => (int) $row['reason_id'], + 'TITLE' => (string) $row['reason_title'], + 'DESC' => (string) $row['reason_desc'], + 'POINTS' => (double) $row['reason_points'], + + 'U_DELETE' => $this->u_action . '&action=delete&r=' . (int) $row['reason_id'], + 'U_EDIT' => $this->u_action . '&action=edit&r=' . (int) $row['reason_id'], + 'U_MOVE_UP' => $this->u_action . '&action=move&dir=up&r=' . (int) $row['reason_id'], + 'U_MOVE_DOWN' => $this->u_action . '&action=move&dir=down&r=' . (int) $row['reason_id'], + ]); + } + + $this->template->assign_vars([ + 'U_APS_REASON_ADD' => $this->u_action . '&action=add', + ]); + } + break; + } + + $s_errors = (bool) count($errors); + + $this->template->assign_vars([ + 'S_ERRORS' => $s_errors, + 'ERROR_MSG' => $s_errors ? implode('
', $errors) : '', + + 'APS_TITLE' => $action ? $this->language->lang('ACP_APS_REASON_' . utf8_strtoupper($action)) : $this->functions->get_name(), + + 'S_APS_ACTION' => $action, + 'S_APS_POINTS' => $this->auth->acl_get('a_aps_points'), + 'S_APS_REASONS' => $this->auth->acl_get('a_aps_reasons'), + + 'U_APS_ACTION' => $this->u_action . ($action ? "&action={$action}" : '') . (!empty($reason_id) ? "&r={$reason_id}" : ''), + ]); + } + + /** + * Handle ACP logs. + * + * @return void + * @access public + */ + public function logs() + { + $this->log->load_lang(); + $this->language->add_lang('aps_acp_common', 'phpbbstudio/aps'); + + // Set up general vars + $start = $this->request->variable('start', 0); + $forum_id = $this->request->variable('f', ''); + $topic_id = $this->request->variable('t', 0); + $post_id = $this->request->variable('p', 0); + $user_id = $this->request->variable('u', 0); + $reportee_id = $this->request->variable('r', 0); + + $delete_mark = $this->request->variable('del_marked', false, false, \phpbb\request\request_interface::POST); + $delete_all = $this->request->variable('del_all', false, false, \phpbb\request\request_interface::POST); + $marked = $this->request->variable('mark', [0]); + + // Sort keys + $sort_days = $this->request->variable('st', 0); + $sort_key = $this->request->variable('sk', 't'); + $sort_dir = $this->request->variable('sd', 'd'); + + // Keywords + $keywords = $this->request->variable('keywords', '', true); + $keywords_param = !empty($keywords) ? '&keywords=' . urlencode(htmlspecialchars_decode($keywords)) : ''; + + if (($delete_mark || $delete_all)) + { + if (confirm_box(true)) + { + $conditions = []; + + if ($delete_mark && count($marked)) + { + $conditions['log_id'] = ['IN' => $marked]; + } + + if ($delete_all) + { + if ($sort_days) + { + $conditions['log_time'] = ['>=', time() - ($sort_days * 86400)]; + } + + $conditions['keywords'] = $keywords; + } + + $this->log->delete($conditions); + + $plural = $delete_all ? 2 : count($marked); + $log_action = 'LOG_ACP_APS_LOGS_' . $delete_all ? 'CLEARED' : 'DELETED'; + $this->log_phpbb->add('admin', $this->user->data['user_id'], $this->user->ip, $log_action, time(), [$this->functions->get_name()]); + + trigger_error($this->language->lang('ACP_APS_LOGS_DELETED', $plural) . adm_back_link($this->u_action)); + } + else + { + confirm_box(false, $this->language->lang('CONFIRM_OPERATION'), build_hidden_fields([ + 'f' => $forum_id, + 'start' => $start, + 'del_marked' => $delete_mark, + 'del_all' => $delete_all, + 'mark' => $marked, + 'st' => $sort_days, + 'sk' => $sort_key, + 'sd' => $sort_dir, + ])); + } + } + + $name = $this->functions->get_name(); + $limit = $this->config['aps_actions_per_page']; + + // Sorting + $limit_days = [ + 0 => $this->language->lang('ALL_ENTRIES'), + 1 => $this->language->lang('1_DAY'), + 7 => $this->language->lang('7_DAYS'), + 14 => $this->language->lang('2_WEEKS'), + 30 => $this->language->lang('1_MONTH'), + 90 => $this->language->lang('3_MONTHS'), + 180 => $this->language->lang('6_MONTHS'), + 365 => $this->language->lang('1_YEAR'), + ]; + $sort_by_text = [ + 'a' => $this->language->lang('SORT_ACTION'), + 'ps' => $name, + 'pn' => $this->language->lang('APS_POINTS_NEW', $name), + 'po' => $this->language->lang('APS_POINTS_OLD', $name), + 'uu' => $this->language->lang('SORT_USERNAME'), + 'ru' => ucfirst($this->language->lang('FROM')), + 't' => $this->language->lang('SORT_DATE'), + ]; + $sort_by_sql = [ + 'a' => 'l.log_action', + 'ps' => 'l.points_sum', + 'pn' => 'l.points_new', + 'po' => 'l.points_old', + 'uu' => 'u.username', + 'ru' => 'r.username', + 't' => 'l.log_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); + + // Define where and sort sql for use in displaying logs + $sql_time = ($sort_days) ? (time() - ($sort_days * 86400)) : 0; + $sql_sort = $sort_by_sql[$sort_key] . ' ' . (($sort_dir == 'd') ? 'DESC' : 'ASC'); + + $rowset = $this->log->get(true, $limit, $start, $forum_id, $topic_id, $post_id, $user_id, $reportee_id, $sql_time, $sql_sort, $keywords); + $start = $this->log->get_valid_offset(); + $total = $this->log->get_log_count(); + + $base_url = $this->u_action . "&$u_sort_param$keywords_param"; + $this->pagination->generate_template_pagination($base_url, 'pagination', 'start', $total, $limit, $start); + + foreach ($rowset as $row) + { + $this->template->assign_block_vars('logs', array_change_key_case($row, CASE_UPPER)); + } + + $this->template->assign_vars([ + '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_KEYWORDS' => $keywords, + ]); + } + + /** + * Set custom form action. + * + * @param string $u_action Custom form action + * @return acp_controller $this This controller for chaining calls + * @access public + */ + public function set_page_url($u_action) + { + $this->u_action = $u_action; + + return $this; + } + + /** + * Build a settings array for point names for all installed languages. + * + * @return array $names Array of localised point name settings + * @access protected + */ + protected function build_point_names() + { + $names = []; + + $sql = 'SELECT * FROM ' . LANG_TABLE . ' ORDER BY lang_english_name'; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $name['aps_points_name_' . $row['lang_iso']] = ['lang' =>$row['lang_english_name'], 'validate' => 'string:0:100', 'type' => 'text:0:100']; + + $names = $row['lang_iso'] === $this->config['default_lang'] ? $name + $names : array_merge($names, $name); + } + $this->db->sql_freeresult($result); + + // Only add the 'expand view' if there are multiple language installed + if (count($names) > 1) + { + // Get the first array key + $key = array_keys($names)[0]; + + // The 'expand view' HTML string + $expand = '' . $this->language->lang('EXPAND_VIEW') . ''; + + $names[$key]['append'] = $expand; + } + + return $names; + } + + /** + * Handle the copy action from the settings page. + * + * @return array Array filled with errors + * @access protected + */ + protected function copy_points() + { + $json_response = new \phpbb\json_response; + + add_form_key('aps_points_copy'); + + if ($this->request->is_set_post('submit_copy')) + { + $errors = []; + + $from = $this->request->variable('aps_forums_from', 0); + $to = $this->request->variable('aps_forums_to', [0]); + + if (empty($from) || empty($to)) + { + $errors[] = $this->language->lang('ACP_APS_POINTS_COPY_EMPTY'); + } + + if (!check_form_key('aps_points_copy')) + { + $errors[] = $this->language->lang('FORM_INVALID'); + } + + if ($errors) + { + if ($this->request->is_ajax()) + { + $json_response->send([ + 'MESSAGE_TITLE' => $this->language->lang('ERROR'), + 'MESSAGE_TEXT' => implode('
', $errors), + ]); + } + else + { + return $errors; + } + } + else + { + $this->acp->copy_multiple($from, $to); + + $this->log_phpbb->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_ACP_APS_COPIED', time(), [$this->functions->get_name()]); + + $json_response->send([ + 'MESSAGE_TITLE' => $this->language->lang('INFORMATION'), + 'MESSAGE_TEXT' => $this->language->lang('ACP_APS_POINTS_COPY_SUCCESS', $this->functions->get_name()), + ]); + + trigger_error($this->language->lang('ACP_APS_POINTS_COPY_SUCCESS', $this->functions->get_name()) . adm_back_link($this->u_action)); + } + } + + $this->template->set_filenames(['copy' => '@phpbbstudio_aps/aps_points_copy.html']); + + $this->template->assign_vars([ + 'S_APS_COPY' => true, + 'S_APS_FORUMS' => make_forum_select(), + 'U_APS_ACTION_COPY' => $this->u_action . '&action=copy', + ]); + + if ($this->request->is_ajax()) + { + $json_response->send([ + 'MESSAGE_TITLE' => $this->language->lang('ACP_APS_POINTS_COPY_TITLE', $this->functions->get_name()), + 'MESSAGE_TEXT' => $this->template->assign_display('copy'), + ]); + } + + return []; + } + + /** + * Handles the clean action from the settings page. + * + * @return void + * @access protected + */ + protected function clean_points() + { + if (confirm_box(true)) + { + $this->acp->clean_points(); + + $this->log_phpbb->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_ACP_APS_CLEANED', time(), [$this->functions->get_name()]); + + $json_response = new \phpbb\json_response; + $json_response->send([ + 'MESSAGE_TITLE' => $this->language->lang('INFORMATION'), + 'MESSAGE_TEXT' => $this->language->lang('ACP_APS_POINTS_CLEAN_SUCCESS', $this->functions->get_name()), + ]); + } + else + { + confirm_box(false, $this->language->lang('ACP_APS_POINTS_CLEAN_CONFIRM', $this->functions->get_name()), build_hidden_fields([ + 'action' => 'clean', + ])); + } + } + + /** + * Handles the link locations from the settings page. + * + * @return void + * @access protected + */ + protected function link_locations() + { + $locations = $this->functions->get_link_locations(); + $variables = ['S_APS_LOCATIONS' => true]; + + foreach ($locations as $location => $status) + { + $variables[$location] = (bool) $status; + } + + $this->template->assign_vars($variables); + + if ($this->request->is_set_post('submit_locations')) + { + $links = []; + + foreach (array_keys($locations) as $location) + { + $links[$location] = $this->request->variable((string) $location, false); + } + + $this->functions->set_link_locations($links); + + $this->log_phpbb->add('admin', $this->user->data['user_id'], $this->user->data['user_ip'], 'LOG_ACP_APS_LOCATIONS'); + + trigger_error($this->language->lang('ACP_APS_LOCATIONS_SUCCESS') . adm_back_link($this->u_action)); + } + } +} diff --git a/ext/phpbbstudio/aps/controller/main_controller.php b/ext/phpbbstudio/aps/controller/main_controller.php new file mode 100644 index 0000000..23a1cda --- /dev/null +++ b/ext/phpbbstudio/aps/controller/main_controller.php @@ -0,0 +1,578 @@ +auth = $auth; + $this->blockader = $blockader; + $this->blocks = $blocks; + $this->dispatcher = $dispatcher; + $this->functions = $functions; + $this->helper = $helper; + $this->language = $language; + $this->request = $request; + $this->template = $template; + $this->user = $user; + $this->root_path = $root_path; + $this->php_ext = $php_ext; + + $this->name = $functions->get_name(); + } + + /** + * Display the points page. + * + * @param string $page The page slug + * @param int $pagination The page number for pagination + * @return Response + * @access public + */ + public function display($page, $pagination) + { + $this->language->add_lang('aps_display', 'phpbbstudio/aps'); + + $this->page = $page; + + // Load page blocks + $this->get_page_blocks($pagination); + + // Load admin and user blocks + $this->get_admin_user_blocks(); + + // Check existance + if ($msg = $this->check_existance()) + { + return $msg; + } + + // Check authorisation + if ($msg = $this->check_auth()) + { + return $msg; + } + + // Handle any action + $this->handle_action(); + + foreach ($this->admin_blocks as $slug => $admin_blocks) + { + if (empty($admin_blocks)) + { + continue; + } + + $data = $this->page_blocks[$slug]; + + // Only list pages that the user is authorised to see + if (!isset($data['auth']) || $data['auth']) + { + $this->template->assign_block_vars('aps_navbar', [ + 'TITLE' => $data['title'], + 'S_ACTIVE' => $this->page === $slug, + 'U_VIEW' => $this->helper->route('phpbbstudio_aps_display', ['page' => $slug]), + ]); + } + } + + $called_functions = []; + + $blocks = array_keys($this->page_blocks[$this->page]['blocks']); + $blocks = empty($this->user_blocks[$this->page]) ? $blocks : array_unique(array_merge($this->user_blocks[$this->page], $blocks)); + + foreach ($blocks as $block_id) + { + // If the block is no longer available, remove it from the user blocks + if (empty($this->page_blocks[$this->page]['blocks'][$block_id])) + { + $this->delete_block($block_id); + + continue; + } + + // If it's disabled by the admin, do not display it + if (!in_array($block_id, $this->admin_blocks[$this->page])) + { + continue; + } + + $block = $this->page_blocks[$this->page]['blocks'][$block_id]; + + // Only show blocks that the user is authorised to see + if (!isset($block['auth']) || $block['auth']) + { + if (empty($this->user_blocks[$this->page]) || in_array($block_id, $this->user_blocks[$this->page])) + { + if (!empty($block['function']) && !in_array($block['function'], $called_functions)) + { + $called_functions[] = $this->call_function($block_id, $block); + } + + $this->template->assign_block_vars('aps_blocks', $this->build_display($block_id, $block)); + } + else + { + $this->template->assign_block_vars('aps_blocks_add', ['item' => $this->build_list($block_id, $block)]); + } + } + } + + // Purge temporary variable + unset($called_functions); + + // Output template variables for display + $this->template->assign_vars([ + 'S_APS_DAE_ENABLED' => $this->functions->is_dae_enabled(), + 'S_APS_OVERVIEW' => true, + 'U_APS_ACTION_SORT' => $this->helper->route('phpbbstudio_aps_display', ['page' => $page, 'aps_action' => 'move']), + 'U_MCP' => ($this->auth->acl_get('m_') || $this->auth->acl_getf_global('m_')) ? append_sid("{$this->root_path}mcp.{$this->php_ext}", 'i=-phpbbstudio-aps-mcp-main_module&mode=front', true, $this->user->session_id) : '', + ]); + + // Breadcrumbs + $this->template->assign_block_vars_array('navlinks', [ + [ + 'FORUM_NAME' => $this->name, + 'U_VIEW_FORUM' => $this->helper->route('phpbbstudio_aps_display'), + ], + [ + 'FORUM_NAME' => $this->language->lang('APS_OVERVIEW'), + 'U_VIEW_FORUM' => $this->helper->route('phpbbstudio_aps_display'), + ], + ]); + + // Output the page! + return $this->helper->render('@phpbbstudio_aps/aps_display.html', $this->page_blocks[$this->page]['title']); + } + + /** + * Build template variables array for a given block. + * + * @param string $block_id The block identifier + * @param array $block The block data + * @return array The block template variables + * @access protected + */ + protected function build_display($block_id, array $block) + { + return [ + 'ID' => $block_id, + 'TITLE' => $block['title'], + 'TEMPLATE' => $block['template'], + 'S_REQUIRED' => !empty($block['required']), + 'U_DELETE' => $this->helper->route('phpbbstudio_aps_display', ['page' => $this->page, 'aps_action' => 'delete', 'id' => $block_id]), + ]; + } + + /** + * Build template list item for a given block. + * + * @param string $block_id The block identifier + * @param array $block The block data + * @return string The block template list item + * @access protected + */ + protected function build_list($block_id, array $block) + { + $u_add = $this->helper->route('phpbbstudio_aps_display', ['page' => $this->page, 'aps_action' => 'add', 'id' => $block_id]); + + return '
  • ' . $block['title'] . '
  • '; + } + + /** + * Call a function defined in the block data. + * + * @param string $block_id The block identifier + * @param array $block The block data + * @return mixed The block function declaration + * @access protected + */ + protected function call_function($block_id, array $block) + { + // Set up function parameters and append the block id + $params = !empty($block['params']) ? $block['params'] : []; + $params = array_merge($params, [ + 'block_id' => $block_id + ]); + + // Call the function + call_user_func_array($block['function'], $params); + + return $block['function']; + } + + /** + * Check if the current user is authorised to see this display page. + * + * @return string|Response + * @access protected + */ + protected function check_auth() + { + if (isset($this->page_blocks[$this->page]['auth']) && !$this->page_blocks[$this->page]['auth']) + { + $message = $this->language->lang('NOT_AUTHORISED'); + $back_link = '' . $this->language->lang('APS_OVERVIEW') . ''; + $back_msg = $this->language->lang('RETURN_TO', $back_link); + + return $this->helper->message($message . '

    ' . $back_msg, [], 'INFORMATION', 401); + } + + return ''; + } + + /** + * Check if the current page is available. + * + * @return string|Response + * @access protected + */ + protected function check_existance() + { + if (empty($this->page_blocks[$this->page])) + { + $message = $this->language->lang('PAGE_NOT_FOUND'); + $back_link = '' . $this->language->lang('APS_OVERVIEW') . ''; + $back_msg = $this->language->lang('RETURN_TO', $back_link);; + + return $this->helper->message($message . '

    ' . $back_msg, [], 'INFORMATION', 404); + } + + return ''; + } + + /** + * Handle actions for the display blocks. + * + * @return void + * @access protected + */ + protected function handle_action() + { + // Request the action + $action = $this->request->variable('aps_action', '', true); + + // Only these actions are available + if (!in_array($action, ['add', 'delete', 'move'])) + { + return; + } + + // Request the block identifier + $block_id = $this->request->variable('id', '', true); + + // Call the action's function + $response = $this->{$action . '_block'}($block_id); + + // If the request is AJAX, send a response + if ($this->request->is_ajax()) + { + $json_response = new \phpbb\json_response; + $json_response->send([ + 'success' => $response, + 'APS_TITLE' => $this->language->lang('APS_SUCCESS'), + 'APS_TEXT' => $this->language->lang('APS_POINTS_BLOCK_' . utf8_strtoupper($action), $this->name), + ]); + } + + // Otherwise assign a meta refresh + $this->helper->assign_meta_refresh_var(0, $this->helper->route('phpbbstudio_aps_display', ['page' => $this->page])); + } + + /** + * Get the admin and user desired page blocks. + * + * @return void + * @access protected + */ + protected function get_admin_user_blocks() + { + $rowset = $this->blockader->rowset($this->user->data['user_id']); + + foreach ($rowset as $user_id => $blocks) + { + if ($user_id == $this->blockader->get_admin_id()) + { + $this->admin_blocks = $blocks; + } + + if ($user_id == $this->user->data['user_id']) + { + $this->user_blocks = $blocks; + } + } + + if (empty($this->admin_blocks)) + { + foreach ($this->page_blocks as $page => $data) + { + $this->admin_blocks[$page] = array_keys($data['blocks']); + } + } + } + + /** + * Get all available display blocks. + * + * @param int $pagination Pagination page number + * @return array The display blocks + * @access public + */ + public function get_page_blocks($pagination = 1) + { + $page_blocks = [ + 'overview' => [ + 'title' => $this->language->lang('APS_OVERVIEW'), + 'blocks' => [ + 'points_top' => [ + 'title' => $this->language->lang('APS_TOP_USERS'), + 'function' => [$this->blocks, 'user_top_search'], + 'template' => '@phpbbstudio_aps/blocks/points_top.html', + ], + 'points_search' => [ + 'title' => $this->language->lang('FIND_USERNAME'), + 'function' => [$this->blocks, 'user_top_search'], + 'template' => '@phpbbstudio_aps/blocks/points_search.html', + ], + 'points_settings' => [ + 'title' => $this->language->lang('SETTINGS'), + 'template' => '@phpbbstudio_aps/blocks/points_settings.html', + ], + 'points_random' => [ + 'title' => $this->language->lang('APS_RANDOM_USER'), + 'function' => [$this->blocks, 'user_random'], + 'template' => '@phpbbstudio_aps/blocks/points_random.html', + ], + 'points_forums' => [ + 'title' => $this->language->lang('APS_POINTS_PER_FORUM', $this->name), + 'function' => [$this->blocks, 'charts_forum'], + 'template' => '@phpbbstudio_aps/blocks/points_forums.html', + ], + 'points_groups' => [ + 'title' => $this->language->lang('APS_POINTS_PER_GROUP', $this->name), + 'function' => [$this->blocks, 'charts_group'], + 'template' => '@phpbbstudio_aps/blocks/points_groups.html', + ], + 'points_growth' => [ + 'title' => $this->language->lang('APS_POINTS_GROWTH', $this->name), + 'function' => [$this->blocks, 'charts_period'], + 'template' => '@phpbbstudio_aps/blocks/points_growth.html', + ], + 'points_trade_off' => [ + 'title' => $this->language->lang('APS_POINTS_TRADE_OFF', $this->name), + 'function' => [$this->blocks, 'charts_period'], + 'template' => '@phpbbstudio_aps/blocks/points_trade_off.html', + ], + ], + ], + 'actions' => [ + 'title' => $this->language->lang('APS_POINTS_ACTIONS', $this->name), + 'auth' => $this->auth->acl_get('u_aps_view_logs'), + 'blocks' => [ + 'points_actions' => [ + 'auth' => $this->auth->acl_get('u_aps_view_logs'), + 'title' => $this->language->lang('APS_POINTS_ACTIONS', $this->name), + 'required' => true, + 'function' => [$this->blocks, 'display_actions'], + 'params' => ['pagination' => $pagination], + 'template' => '@phpbbstudio_aps/blocks/points_actions.html', + ], + 'points_registration' => [ + 'auth' => $this->auth->acl_get('u_aps_view_logs'), + 'title' => $this->language->lang('APS_RECENT_ADJUSTMENTS'), + 'function' => [$this->blocks, 'recent_adjustments'], + 'template' => '@phpbbstudio_aps/blocks/points_adjustments.html', + ], + ], + ], + ]; + + /** + * Event to add additional page blocks to the APS display page. + * + * @event phpbbstudio.aps.display_blocks + * @var array page_blocks Available page blocks + * @var int pagination Pagination's page number + * @since 1.0.0 + */ + $vars = ['page_blocks', 'pagination']; + extract($this->dispatcher->trigger_event('phpbbstudio.aps.display_blocks', compact($vars))); + + $this->page_blocks = $page_blocks; + + return $this->page_blocks; + } + + /** + * Add a block to the user desired blocks. + * + * @param string $block_id The block identifier + * @return string The rendered block for display + * @access protected + */ + protected function add_block($block_id) + { + $insert = empty($this->user_blocks); + + $this->user_blocks[$this->page] = !$insert ? array_merge($this->user_blocks[$this->page], [$block_id]) : [$block_id]; + + $this->blockader->set_blocks($this->user->data['user_id'], $this->user_blocks, $insert); + + $this->template->set_filenames(['block' => '@phpbbstudio_aps/blocks/base.html']); + + if (!empty($this->page_blocks[$this->page]['blocks'][$block_id]['function'])) + { + $this->call_function($block_id, $this->page_blocks[$this->page]['blocks'][$block_id]); + } + + $this->template->assign_vars([ + 'block' => $this->build_display($block_id, $this->page_blocks[$this->page]['blocks'][$block_id]), + 'S_USER_LOGGED_IN' => $this->user->data['user_id'] != ANONYMOUS, + 'S_IS_BOT' => $this->user->data['is_bot'], + ]); + + return $this->template->assign_display('block'); + } + + /** + * Delete a block from the user desired blocks. + * + * @param string $block_id The block identifier + * @return string HTML list item + * @access protected + */ + protected function delete_block($block_id) + { + $insert = empty($this->user_blocks); + + if ($insert) + { + foreach ($this->page_blocks as $page => $data) + { + $this->user_blocks[$page] = array_keys($data['blocks']); + } + } + + if (($key = array_search($block_id, $this->user_blocks[$this->page])) !== false) { + unset($this->user_blocks[$this->page][$key]); + } + + $this->blockader->set_blocks($this->user->data['user_id'], $this->user_blocks, $insert); + + return $this->build_list($block_id, $this->page_blocks[$this->page]['blocks'][$block_id]); + } + + /** + * Move (order) the user desired blocks. + * + * @return bool|int Boolean on update or integer on insert. + * @access protected + */ + protected function move_block() + { + $insert = empty($this->user_blocks); + + $order = $this->request->variable('order', ['']); + + // Filter out empty block identifiers + $this->user_blocks[$this->page] = array_filter($order); + + return $this->blockader->set_blocks($this->user->data['user_id'], $this->user_blocks, $insert); + } +} diff --git a/ext/phpbbstudio/aps/controller/mcp_controller.php b/ext/phpbbstudio/aps/controller/mcp_controller.php new file mode 100644 index 0000000..11579ec --- /dev/null +++ b/ext/phpbbstudio/aps/controller/mcp_controller.php @@ -0,0 +1,617 @@ +auth = $auth; + $this->config = $config; + $this->db = $db; + $this->dispatcher = $dispatcher; + $this->distributor = $distributor; + $this->functions = $functions; + $this->group_helper = $group_helper; + $this->language = $language; + $this->log = $log; + $this->notification = $notification; + $this->pagination = $pagination; + $this->reasoner = $reasoner; + $this->request = $request; + $this->template = $template; + $this->user = $user; + $this->valuator = $valuator; + + $this->root_path = $root_path; + $this->php_ext = $php_ext; + + $this->name = $functions->get_name(); + } + + /** + * Handle MCP front page. + * + * @return void + * @access public + */ + public function front() + { + if ($this->auth->acl_get('u_aps_view_logs')) + { + $this->log->load_lang(); + + // Latest 5 logs + $logs = $this->log->get(false, 5); + foreach ($logs as $row) + { + $this->template->assign_block_vars('logs', array_change_key_case($row, CASE_UPPER)); + } + + // Latest 5 adjustments + $moderated = $this->log->get(false, 5, 0, '', 0, 0, 0, 0, 0, 'l.log_time DESC', 'APS_POINTS_USER_ADJUSTED'); + foreach ($moderated as $row) + { + $this->template->assign_block_vars('moderated', array_change_key_case($row, CASE_UPPER)); + } + } + + // Top 5 users + $sql = 'SELECT user_id, username, user_colour, user_points + FROM ' . $this->functions->table('users') . ' + WHERE user_type <> ' . USER_IGNORE . ' + ORDER BY user_points DESC, username_clean ASC'; + $result = $this->db->sql_query_limit($sql, 5); + while ($row = $this->db->sql_fetchrow($result)) + { + $this->template->assign_block_vars('aps_users_top', [ + 'NAME' => get_username_string('full', $row['user_id'], $row['username'], $row['user_colour']), + 'POINTS' => $row['user_points'], + ]); + } + $this->db->sql_freeresult($result); + + // Bottom 5 user + $sql = 'SELECT user_id, username, user_colour, user_points + FROM ' . $this->functions->table('users') . ' + WHERE user_type <> ' . USER_IGNORE . ' + ORDER BY user_points ASC, username_clean DESC'; + $result = $this->db->sql_query_limit($sql, 5); + while ($row = $this->db->sql_fetchrow($result)) + { + $this->template->assign_block_vars('aps_users_bottom', [ + 'NAME' => get_username_string('full', $row['user_id'], $row['username'], $row['user_colour']), + 'POINTS' => $row['user_points'], + ]); + } + $this->db->sql_freeresult($result); + + /** + * Event to assign additional variables for the APS MCP front page. + * + * @event phpbbstudio.aps.mcp_front + * @since 1.0.0 + */ + $this->dispatcher->dispatch('phpbbstudio.aps.mcp_front'); + + $this->template->assign_vars([ + 'S_APS_LOGS' => $this->auth->acl_get('u_aps_view_logs'), + ]); + } + + /** + * Handle MCP logs. + * + * @return void + * @access public + */ + public function logs() + { + $this->log->load_lang(); + + // Set up general vars + $start = $this->request->variable('start', 0); + $forum_id = $this->request->variable('f', ''); + $topic_id = $this->request->variable('t', 0); + $post_id = $this->request->variable('p', 0); + $user_id = $this->request->variable('u', 0); + $reportee_id = $this->request->variable('r', 0); + + // Sort keys + $sort_days = $this->request->variable('st', 0); + $sort_key = $this->request->variable('sk', 't'); + $sort_dir = $this->request->variable('sd', 'd'); + + // Keywords + $keywords = $this->request->variable('keywords', '', true); + $keywords_param = !empty($keywords) ? '&keywords=' . urlencode(htmlspecialchars_decode($keywords)) : ''; + + $name = $this->functions->get_name(); + $limit = $this->config['aps_actions_per_page']; + + // Sorting + $limit_days = [ + 0 => $this->language->lang('ALL_ENTRIES'), + 1 => $this->language->lang('1_DAY'), + 7 => $this->language->lang('7_DAYS'), + 14 => $this->language->lang('2_WEEKS'), + 30 => $this->language->lang('1_MONTH'), + 90 => $this->language->lang('3_MONTHS'), + 180 => $this->language->lang('6_MONTHS'), + 365 => $this->language->lang('1_YEAR'), + ]; + $sort_by_text = [ + 'a' => $this->language->lang('SORT_ACTION'), + 'ps' => $name, + 'pn' => $this->language->lang('APS_POINTS_NEW', $name), + 'po' => $this->language->lang('APS_POINTS_OLD', $name), + 'uu' => $this->language->lang('SORT_USERNAME'), + 'ru' => $this->language->lang('FROM'), + 't' => $this->language->lang('SORT_DATE'), + ]; + $sort_by_sql = [ + 'a' => 'l.log_action', + 'ps' => 'l.points_sum', + 'pn' => 'l.points_new', + 'po' => 'l.points_old', + 'uu' => 'u.username', + 'ru' => 'r.username', + 't' => 'l.log_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); + + // Define where and sort sql for use in displaying logs + $sql_time = ($sort_days) ? (time() - ($sort_days * 86400)) : 0; + $sql_sort = $sort_by_sql[$sort_key] . ' ' . (($sort_dir == 'd') ? 'DESC' : 'ASC'); + + $rowset = $this->log->get(true, $limit, $start, $forum_id, $topic_id, $post_id, $user_id, $reportee_id, $sql_time, $sql_sort, $keywords); + $start = $this->log->get_valid_offset(); + $total = $this->log->get_log_count(); + + $base_url = $this->u_action . "&$u_sort_param$keywords_param"; + $this->pagination->generate_template_pagination($base_url, 'pagination', 'start', $total, $limit, $start); + + foreach ($rowset as $row) + { + $this->template->assign_block_vars('logs', array_change_key_case($row, CASE_UPPER)); + } + + $this->template->assign_vars([ + '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_KEYWORDS' => $keywords, + ]); + } + + /** + * Handle MCP user adjustment. + * + * @return void + * @access public + */ + public function change() + { + $this->log->load_lang(); + + $group_id = $this->request->variable('g', 0); + $user_id = $this->request->variable('u', 0); + + if (empty($group_id) && empty($user_id)) + { + $this->find_user(); + + return; + } + + $this->language->add_lang('acp/common'); + + $action = $this->request->variable('action', ''); + + switch ($action) + { + case 'add': + case 'sub': + case 'set': + if (!$this->auth->acl_get('m_aps_adjust_custom')) + { + trigger_error($this->language->lang('NOT_AUTHORISED'), E_USER_WARNING); + } + break; + + case '': + // do nothing + break; + + default: + if (!$this->auth->acl_get('m_aps_adjust_reason')) + { + trigger_error($this->language->lang('NOT_AUTHORISED'), E_USER_WARNING); + } + break; + } + + if (!empty($user_id)) + { + $sql = 'SELECT user_id, username, user_colour + FROM ' . $this->functions->table('users') . ' + WHERE user_type <> ' . USER_IGNORE . ' + AND user_id = ' . (int) $user_id; + $result = $this->db->sql_query_limit($sql, 1); + $user = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if ($user === false) + { + trigger_error($this->language->lang('NO_USER') . $this->back_link($this->u_action), E_USER_WARNING); + } + + $user_ids = [$user_id]; + + $u_action = '&u=' . (int) $user_id; + } + else + { + $sql = 'SELECT user_id + FROM ' . $this->functions->table('user_group') . ' + WHERE group_id = ' . (int) $group_id; + $result = $this->db->sql_query($sql); + $rowset = $this->db->sql_fetchrowset($result); + $this->db->sql_freeresult($result); + + if (empty($rowset)) + { + $this->language->add_lang('acp/groups'); + + trigger_error($this->language->lang('GROUPS_NO_MEMBERS') . $this->back_link($this->u_action), E_USER_WARNING); + } + + $user_ids = array_column($rowset, 'user_id'); + + $u_action = '&g=' . (int) $group_id; + } + + + // Actions + $reasons = $this->reasoner->rowset(); + $actions = [ + 'add' => $this->language->lang('ADD'), + 'sub' => $this->language->lang('REMOVE'), + 'set' => $this->language->lang('CHANGE'), + ]; + + $users = $this->valuator->users($user_ids); + + $submit = $this->request->is_set_post('submit'); + $points = $this->request->variable('points', 0.00); + $reason = $this->request->variable('reason', '', true); + + if ($submit) + { + if ($action === '') + { + trigger_error($this->language->lang('NO_ACTION'), E_USER_WARNING); + } + + if (confirm_box(true)) + { + foreach ($users as $uid => $user_points) + { + switch ($action) + { + case 'add': + $sum_points = $points; + break; + case 'sub': + $sum_points = $this->functions->equate_points(0, $points, '-'); + break; + case 'set': + $sum_points = $this->functions->equate_points($points, $user_points, '-'); + break; + + default: + if (empty($reasons[$action])) + { + trigger_error($this->language->lang('NO_ACTION') . $this->back_link($this->u_action), E_USER_WARNING); + } + + $sum_points = $reasons[$action]['reason_points']; + $reason = $reasons[$action]['reason_title'] . '
    ' . $reasons[$action]['reason_desc']; + break; + } + + $log_entry = []; + + $log_entry[] = [ + 'action' => 'APS_POINTS_USER_ADJUSTED', + 'actions' => !empty($reason) ? [$reason => $sum_points] : ['APS_POINTS_USER_ADJUSTED' => $sum_points], + 'user_id' => (int) $uid, + 'reportee_id' => (int) $this->user->data['user_id'], + 'reportee_ip' => (string) $this->user->ip, + 'points_old' => $user_points, + 'points_sum' => $sum_points, + ]; + + $this->distributor->distribute($uid, $sum_points, $log_entry, $user_points); + } + + $this->config->increment('aps_notification_id', 1); + + $this->notification->add_notifications('phpbbstudio.aps.notification.type.adjust', [ + 'name' => $this->functions->get_name(), + 'reason' => $reason, + 'user_ids' => array_keys($users), + 'moderator' => get_username_string('no_profile', $this->user->data['user_id'], $this->user->data['username'], $this->user->data['user_colour']), + 'moderator_id' => (int) $this->user->data['user_id'], + 'notification_id' => (int) $this->config['aps_notification_id'], + ]); + + trigger_error($this->language->lang('MCP_APS_POINTS_USER_CHANGE_SUCCESS', $this->name) . $this->back_link($this->u_action)); + } + else + { + confirm_box(false, $this->language->lang('MCP_APS_POINTS_USER_CHANGE', $this->name), build_hidden_fields([ + 'submit' => $submit, + 'action' => $action, + 'points' => $points, + 'reason' => $reason, + ])); + + redirect($this->u_action); + } + } + + if (!empty($user_id) && $this->auth->acl_get('u_aps_view_logs')) + { + $logs = $this->log->get(false, 5, 0, '', 0, 0, (int) $user_id); + + foreach ($logs as $row) + { + $this->template->assign_block_vars('logs', array_change_key_case($row, CASE_UPPER)); + } + + $this->template->assign_var('S_APS_LOGS', true); + } + + if (!empty($group_id)) + { + $sql = 'SELECT group_id, group_name, group_colour + FROM ' . $this->functions->table('groups') . ' + WHERE group_id = ' . (int) $group_id; + $result = $this->db->sql_query_limit($sql, 1); + $group = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $group_name = $this->group_helper->get_name_string('full', $group['group_id'], $group['group_name'], $group['group_colour']); + } + + $this->template->assign_vars([ + 'APS_ACTIONS' => $actions, + 'APS_REASONS' => $reasons, + 'APS_POINTS' => $user_id ? $this->functions->display_points($users[$user_id]) : '', + 'APS_USERNAME' => !empty($user) ? get_username_string('full', $user['user_id'], $user['username'], $user['user_colour']) : '', + 'APS_GROUP' => !empty($group_name) ? $group_name : '', + + 'S_APS_CUSTOM' => $this->auth->acl_get('m_aps_adjust_custom'), + 'S_APS_REASON' => $this->auth->acl_get('m_aps_adjust_reason'), + 'S_APS_POINTS' => true, + + 'U_APS_ACTION' => $this->u_action . $u_action, + ]); + } + + /** + * Find a user for the MCP adjustment page. + * + * @return void + * @access protected + */ + protected function find_user() + { + $this->language->add_lang('acp/groups'); + + $form_name = 'mcp_aps_change'; + add_form_key($form_name); + + $submit_group = $this->request->is_set_post('submit_group'); + $submit_user = $this->request->is_set_post('submit_user'); + $submit = $submit_group || $submit_user; + + $group_id = $this->request->variable('group_id', 0); + + if ($submit && !check_form_key($form_name)) + { + $error = 'FORM_INVALID'; + } + else if ($submit) + { + if ($submit_group) + { + redirect($this->u_action . '&g=' . (int) $group_id); + } + + if ($submit_user) + { + if (!function_exists('user_get_id_name')) + { + /** @noinspection PhpIncludeInspection */ + include $this->root_path . 'includes/functions_user.' . $this->php_ext; + } + + $username[] = $this->request->variable('username', '', true); + + $error = user_get_id_name($user_ids, $username); + + if (empty($error)) + { + $user_id = $user_ids[0]; + + redirect($this->u_action . '&u=' . (int) $user_id); + } + } + } + + if (!function_exists('group_select_options')) + { + /** @noinspection PhpIncludeInspection */ + include $this->root_path . 'includes/functions_admin.' . $this->php_ext; + } + + $this->template->assign_vars([ + 'S_ERROR' => !empty($error), + 'ERROR_MSG' => !empty($error) ? $this->language->lang($error) : '', + + 'APS_USERNAME' => !empty($username[0]) ? $username[0] : '', + 'APS_GROUPS' => group_select_options($group_id), + + 'S_APS_SEARCH' => true, + + 'U_APS_ACTION' => $this->u_action, + 'U_APS_SEARCH' => append_sid("{$this->root_path}memberlist.{$this->php_ext}", 'mode=searchuser&form=mcp_aps_change&field=username'), + ]); + } + + /** + * Generate a back link for this MCP controller. + * + * @param string $action The action to return to + * @return string A HTML formatted URL to the action + * @access protected + */ + protected function back_link($action) + { + return '

    « ' . $this->language->lang('BACK_TO_PREV') . ''; + } + + /** + * Set custom form action. + * + * @param string $u_action Custom form action + * @return mcp_controller $this This controller for chaining calls + * @access public + */ + public function set_page_url($u_action) + { + $this->u_action = $u_action; + + return $this; + } +} diff --git a/ext/phpbbstudio/aps/core/acp.php b/ext/phpbbstudio/aps/core/acp.php new file mode 100644 index 0000000..035c2f5 --- /dev/null +++ b/ext/phpbbstudio/aps/core/acp.php @@ -0,0 +1,236 @@ + [], 1 => []]; + + /** + * Constructor. + * + * @param \phpbbstudio\aps\core\functions $functions APS Core functions + * @param \phpbb\template\template $template Template object + * @param \phpbbstudio\aps\actions\type\action[] $types Array of action types from the service collection + * @param \phpbbstudio\aps\points\valuator $valuator APS Valuator object + * @return void + * @access public + */ + public function __construct( + functions $functions, + \phpbb\template\template $template, + $types, + \phpbbstudio\aps\points\valuator $valuator + ) + { + $this->functions = $functions; + $this->template = $template; + $this->types = $types; + $this->valuator = $valuator; + } + + /** + * Returns the list of fields for the points list. + * + * @return array Array of action types from the service collection + * @access public + */ + public function get_fields() + { + return $this->fields; + } + + /** + * Initiate a build for a points list for the in the ACP. + * + * @param int|null $forum_id Forum identifier + * @param string $block_name The name for the template block + * @return void + * @access public + */ + public function build($forum_id = null, $block_name = 'aps_categories') + { + $this->build_list(is_null($forum_id)); + + $this->assign_blocks($block_name); + + $this->assign_values($forum_id); + } + + /** + * Build a local|global points list for in the ACP. + * + * @param bool $global Whether we are building a global or local list + * @return void + * @access public + */ + public function build_list($global) + { + /** @var \phpbbstudio\aps\actions\type\action $type */ + foreach ($this->types as $type) + { + if ($type->is_global() === $global) + { + $this->fields[(int) $type->is_global()] = array_merge($this->fields[(int) $type->is_global()], $type->get_fields()); + + if (empty($this->blocks[$type->get_category()])) + { + $this->blocks[$type->get_category()] = [ + $type->get_action() => $type->get_data(), + ]; + } + else + { + if (empty($this->blocks[$type->get_category()][$type->get_action()])) + { + $this->blocks[$type->get_category()][$type->get_action()] = $type->get_data(); + } + else + { + $this->blocks[$type->get_category()][$type->get_action()] += $type->get_data(); + } + } + } + } + } + + /** + * Assign the points list to the template. + * + * @param string $block_name The name for the template block + * @return void + * @access public + */ + public function assign_blocks($block_name = 'aps_categories') + { + foreach ($this->blocks as $category => $blocks) + { + $this->template->assign_block_vars($block_name, [ + 'title' => $category, + 'blocks' => $blocks, + ]); + } + } + + /** + * Assign the point values to the template. + * + * @param int $forum_id The forum identifier + * @return array The point values + * @access public + */ + public function assign_values($forum_id) + { + $values = $this->valuator->get_points($this->fields, (int) $forum_id); + $values = $values[(int) $forum_id]; + + // Clean upon assignment, as this possible runs more often than submission + $this->valuator->clean_points(array_keys($values), (int) $forum_id); + + $this->template->assign_vars([ + 'APS_VALUES' => $values, + ]); + + return $values; + } + + /** + * Sets the point values for a given forum identifier. + * + * @param array $points The points to set + * @param int $forum_id The forum identifier + * @return void + * @access public + */ + public function set_points(array $points, $forum_id = 0) + { + $this->valuator->set_points($points, (int) $forum_id); + } + + /** + * Delete the point values in the database for a specific forum. + * + * @param int $forum_id The forum identifier + * @return void + * @access public + */ + public function delete_points($forum_id) + { + $this->valuator->delete_points($forum_id); + } + + /** + * Copy the point values from one forum to an other. + * + * @param int $from The from forum identifier + * @param int $to The to forum identifier + * @param array $points The point values to copy + * @return void + * @access public + */ + public function copy_points($from, $to, array $points) + { + $points = [0 => array_keys($points)]; + $points = $this->valuator->get_points($points, (int) $from); + $points = $points[(int) $from]; + + $this->valuator->set_points($points, $to); + } + + /** + * Copy the point values from one forum to multiple others. + * + * @param int $from The from forum identifier + * @param int $to The to forum identifier + * @return void + * @access public + */ + public function copy_multiple($from, $to) + { + $this->valuator->copy_points($from, $to); + } + + /** + * Clean the points table. + * + * @return void + * @access public + */ + public function clean_points() + { + /** @var \phpbbstudio\aps\actions\type\action $type */ + foreach ($this->types as $type) + { + $this->fields[(int) $type->is_global()] = array_merge($this->fields[(int) $type->is_global()], $type->get_fields()); + } + + $this->valuator->clean_all_points($this->fields); + } +} diff --git a/ext/phpbbstudio/aps/core/blocks.php b/ext/phpbbstudio/aps/core/blocks.php new file mode 100644 index 0000000..b009712 --- /dev/null +++ b/ext/phpbbstudio/aps/core/blocks.php @@ -0,0 +1,647 @@ +auth = $auth; + $this->config = $config; + $this->db = $db; + $this->dbal = $dbal; + $this->functions = $functions; + $this->group_helper = $group_helper; + $this->helper = $helper; + $this->language = $language; + $this->log = $log; + $this->pagination = $pagination; + $this->request = $request; + $this->template = $template; + $this->user = $user; + + $this->root_path = $root_path; + $this->php_ext = $php_ext; + $this->table = $table; + + $this->name = $functions->get_name(); + + $log->load_lang(); + } + + /** + * Display the "Top users" and "Find a Member" blocks. + * + * @param string $block_id The block identifier + * @return void + * @access public + */ + public function user_top_search($block_id) + { + $submit = $this->request->is_set_post('submit'); + $action = $this->request->variable('action', '', true); + + $count = $this->request->variable('aps_user_top_count', (int) $this->config['aps_display_top_count']); + $top_username = ''; + + $sql = 'SELECT user_id, username, username_clean, user_colour, user_points, + user_avatar, user_avatar_type, user_avatar_width, user_avatar_height + FROM ' . $this->functions->table('users') . ' + WHERE user_type <> ' . USER_IGNORE . ' + ORDER BY user_points DESC, username_clean ASC'; + $result = $this->db->sql_query_limit($sql, $count); + while ($row = $this->db->sql_fetchrow($result)) + { + $top_username = empty($top_username) ? $row['username_clean'] : $top_username; + + $this->template->assign_block_vars('top_users', [ + 'NAME' => get_username_string('full', $row['user_id'], $row['username'], $row['user_colour']), + 'AVATAR' => phpbb_get_user_avatar($row), + 'POINTS' => $row['user_points'], + 'U_ADJUST' => append_sid("{$this->root_path}mcp.{$this->php_ext}", 'i=-phpbbstudio-aps-mcp-main_module&mode=change&u=' . (int) $row['user_id'], true, $this->user->session_id) + ]); + } + $this->db->sql_freeresult($result); + + // Set up the default user to display, either this user or the top user if this user is a guest + $default = $this->user->data['user_id'] != ANONYMOUS ? $this->user->data['username'] : $top_username; + $username = $this->request->variable('aps_user_search', $default, true); + + // Find the user for the provided username + $user = $this->find_user($username); + + // If a user was found + if ($user !== false) + { + // Count the amount of users with more points than this user. + $sql = 'SELECT COUNT(user_points) as rank + FROM ' . $this->functions->table('users') . ' + WHERE user_points > ' . $user['user_points']; + $result = $this->db->sql_query_limit($sql, 1); + $user_rank = (int) $this->db->sql_fetchfield('rank'); + $this->db->sql_freeresult($result); + + // Increment by one, as the rank is the amount of users above this user + $user_rank++; + } + + // Output the template variables for display + $this->template->assign_vars([ + // Set up a default no avatar + 'APS_NO_AVATAR' => $this->functions->get_no_avatar(), + + // The searched user data + 'APS_SEARCH_USERNAME' => $username, + 'APS_SEARCH_USER_AVATAR' => !empty($user) ? phpbb_get_user_avatar($user) : '', + 'APS_SEARCH_USER_FULL' => !empty($user) ? get_username_string('full', $user['user_id'], $user['username'], $user['user_colour']) : $this->language->lang('NO_USER'), + 'APS_SEARCH_USER_POINTS' => !empty($user) ? $user['user_points'] : 0.00, + 'APS_SEARCH_USER_RANK' => !empty($user_rank) ? $user_rank : $this->language->lang('NA'), + 'U_APS_SEARCH_USER_ADJUST' => !empty($user) ? append_sid("{$this->root_path}mcp.{$this->php_ext}", 'i=-phpbbstudio-aps-mcp-main_module&mode=change&u=' . (int) $user['user_id'], true, $this->user->session_id) : '', + + // Amount of top users to display + 'APS_TOP_USERS_COUNT' => $count, + + // APS Moderator + 'S_APS_USER_ADJUST' => $this->auth->acl_get('m_aps_adjust_custom') || $this->auth->acl_get('m_aps_'), + + // Block actions + 'U_APS_ACTION_SEARCH' => $this->helper->route('phpbbstudio_aps_display', ['page' => 'overview', 'action' => 'search']), + 'U_APS_ACTION_TOP' => $this->helper->route('phpbbstudio_aps_display', ['page' => 'overview', 'action' => 'top']), + ]); + + // Handle any AJAX actions regarding these blocks + if ($submit && $this->request->is_ajax() && in_array($action, ['search', 'top'])) + { + $this->template->set_filenames(['aps_body' => '@phpbbstudio_aps/blocks/base.html']); + $this->template->assign_vars([ + 'block' => [ + 'ID' => $block_id, + 'TITLE' => $action === 'top' ? $this->language->lang('APS_TOP_USERS') : $this->language->lang('FIND_USERNAME'), + 'TEMPLATE' => '@phpbbstudio_aps/blocks/points_' . $action . '.html', + ], + ]); + + $json_response = new \phpbb\json_response; + $json_response->send([ + 'body' => $this->template->assign_display('aps_body'), + ]); + } + } + + /** + * Display the "Random member" block. + * + * @return void + * @access public + */ + public function user_random() + { + $sql = 'SELECT user_id, username, user_colour, user_points, + user_avatar, user_avatar_type, user_avatar_width, user_avatar_height + FROM ' . $this->functions->table('users') . ' + WHERE user_type <> ' . USER_IGNORE . ' + AND user_type <> ' . USER_INACTIVE . ' + ORDER BY ' . $this->dbal->random(); + + $result = $this->db->sql_query_limit($sql, 1); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $this->template->assign_vars([ + 'APS_RANDOM_NO_AVATAR' => $this->functions->get_no_avatar(), + + 'APS_RANDOM_USER_AVATAR' => phpbb_get_user_avatar($row), + 'APS_RANDOM_USER_FULL' => get_username_string('full', $row['user_id'], $row['username'], $row['user_colour']), + 'APS_RANDOM_USER_POINTS' => $row['user_points'], + ]); + } + + /** + * Display the "Points actions" block. + * + * @param int $pagination The pagination's page number + * @param string $block_id The block identifier + * @return void + * @access public + */ + public function display_actions($pagination, $block_id) + { + $params = ['page' => 'actions']; + $limit = $this->config['aps_actions_per_page']; + + // Set up general vars + $s_reportee = $this->auth->acl_get('u_aps_view_mod'); + $s_username = $this->auth->acl_get('u_aps_view_logs_other'); + + $forum_id = $this->request->variable('f', ''); + $topic_title = $this->request->variable('t', '', true); + $username = $this->request->variable('u', '', true); + $reportee = $this->request->variable('r', '', true); + + $username = $s_username ? $username : ''; + $reportee = $s_reportee ? $reportee : ''; + + $topic_ids = $this->find_topic($topic_title); + $user_id = $this->find_user($username, false); + $reportee_id = $this->find_user($reportee, false); + + $post_id = 0; + $user_id = $s_username ? $user_id : (int) $this->user->data['user_id']; + + // Sort keys + $sort_days = $this->request->variable('st', 0); + $sort_key = $this->request->variable('sk', 't'); + $sort_dir = $this->request->variable('sd', 'd'); + + // Keywords + $keywords = $this->request->variable('keywords', '', true); + if (!empty($keywords)) + { + $params['keywords'] = urlencode(htmlspecialchars_decode($keywords)); + } + + // Calculate the start (SQL offset) from the page number + $start = ($pagination - 1) * $limit; + + // Sorting + $limit_days = [ + 0 => $this->language->lang('APS_POINTS_ACTIONS_ALL', $this->name), + 1 => $this->language->lang('1_DAY'), + 7 => $this->language->lang('7_DAYS'), + 14 => $this->language->lang('2_WEEKS'), + 30 => $this->language->lang('1_MONTH'), + 90 => $this->language->lang('3_MONTHS'), + 180 => $this->language->lang('6_MONTHS'), + 365 => $this->language->lang('1_YEAR'), + ]; + $sort_by_text = [ + 'a' => $this->language->lang('APS_POINTS_ACTION', $this->name), + 'ps' => $this->name, + 'pn' => $this->language->lang('APS_POINTS_NEW', $this->name), + 'po' => $this->language->lang('APS_POINTS_OLD', $this->name), + 'uu' => $this->language->lang('SORT_USERNAME'), + 'ru' => ucfirst($this->language->lang('FROM')), + 't' => $this->language->lang('APS_POINTS_ACTION_TIME', $this->name), + ]; + $sort_by_sql = [ + 'a' => 'l.log_action', + 'ps' => 'l.points_sum', + 'pn' => 'l.points_new', + 'po' => 'l.points_old', + 'uu' => 'u.username', + 'ru' => 'r.username', + 't' => 'l.log_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); + + if (!empty($u_sort_param)) + { + $sort_params = explode('&', $u_sort_param); + + foreach ($sort_params as $param) + { + list($key, $value) = explode('=', $param); + + $params[$key] = $value; + } + } + + // Define where and sort sql for use in displaying logs + $sql_time = ($sort_days) ? (time() - ($sort_days * 86400)) : 0; + $sql_sort = $sort_by_sql[$sort_key] . ' ' . (($sort_dir == 'd') ? 'DESC' : 'ASC'); + + $rowset = $this->log->get(true, $limit, $start, $forum_id, $topic_ids, $post_id, $user_id, $reportee_id, $sql_time, $sql_sort, $keywords); + $start = $this->log->get_valid_offset(); + $total = $this->log->get_log_count(); + + $user_ids = []; + + foreach ($rowset as $row) + { + $user_ids[] = $row['user_id']; + $this->template->assign_block_vars('aps_actions', array_merge(array_change_key_case($row, CASE_UPPER), [ + 'S_AUTH_BUILD' => (bool) $this->auth->acl_get('u_aps_view_build'), + 'S_AUTH_BUILD_OTHER' => (bool) ($this->auth->acl_get('u_aps_view_build_other') || ((int) $this->user->data['user_id'] === $row['user_id'])), + 'S_AUTH_MOD' => (bool) $this->auth->acl_get('u_aps_view_mod'), + 'S_MOD' => (bool) strpos($row['action'],'_USER_') !== false, + ])); + } + + $avatars = $this->functions->get_avatars($user_ids); + + if (!function_exists('make_forum_select')) + { + /** @noinspection PhpIncludeInspection */ + include $this->root_path . 'includes/functions_admin.' . $this->php_ext; + } + + /** + * phpBB's DocBlock expects a string but allows arrays aswell.. + * @noinspection PhpParamsInspection + */ + $this->pagination->generate_template_pagination( + [ + 'routes' => [ + 'phpbbstudio_aps_display', + 'phpbbstudio_aps_display_pagination', + ], + 'params' => $params, + ], 'pagination', 'pagination', $total, $limit, $start); + + $this->template->assign_vars([ + 'PAGE_NUMBER' => $this->pagination->on_page($total, $limit, $start), + 'TOTAL_LOGS' => $this->language->lang('APS_POINTS_ACTIONS_TOTAL', $this->name, $total), + + 'APS_ACTIONS_AVATARS' => $avatars, + 'APS_ACTIONS_NO_AVATAR' => $this->functions->get_no_avatar(), + + 'S_AUTH_FROM' => $s_reportee, + 'S_AUTH_USER' => $s_username, + + 'S_SEARCH_TOPIC' => $topic_title, + 'S_SEARCH_FROM' => $reportee, + 'S_SEARCH_USER' => $username, + 'S_SELECT_FORUM' => make_forum_select((int) $forum_id), + + 'S_SELECT_SORT_DAYS' => $s_limit_days, + 'S_SELECT_SORT_KEY' => $s_sort_key, + 'S_SELECT_SORT_DIR' => $s_sort_dir, + 'S_KEYWORDS' => $keywords, + + 'U_APS_ACTION_LOGS' => $this->helper->route('phpbbstudio_aps_display', ['page' => 'actions', 'action' => 'search']), + ]); + + $submit = $this->request->is_set_post('submit'); + $action = $this->request->variable('action', '', true); + + // Handle any AJAX action regarding this block + if ($submit && $this->request->is_ajax() && $action === 'search') + { + $this->template->set_filenames(['aps_body' => '@phpbbstudio_aps/blocks/base.html']); + $this->template->assign_vars([ + 'block' => [ + 'ID' => $block_id, + 'TITLE' => $this->language->lang('APS_POINTS_ACTIONS', $this->name), + 'TEMPLATE' => '@phpbbstudio_aps/blocks/points_actions.html', + ], + ]); + + $json_response = new \phpbb\json_response; + $json_response->send([ + 'body' => $this->template->assign_display('aps_body'), + ]); + } + } + + /** + * Display the "Recent adjustments" block. + * + * @return void + * @access public + */ + public function recent_adjustments() + { + $user_id = !$this->auth->acl_get('u_aps_view_logs_other') ? (int) $this->user->data['user_id'] : 0; + + $limit = (int) $this->config['aps_display_adjustments']; + $rowset = $this->log->get(true, $limit, 0, 0, 0, 0, $user_id, 0, 0, 'l.log_time DESC', 'APS_POINTS_USER_ADJUSTED'); + + $user_ids = []; + + foreach ($rowset as $row) + { + $user_ids[] = $row['user_id']; + $this->template->assign_block_vars('aps_adjustments', array_merge(array_change_key_case($row, CASE_UPPER), [ + 'S_AUTH_BUILD' => (bool) $this->auth->acl_get('u_aps_view_build'), + 'S_AUTH_BUILD_OTHER' => (bool) ($this->auth->acl_get('u_aps_view_build_other') || ((int) $this->user->data['user_id'] === $row['user_id'])), + 'S_AUTH_MOD' => (bool) $this->auth->acl_get('u_aps_view_mod'), + 'S_MOD' => (bool) strpos($row['action'],'_USER_') !== false, + ])); + } + + $avatars = $this->functions->get_avatars($user_ids); + + $this->template->assign_vars([ + 'APS_ADJUSTMENTS_AVATARS' => $avatars, + 'APS_ADJUSTMENTS_NO_AVATAR' => $this->functions->get_no_avatar(), + ]); + } + + /** + * Display the "Points per forum" block. + * + * @return void + * @access public + */ + public function charts_forum() + { + $rowset = []; + + $sql = 'SELECT forum_id, SUM(points_sum) as points + FROM ' . $this->table . ' + WHERE log_approved = 1 + GROUP BY forum_id'; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if (empty($row['points'])) + { + continue; + } + + $rowset[(int) $row['forum_id']]['POINTS'] = $row['points']; + } + $this->db->sql_freeresult($result); + + $sql = 'SELECT forum_name, forum_id + FROM ' . $this->functions->table('forums') . ' + WHERE ' . $this->db->sql_in_set('forum_id', array_keys($rowset), false, true); + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $rowset[(int) $row['forum_id']]['NAME'] = utf8_decode_ncr($row['forum_name']); + } + $this->db->sql_freeresult($result); + + if (isset($rowset[0])) + { + $rowset[0]['NAME'] = $this->language->lang('APS_POINTS_GLOBAL'); + } + + $this->template->assign_block_vars_array('aps_forums', $rowset); + } + + /** + * Display the "Points per group" block. + * + * @return void + * @access public + */ + public function charts_group() + { + $rowset = []; + + $sql = 'SELECT u.group_id, SUM(p.points_sum) as points + FROM ' . $this->table . ' p, + ' . $this->functions->table('users') . ' u + WHERE u.user_id = p.user_id + AND p.log_approved = 1 + GROUP BY u.group_id'; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $rowset[(int) $row['group_id']] = $row['points']; + } + $this->db->sql_freeresult($result); + + $sql = 'SELECT group_name, group_colour, group_id + FROM ' . $this->functions->table('groups') . ' + WHERE group_name <> "BOTS" + AND group_type <> ' . GROUP_HIDDEN . ' + AND ' . $this->db->sql_in_set('group_id', array_keys($rowset), false, true); + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $this->template->assign_block_vars('aps_groups', [ + 'COLOUR' => $row['group_colour'], + 'NAME' => $this->group_helper->get_name($row['group_name']), + 'POINTS' => $rowset[(int) $row['group_id']], + ]); + } + $this->db->sql_freeresult($result); + } + + /** + * Display the "Points trade off" and "Points growth" blocks. + * + * @return void + * @access public + */ + public function charts_period() + { + $sql = 'SELECT ' . $this->dbal->unix_to_month('log_time') . ' as month, + ' . $this->dbal->unix_to_year('log_time') . ' as year, + SUM(' . $this->db->sql_case('points_sum < 0', 'points_sum', 0) . ') AS negative, + SUM(' . $this->db->sql_case('points_sum > 0', 'points_sum', 0) . ') AS positive + FROM ' . $this->table . ' + WHERE log_time > ' . strtotime('-1 year') . ' + GROUP BY ' . $this->dbal->unix_to_month('log_time') . ', + ' . $this->dbal->unix_to_year('log_time') . ' + ORDER BY ' . $this->dbal->unix_to_year('log_time') . ' ASC, + ' . $this->dbal->unix_to_month('log_time') . ' ASC'; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $timestamp = $this->user->get_timestamp_from_format('m Y', $row['month'] . ' ' . $row['year']); + $formatted = $this->user->format_date($timestamp, 'F Y'); + + $this->template->assign_block_vars('aps_period', [ + 'DATE' => $formatted, + 'NEGATIVE' => -$row['negative'], // Make it positive + 'POSITIVE' => $row['positive'], + 'TOTAL' => $this->functions->equate_points($row['positive'], $row['negative']), + ]); + } + $this->db->sql_freeresult($result); + } + + /** + * Finds a user row for the provided username. + * + * @param string $username The username + * @param bool $full Whether we want just the identifier or everything + * @return mixed If $full is true: a user row or false if no user was found + * If $full is false: the user identifier + * @access protected + */ + protected function find_user($username, $full = true) + { + if (empty($username) && !$full) + { + return 0; + } + + $select = !$full ? 'user_id' : 'user_id, username, username_clean, user_colour, user_points, user_avatar, user_avatar_type, user_avatar_width, user_avatar_height'; + + $sql = 'SELECT ' . $select . ' + FROM ' . $this->functions->table('users') . ' + WHERE user_type <> ' . USER_IGNORE . ' + AND (username = "' . $this->db->sql_escape($username) . '" + OR username_clean = "' . $this->db->sql_escape(utf8_clean_string($username)) . '" + )'; + $result = $this->db->sql_query_limit($sql, 1); + $user = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + return $full ? $user : (int) $user['user_id']; + } + + /** + * Find a topic identifier for a provided topic title. + * + * @param string $title The topic title + * @return array The topic identifier or 0 if no topic was found or unauthorised + * @access protected + */ + protected function find_topic($title) + { + $topic_ids = []; + + $sql = 'SELECT forum_id, topic_id + FROM ' . $this->functions->table('topics') . ' + WHERE topic_title = "' . $this->db->sql_escape($title) . '"'; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if ($this->auth->acl_get('f_read', (int) $row['topic_id'])) + { + $topic_ids[] = (int) $row['topic_id']; + } + } + + return $topic_ids; + } +} diff --git a/ext/phpbbstudio/aps/core/dbal.php b/ext/phpbbstudio/aps/core/dbal.php new file mode 100644 index 0000000..79467f2 --- /dev/null +++ b/ext/phpbbstudio/aps/core/dbal.php @@ -0,0 +1,119 @@ +layer = $db->get_sql_layer(); + } + + /** + * Get the "random"-function for the current SQL layer. + * + * @return string The "random"-function + * @access public + */ + public function random() + { + switch ($this->layer) + { + case 'postgres': + return 'RANDOM()'; + break; + + case 'mssql': + case 'mssql_odbc': + return 'NEWID()'; + break; + + default: + return 'RAND()'; + break; + } + } + + /** + * Get the "month from a UNIX timestamp"-function for the current SQL layer. + * + * @param string $column The column name holding the UNIX timestamp + * @return string The "month from a UNIX timestamp"-function + * @access public + */ + public function unix_to_month($column) + { + switch ($this->layer) + { + case 'mssql': + case 'mssql_odbc': + case 'mssqlnative': + return 'DATEADD(m, ' . $column . ', 19700101)'; + break; + + case 'postgres': + return 'extract(month from to_timestamp(' . $column . '))'; + break; + + case 'sqlite3': + return "strftime('%m', datetime(" . $column . ", 'unixepoch'))"; + break; + + default: + return 'MONTH(FROM_UNIXTIME(' . $column . '))'; + break; + } + } + + /** + * Get the "year from a UNIX timestamp"-function for the current SQL layer. + * + * @param string $column The column name holding the UNIX timestamp + * @return string The "year from a UNIX timestamp"-function + * @access public + */ + public function unix_to_year($column) + { + switch ($this->layer) + { + case 'mssql': + case 'mssql_odbc': + case 'mssqlnative': + return 'DATEADD(y, ' . $column . ', 19700101)'; + break; + + case 'postgres': + return 'extract(year from to_timestamp(' . $column . '))'; + break; + + case 'sqlite3': + return "strftime('%y', datetime(" . $column . ", 'unixepoch'))"; + break; + + default: + return 'YEAR(FROM_UNIXTIME(' . $column . '))'; + break; + } + } +} diff --git a/ext/phpbbstudio/aps/core/functions.php b/ext/phpbbstudio/aps/core/functions.php new file mode 100644 index 0000000..ae4531b --- /dev/null +++ b/ext/phpbbstudio/aps/core/functions.php @@ -0,0 +1,501 @@ +auth = $auth; + $this->config = $config; + $this->db = $db; + $this->language = $language; + $this->path_helper = $path_helper; + $this->request = $request; + $this->user = $user; + + $this->table_prefix = $table_prefix; + $this->constants = $constants; + + $this->is_dae_enabled = $ext_manager->is_enabled('threedi/dae') && $config['threedi_default_avatar_extended']; + } + + /** + * Prefix a table name. + * + * This is to not rely on constants. + * + * @param string $name The table name to prefix + * @return string The prefixed table name + * @access public + */ + public function table($name) + { + return $this->table_prefix . $name; + } + + /** + * Select a forum name for a specific forum identifier. + * + * @param int $forum_id The forum identifier + * @return string The forum name + * @access public + */ + public function forum_name($forum_id) + { + $sql = 'SELECT forum_name FROM ' . $this->table('forums') . ' WHERE forum_id = ' . (int) $forum_id; + $result = $this->db->sql_query_limit($sql, 1); + $forum_name = $this->db->sql_fetchfield('forum_name'); + $this->db->sql_freeresult($result); + + return $forum_name; + } + + public function post_data($post_id) + { + $sql = 'SELECT t.topic_first_post_id, p.poster_id + FROM ' . $this->table('posts') . ' p, + ' . $this->table('topics') . ' t + WHERE p.topic_id = t.topic_id + AND p.post_id = ' . (int) $post_id; + $result = $this->db->sql_query_limit($sql, 1); + $post_data = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + return $post_data; + } + + /** + * Get topic and post locked status. + * + * Called when a moderator edits a post. + * + * @param int $post_id The post identifier + * @return array The database row + * @access public + */ + public function topic_post_locked($post_id) + { + $sql = 'SELECT t.topic_poster, t.topic_status, p.post_edit_locked + FROM ' . $this->table('posts') . ' p, + ' . $this->table('topics') . ' t + WHERE p.topic_id = t.topic_id + AND post_id = ' . (int) $post_id; + $result = $this->db->sql_query_limit($sql, 1); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + return $row; + } + + /** + * Get avatars for provided user identifiers. + * + * @param array $user_ids The user identifiers. + * @return array Array of the users' avatars indexed per user identifier + * @access public + */ + public function get_avatars($user_ids) + { + $avatars = []; + + $sql = 'SELECT user_id, user_avatar, user_avatar_type, user_avatar_width, user_avatar_height + FROM ' . $this->table('users') . ' + WHERE ' . $this->db->sql_in_set('user_id', $user_ids, false, true) . ' + AND user_type <> ' . USER_IGNORE; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $avatars[(int) $row['user_id']] = phpbb_get_user_avatar($row); + } + $this->db->sql_freeresult($result); + + return $avatars; + } + + /** + * Checks whether Advanced Points System is ran in Safe Mode, + * meaning that exceptions will be caught and logged instead of thrown. + * Safe mode should be turned "off" when testing and developing. + * + * @return bool Whether APS is ran in Safe Mode or not. + * @access public + */ + public function safe_mode() + { + return (bool) $this->config['aps_points_safe_mode']; + } + + /** + * Get a formatted points string according to the settings. + * + * @param double $points The points to display + * @param bool $icon Whether or not to also display the points icon + * @return string The formatted points for display + * @access public + */ + public function display_points($points, $icon = true) + { + $separator_dec = htmlspecialchars_decode($this->config['aps_points_separator_dec']); + $separator_thou = htmlspecialchars_decode($this->config['aps_points_separator_thou']); + + $points = number_format((double) $points, (int) $this->config['aps_points_decimals'], (string) $separator_dec, (string) $separator_thou); + + // If we do not want the icon, return now + if (!$icon) + { + return $points; + } + + // Get the icon side + $right = (bool) $this->config['aps_points_icon_position']; + + return $right ? ($points . ' ' . $this->get_icon()) : ($this->get_icon() . ' ' . $points); + } + + /** + * Format points for usage in input fields + * + * @param double $points The points to format + * @return double + * @access public + */ + public function format_points($points) + { + return (double) round($points, (int) $this->config['aps_points_decimals']); + } + + /** + * Get the points icon for display. + * + * @param bool $force_fa Whether to force FA icon + * @return string The HTML formatted points icon + * @access public + */ + public function get_icon($force_fa = false) + { + if (!$force_fa && $this->config['aps_points_icon_img']) + { + $board_url = defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH; + $base_path = $board_url || $this->request->is_ajax() ? generate_board_url() . '/' : $this->path_helper->get_web_root_path(); + + $source = $base_path . 'images/' . (string) $this->config['aps_points_icon_img']; + + return '' . $this->get_name() . ''; + } + + return ''; + } + + /** + * Get the localised points name. + * + * @return string The localised points name + * @access public + */ + public function get_name() + { + if (empty($this->name)) + { + $key = 'aps_points_name_'; + + $name = !empty($this->config[$key . $this->user->lang_name]) ? $this->config[$key . $this->user->lang_name] : $this->config[$key . $this->config['default_lang']]; + + // Fallback + $name = !empty($name) ? $name : 'Points'; + + $this->name = $name; + } + + return $this->name; + } + + public function get_auth($name, $forum_id) + { + // Fix for template functions + $forum_id = $forum_id === true ? 0 : $forum_id; + + return $this->auth->acl_get($name, $forum_id); + } + + /** + * Get an config value for given config name. + * + * @param string $name The APS config name + * @return string The APS config value + * @access public + */ + public function get_config($name) + { + return $this->config->offsetGet($name); + } + + /** + * Get the step amount for a numeric input field. + * + * @return double + * @access public + */ + public function get_step() + { + return round(substr_replace('001', '.', (3 - (int) $this->config['aps_points_decimals']), 0), $this->config['aps_points_decimals']); + } + + /** + * Equates an array of points to a single points value. + * + * @param array $array The array to equate + * @param string $operator The equation operator + * @return double The equated points value + * @access public + */ + public function equate_array(array $array, $operator = '+') + { + $result = array_reduce( + $array, + function($a, $b) use ($operator) + { + return $this->equate_points($a, $b, $operator); + }, + 0.00); + + return $result; + } + + /** + * Equate two points by reference. + * + * @param double $a The referenced points value + * @param double $b The points value to equate + * @param string $operator The equation operator + * @return void Passed by reference + * @access public + */ + public function equate_reference(&$a, $b, $operator = '+') + { + $a = $this->equate_points($a, $b, $operator); + } + + /** + * Equate two points. + * + * @param double $a The points value to equate + * @param double $b The points value to equate + * @param string $operator The equation operator + * @return double The equated points value + * @access public + */ + public function equate_points($a, $b, $operator = '+') + { + $b = $this->is_points($b) ? $b : 0; + + switch ($operator) + { + # Multiply + case 'x': + case '*'; + $a *= $b; + break; + + # Divide + case '÷': + case '/': + $a = $b ? $a / $b : 0; + break; + + # Subtract + case '-': + $a -= $b; + break; + + # Add + case '+': + default: + $a += $b; + break; + } + + return (double) $a; + } + + /** + * Check if a points value is numeric. + * + * @param mixed $points The points value + * @return bool Whether the value is numeric or not + * @access public + */ + public function is_points($points) + { + return is_numeric($points); + } + + /** + * Checks whether a user's points are within the Min. and Max. allowed points. + * + * @param double $points The new total + * @return double The new total that is within the boundaries + * @access public + */ + public function boundaries($points) + { + // Check if the new total is lower than the minimum value, has to be '' as 0 is a valid minimum value. + if (($min = $this->config['aps_points_min']) !== '') + { + $min = (double) $min; + $points = $points < $min ? $min : $points; + } + + // Check if the new total is higher than the maximum value, has to be '' as 0 is a valid maximum value. + if (($max = $this->config['aps_points_max']) !== '') + { + $max = (double) $max; + $points = $points > $max ? $max : $points; + } + + return $points; + } + + /** + * Get a default no_avatar HTML string. + * + * @return string HTML formatted no_avatar string + * @access public + */ + public function get_no_avatar() + { + // If DAE is enabled we do not have to set up a default avatar + if ($this->is_dae_enabled()) + { + return ''; + } + + $board_url = generate_board_url() . '/'; + $corrected_path = $this->path_helper->get_web_root_path(); + $web_path = (defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH) ? $board_url : $corrected_path; + $theme_path = "{$web_path}styles/" . rawurlencode($this->user->style['style_path']) . '/theme'; + + $no_avatar = '' . $this->language->lang('USER_AVATAR') . ''; + + return $no_avatar; + } + + /** + * Checks whether Default Avatar Extended (DAE) is enabled or not. + * + * @return bool Whether DAE is enabled or not. + * @access public + */ + public function is_dae_enabled() + { + return (bool) ($this->is_dae_enabled && $this->config['threedi_default_avatar_extended']); + } + + /** + * Get link locations. + * + * @param string $key The config key + * @return array The link locations data + */ + public function get_link_locations($key = 'aps_link_locations') + { + $links = []; + + foreach($this->constants['locations'] as $location => $flag) + { + $links[$location] = (bool) ((int) $this->config[$key] & $flag); + } + + return $links; + } + + /** + * Set link locations + * + * @param array $locations The link locations data + * @param string $key The config key + * @return void + */ + public function set_link_locations(array $locations, $key = 'aps_link_locations') + { + $flags = 0; + + foreach ($locations as $location => $status) + { + $flags += $status ? (int) $this->constants['locations'][$location] : 0; + } + + $this->config->set($key, (int) $flags); + } +} diff --git a/ext/phpbbstudio/aps/core/language.php b/ext/phpbbstudio/aps/core/language.php new file mode 100644 index 0000000..437acee --- /dev/null +++ b/ext/phpbbstudio/aps/core/language.php @@ -0,0 +1,106 @@ +config = $config; + $this->language = $language; + $this->manager = $manager; + $this->user = $user; + + $this->php_ext = $php_ext; + } + + /** + * Load all language files used for the Advanced Points System. + * + * @see \p_master::add_mod_info() + * + * @return void + * @access public + */ + public function load() + { + $finder = $this->manager->get_finder(); + + $finder->prefix('phpbbstudio_aps_') + ->suffix('.' . $this->php_ext); + + // 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 = []; + + // Search for board default language if it's not the user language + if ($this->config['default_lang'] != $this->user->lang_name) + { + $default_lang_files = $finder + ->extension_directory('/language/' . basename($this->config['default_lang'])) + ->find(); + } + + // Search for english, if its not the default or user language + if ($this->config['default_lang'] != 'en' && $this->user->lang_name != 'en') + { + $english_lang_files = $finder + ->extension_directory('/language/en') + ->find(); + } + + // Find files in the user's language + $user_lang_files = $finder + ->extension_directory('/language/' . $this->user->lang_name) + ->find(); + + $lang_files = array_merge($english_lang_files, $default_lang_files, $user_lang_files); + foreach ($lang_files as $lang_file => $ext_name) + { + $this->language->add_lang($lang_file, $ext_name); + } + } +} diff --git a/ext/phpbbstudio/aps/core/log.php b/ext/phpbbstudio/aps/core/log.php new file mode 100644 index 0000000..48d2864 --- /dev/null +++ b/ext/phpbbstudio/aps/core/log.php @@ -0,0 +1,560 @@ +auth = $auth; + $this->config = $config; + $this->db = $db; + $this->functions = $functions; + $this->language = $language; + $this->lang_aps = $lang_aps; + $this->user = $user; + $this->table = $table; + + $this->root_path = $root_path; + $this->admin_path = $root_path . $admin_path; + $this->php_ext = $php_ext; + + $this->set_is_admin((defined('ADMIN_START') && ADMIN_START) || (defined('IN_ADMIN') && IN_ADMIN)); + } + + /** + * Set is_in_admin in order to return administrative user profile links in get(). + * + * @param bool $is_in_admin Called from within the acp? + * @return void + * @access public + */ + public function set_is_admin($is_in_admin) + { + $this->is_in_admin = (bool) $is_in_admin; + } + + /** + * Returns the is_in_admin option. + * + * @return bool Called from within the acp? + * @access public + */ + public function get_is_admin() + { + return $this->is_in_admin; + } + + /** + * {@inheritDoc} + */ + public function get_log_count() + { + return ($this->entries_count) ? $this->entries_count : 0; + } + /** + * {@inheritDoc} + */ + public function get_valid_offset() + { + return ($this->last_page_offset) ? $this->last_page_offset : 0; + } + + /** + * Loads the language files used by the Advanced Points System. + * + * @return void + * @access public + */ + public function load_lang() + { + $this->lang_aps->load(); + } + + /** + * Log a points action. + * + * @param array $data The array to log + * @param int $time The time to log + * @return bool|int False on error, new log entry identifier otherwise + * @access public + */ + public function add(array $data, $time = 0) + { + // We need to have at least the log action, points gained/lost and either the old or new user points. + if ($this->check_row($data)) + { + return false; + } + + $row = $this->prepare_row($data, $time); + + $sql = 'INSERT INTO ' . $this->table . ' ' . $this->db->sql_build_array('UPDATE', $row); + $this->db->sql_query($sql); + + return $this->db->sql_nextid(); + } + + /** + * Log multiple points actions at once. + * + * @param array $data The arrays to log + * @param int $time The time to log + * @return bool + * @access public + */ + public function add_multi(array $data, $time = 0) + { + $logs = []; + + foreach ($data as $row) + { + // We need to have at least the log action, points gained/lost and either the old or new user points. + if ($this->check_row($row)) + { + continue; + } + + $logs[] = $this->prepare_row($row, $time); + } + + $this->db->sql_multi_insert($this->table, $logs); + + return (bool) !empty($logs); + } + + /** + * Check whether a log row has the minimal required information. + * + * @param array $row The log row the check + * @return bool Whether this log row is eligible or not + * @access public + */ + public function check_row(array $row) + { + return (bool) (empty($row['action']) || in_array($row['points_sum'], [0, 0.0, 0.00]) || (!isset($row['points_old']) && !isset($row['points_new']))); + } + + /** + * Prepare a log row for inserting in the database table. + * + * @param array $row The log row to prepare + * @param int $time The time to log + * @return array The prepared log row + * @access public + */ + public function prepare_row(array $row, $time) + { + return [ + 'log_action' => $row['action'], + 'log_actions' => !empty($row['actions']) ? serialize($row['actions']) : '', + 'log_time' => $time ? $time : time(), + 'log_approved' => isset($row['approved']) ? (bool) $row['approved'] : true, + 'forum_id' => !empty($row['forum_id']) ? (int) $row['forum_id'] : 0, + 'topic_id' => !empty($row['topic_id']) ? (int) $row['topic_id'] : 0, + 'post_id' => !empty($row['post_id']) ? (int) $row['post_id'] : 0, + 'user_id' => !empty($row['user_id']) ? (int) $row['user_id'] : (int) $this->user->data['user_id'], + 'reportee_id' => !empty($row['reportee_id']) ? (int) $row['reportee_id'] : (int) $this->user->data['user_id'], + 'reportee_ip' => !empty($row['reportee_ip']) ? (string) $row['reportee_ip'] : (string) $this->user->ip, + 'points_old' => isset($row['points_old']) ? (double) $row['points_old'] : $this->functions->equate_points($row['points_new'], $row['points_sum'], '-'), + 'points_sum' => (double) $row['points_sum'], + 'points_new' => isset($row['points_new']) ? (double) $this->functions->boundaries($row['points_new']) : $this->functions->boundaries($this->functions->equate_points($row['points_old'], $row['points_sum'], '+')), + ]; + } + + /** + * Delete a points action from the logs depending on the conditions. + * + * @param array $conditions The delete conditions + * @return void + * @access public + */ + public function delete(array $conditions) + { + // Need an "empty" sql where to begin with + $sql_where = ''; + + 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->table . ' WHERE log_id <> 0 ' . $sql_where; + $this->db->sql_query($sql); + } + + /** + * Gets the logged point values for a given user id and post ids combination. + * + * @param int $user_id The user identifier + * @param array $post_ids The post identifiers + * @param bool $approved Whether the logged entries are set to approved or not + * @return array The array of point values indexed per post identifier + * @access public + */ + public function get_values($user_id, array $post_ids, $approved = true) + { + $points = []; + + $sql = 'SELECT points_sum, post_id + FROM ' . $this->table . ' + WHERE user_id = ' . (int) $user_id . ' + AND log_approved = ' . (int) $approved . ' + AND ' . $this->db->sql_in_set('post_id', $post_ids, false, true); + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $points[(int) $row['post_id']] = $row['points_sum']; + } + $this->db->sql_freeresult($result); + + return $points; + } + + /** + * Sets logged entries to approved for a given user id and post ids combination. + * + * @param int $user_id The user identifier + * @param array $post_ids The post identifiers + * @return void + * @access public + */ + public function approve($user_id, array $post_ids) + { + $sql = 'UPDATE ' . $this->table . ' + SET log_approved = 1 + WHERE log_approved = 0 + AND user_id = ' . (int) $user_id . ' + AND ' . $this->db->sql_in_set('post_id', $post_ids, false, true); + $this->db->sql_query($sql); + } + + /** + * Get logged point actions for a certain combination. + * + * @param bool $count Whether we should count the total amount of logged entries for this combination. + * @param int $limit The amount of rows to return + * @param int $offset The amount of rows from the start to return + * @param int|string $forum_id The forum identifier (set to '' as 0 is a valid choice) + * @param int|array $topic_id The topic identifier + * @param int $post_id The post identifier + * @param int $user_id The user identifier + * @param int $reportee_id The reportee identifier (from user) + * @param int $time The logged time + * @param string $sort_by The ORDER BY clause + * @param string $keywords The keywords to search for + * @return array The found logged point actions for this combination + * @access public + */ + public function get($count = true, $limit = 0, $offset = 0, $forum_id = '', $topic_id = 0, $post_id = 0, $user_id = 0, $reportee_id = 0, $time = 0, $sort_by = 'l.log_time DESC', $keywords = '') + { + $this->entries_count = 0; + $this->last_page_offset = $offset; + + $limit = !empty($limit) ? $limit : $this->config['aps_actions_per_page']; + + $profile_url = ($this->get_is_admin() && $this->admin_path) ? append_sid("{$this->admin_path}index.{$this->php_ext}", 'i=users&mode=overview') : append_sid("{$this->root_path}memberlist.{$this->php_ext}", 'mode=viewprofile'); + + $sql_where = 'l.user_id = u.user_id'; + $sql_where .= $time ? ' AND l.log_time >= ' . (int) $time : ''; + $sql_where .= $forum_id !== '' ? ' AND l.forum_id = ' . (int) $forum_id : ''; + $sql_where .= $topic_id ? (is_array($topic_id) ? ' AND ' . $this->db->sql_in_set('l.topic_id', $topic_id) : ' AND l.topic_id = ' . (int) $topic_id) : ''; + $sql_where .= $post_id ? ' AND l.post_id = ' . (int) $post_id : ''; + $sql_where .= $user_id ? ' AND l.user_id = ' . (int) $user_id : ''; + $sql_where .= $reportee_id ? ' AND l.reportee_id = ' . (int) $reportee_id : ''; + $sql_where .= $this->get_is_admin() ? '' : ' AND l.log_approved = 1'; + + $sql_keywords = ''; + if (!empty($keywords)) + { + // Get the SQL condition for our keywords + $sql_keywords = $this->generate_sql_keyword($keywords); + } + + $sql_ary = [ + 'SELECT' => 'l.*, + u.user_id, u.username, u.user_colour, + r.user_id as reportee_id, r.username as reportee_name, r.user_colour as reportee_colour, + f.forum_name, t.topic_title, p.post_subject', + 'FROM' => [ + $this->table => 'l', + USERS_TABLE => 'u', + ], + 'LEFT_JOIN' => [ + [ + 'FROM' => [USERS_TABLE => 'r'], + 'ON' => 'l.reportee_id = r.user_id', + ], + [ + 'FROM' => [FORUMS_TABLE => 'f'], + 'ON' => 'l.forum_id = f.forum_id', + ], + [ + 'FROM' => [TOPICS_TABLE => 't'], + 'ON' => 'l.topic_id = t.topic_id', + ], + [ + 'FROM' => [POSTS_TABLE => 'p'], + 'ON' => 'l.post_id = p.post_id AND t.topic_first_post_id != p.post_id', + ], + ], + 'WHERE' => $sql_where . $sql_keywords, + 'ORDER_BY' => $sort_by, + ]; + + // Provide moderator anonymity, exclude any "_MOD_" actions + if (!$this->auth->acl_get('u_aps_view_mod')) + { + $sql_ary['WHERE'] .= ' AND log_action ' . $this->db->sql_not_like_expression($this->db->get_any_char() . '_MOD_' . $this->db->get_any_char()); + } + + if ($count) + { + $count_array = $sql_ary; + + $count_array['SELECT'] = 'COUNT(log_id) as count'; + unset($count_array['LEFT_JOIN'], $count_array['ORDER_BY']); + + $sql = $this->db->sql_build_query('SELECT', $count_array); + $result = $this->db->sql_query($sql); + $this->entries_count = (int) $this->db->sql_fetchfield('count'); + $this->db->sql_freeresult($result); + + if ($this->entries_count === 0) + { + $this->last_page_offset = 0; + return []; + } + + while ($this->last_page_offset >= $this->entries_count) + { + $this->last_page_offset = max(0, $this->last_page_offset - $limit); + } + } + + $logs = []; + + $sql = $this->db->sql_build_query('SELECT', $sql_ary); + $result = $this->db->sql_query_limit($sql, $limit, $this->last_page_offset); + while ($row = $this->db->sql_fetchrow($result)) + { + $s_authed = (bool) ($row['forum_id'] && $this->auth->acl_get('f_read', (int) $row['forum_id'])); + + // append_sid() will ignore params with a NULL value + $forum_params = ['f' => ($row['forum_id'] ? (int) $row['forum_id'] : null)]; + $topic_params = ['t' => ($row['topic_id'] ? (int) $row['topic_id'] : null)]; + + $s_points = ($this->auth->acl_get('a_forum') && $this->auth->acl_get('a_aps_points')); + $points_forum = append_sid("{$this->admin_path}index.{$this->php_ext}", ['i' => 'acp_forums', 'mode' => 'manage', 'action' => 'edit', 'f' => (int) $row['forum_id'], '#' => 'aps_points']); + $points_global = append_sid("{$this->admin_path}index.{$this->php_ext}", ['i' => '-phpbbstudio-aps-acp-main_module', 'mode' => 'points']); + + $logs[] = [ + 'id' => (int) $row['log_id'], + + 'user' => get_username_string('full', $row['user_id'], $row['username'], $row['user_colour'], false, $profile_url), + 'user_id' => (int) $row['user_id'], + 'reportee' => $row['reportee_id'] != ANONYMOUS ? get_username_string('full', $row['reportee_id'], $row['reportee_name'], $row['reportee_colour'], false, $profile_url) : '', + 'reportee_id' => (int) $row['reportee_id'], + 's_self' => (bool) ((int) $row['user_id'] === (int) $row['reportee_id']), + + 'ip' => (string) $row['reportee_ip'], + 'time' => (int) $row['log_time'], + 'action' => (string) $row['log_action'], + 'actions' => unserialize($row['log_actions']), + + 'approved' => (bool) $row['log_approved'], + + 'forum_id' => (int) $row['forum_id'], + 'forum_name' => (string) $row['forum_name'], + 'u_forum' => ($row['forum_id'] && $s_authed) ? append_sid("{$this->root_path}viewforum.{$this->php_ext}", $forum_params) : '', + + 'topic_id' => (int) $row['topic_id'], + 'topic_title' => (string) $row['topic_title'], + 'u_topic' => ($row['topic_id'] && $s_authed) ? append_sid("{$this->root_path}viewtopic.{$this->php_ext}", array_merge($forum_params, $topic_params)) : '', + + 'post_id' => (int) $row['post_id'], + 'post_subject' => (string) $row['post_subject'], + 'u_post' => ($row['post_id'] && $s_authed) ? append_sid("{$this->root_path}viewtopic.{$this->php_ext}", array_merge($forum_params, $topic_params, ['p' => (int) $row['post_id'], '#' => 'p' . (int) $row['post_id']])) : '', + + 'points_old' => $row['points_old'] !== '0.00' ? (double) $row['points_old'] : $this->functions->equate_points((double) $row['points_new'], $row['points_sum'], '-'), + 'points_sum' => (double) $row['points_sum'], + 'points_new' => $row['points_new'] !== '0.00' ? (double) $row['points_new'] : $this->functions->equate_points((double) $row['points_old'], $row['points_sum'], '+'), + 'u_points' => $s_points ? ($row['forum_id'] ? $points_forum : $points_global) : '', + ]; + } + $this->db->sql_freeresult($result); + + return $logs; + } + + /** + * 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 + * @access protected + */ + 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 = []; + + // Build pattern and keywords... + for ($i = 0, $num_keywords = count($keywords); $i < $num_keywords; $i++) + { + $keywords_pattern[] = preg_quote($keywords[$i], '#'); + } + + $keywords_pattern = '#' . implode('|', $keywords_pattern) . '#ui'; + + $operations = []; + + foreach ($this->language->get_lang_array() as $key => $value) + { + if (substr($key, 0, 4) == 'APS_') + { + 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; + } + else if (preg_match($keywords_pattern, $key)) + { + $operations[] = $key; + } + } + } + + if (!empty($operations)) + { + $sql_keywords = ' ' . $statement_operator . ' ('; + $sql_keywords .= $this->db->sql_in_set($table_alias . 'log_action', $operations); + $sql_keywords .= ')'; + } + } + + return $sql_keywords; + } +} diff --git a/ext/phpbbstudio/aps/core/template.php b/ext/phpbbstudio/aps/core/template.php new file mode 100644 index 0000000..6fac8c7 --- /dev/null +++ b/ext/phpbbstudio/aps/core/template.php @@ -0,0 +1,104 @@ +functions = $functions; + } + + /** + * Get the name of this extension + * + * @return string + * @access public + */ + public function getName() + { + return 'phpbbstudio_aps'; + } + + /** + * Returns a list of global functions to add to the existing list. + * + * @return array An array of global functions + * @access public + */ + public function getFunctions() + { + return [ + // Template functions prefixed with "aps_" come here + new \Twig_SimpleFunction('aps_*', [$this, 'aps_handle']), + ]; + } + + /** + * Handle the called template function. + * + * @param string $function The APS Core function name + * @param mixed $points First parameter from the called template function + * @param bool $boolean Second parameter from the called template function + * @return mixed + * @access public + */ + public function aps_handle($function, $points = 0, $boolean = true) + { + switch ($function) + { + case 'auth': + return $this->functions->get_auth($points, $boolean); + break; + + case 'config': + return $this->functions->get_config($points); + break; + + case 'display': + return $this->functions->display_points($points, $boolean); + break; + + case 'format': + return $this->functions->format_points($points); + break; + + case 'icon': + return $this->functions->get_icon($points); + break; + + case 'name'; + return $this->functions->get_name(); + break; + + case 'step': + return $this->functions->get_step(); + break; + + default: + return ''; + break; + } + } +} diff --git a/ext/phpbbstudio/aps/cron/task/birthday.php b/ext/phpbbstudio/aps/cron/task/birthday.php new file mode 100644 index 0000000..4383a16 --- /dev/null +++ b/ext/phpbbstudio/aps/cron/task/birthday.php @@ -0,0 +1,122 @@ +config = $config; + $this->db = $db; + $this->functions = $functions; + $this->manager = $manager; + } + + /** + * Runs this cron task. + * + * @return void + * @access public + */ + public function run() + { + $user_ids = $data = []; + $data['birthdays'] = []; + + // Set the default timezone + date_default_timezone_set($this->config['board_timezone']); + + // Get current day and month (no leading zero) + $day = date('j'); + $month = date('n'); + + // Birthdays are stored with a leading space if only one digit: " 8- 6-1990". + $data['day'] = strlen($day) === 1 ? ' ' . $day : $day; + $data['month'] = strlen($month) === 1 ? ' ' . $month : $month; + + // Build a SQL like expression: DD-MM-% + $birthday = $data['day'] . '-' . $data['month'] . '-' . $this->db->get_any_char(); + + // Select all the user identifiers that are celebrating their birthday today + $sql = 'SELECT user_id, user_birthday + FROM ' . $this->functions->table('users') . ' + WHERE user_type <> ' . USER_IGNORE . ' + AND user_birthday ' . $this->db->sql_like_expression($birthday); + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $user_ids[] = $row['user_id']; + + $data['birthdays'][(int) $row['user_id']] = $row['user_birthday']; + } + $this->db->sql_freeresult($result); + + // Calculate the points! + $this->manager->trigger('birthday', $user_ids, $data); + + // Update the cron task run time here + $this->config->set('aps_birthday_last_run', time(), false); + } + + /** + * Returns whether this cron task can run, given current board configuration. + * + * @return bool + * @access public + */ + 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 + * @access public + */ + public function should_run() + { + return $this->config['aps_birthday_last_run'] < time() - $this->cron_frequency; + } +} diff --git a/ext/phpbbstudio/aps/docs/.htaccess b/ext/phpbbstudio/aps/docs/.htaccess new file mode 100644 index 0000000..4128d34 --- /dev/null +++ b/ext/phpbbstudio/aps/docs/.htaccess @@ -0,0 +1,4 @@ + + Order Allow,Deny + Deny from All + diff --git a/ext/phpbbstudio/aps/docs/CHANGELOG.md b/ext/phpbbstudio/aps/docs/CHANGELOG.md new file mode 100644 index 0000000..9173680 --- /dev/null +++ b/ext/phpbbstudio/aps/docs/CHANGELOG.md @@ -0,0 +1,53 @@ +# phpBB Studio - Advanced Points System + +#### v1.0.6-RC on 11-03-2020 +- Fixed template block inclusion +- Fixed approved post points distribution +- Updated permission language strings +- Added phpBB 3.3 compatibility + +#### v1.0.5-RC1 on 20-12-2019 +- Entered the stage features frozen. +- Fixed a bug where points were displayed on profile despite the setting +- Fixed a bug where excluded points were still receiving points +- Added an option to ignore points which do not meet certain criteria +- Added an option to determine where the Points link shows up +- Enhanced the CSS to be altered more easily for other styles + +#### v1.0.4-beta on 01-12-2019 +- Major code clean up +- Bumped phpBB version requirement to 3.2.8 +- Fixed MCP "Front" missing log language strings +- Fixed ACP "Display" missing language strings +- Fixed ACP setting for "icon position" not taking affect +- Added radio CSS to admin +- Added changing user points for an entire group +- Added the possibility to use an image as icon +- Added to automatically hide display categories without blocks +- Added version checker + +#### v1.0.3-beta +- Fixed PHP event DocBlocks versions +- Added PHP event for points distribution + +#### v1.0.2-beta +- Improved position of reportee/moderator name in Actions list +- Added public function for points distribution +- Added PHP and Style events + +#### v1.0.1-beta +- Updated route requirements +- Fixed pagination issues for blocks +- Fixed top users _cups_ to now show proper colour on same points +- Added top users numbers for users not on the podium +- Added two new permissions + - Can only view own logged actions + - Can only view own logged actions augmentation _(build up)_ +- Added support for forum names with special characters, such as emoji +- Added more CSS utility classes +- Fixed CSS to be stylelint compliant +- Fixed a bug where charts were displaying unapproved points +- Fixed a bug where users did not receive unapproved points + +#### v1.0.0-beta +- first public release diff --git a/ext/phpbbstudio/aps/docs/EVENTS.txt b/ext/phpbbstudio/aps/docs/EVENTS.txt new file mode 100644 index 0000000..a3c285a --- /dev/null +++ b/ext/phpbbstudio/aps/docs/EVENTS.txt @@ -0,0 +1,19 @@ +/** + * Advanced Points System extension © Copyright phpBB Studio 2019 + * https://www.phpbbstudio.com + * + * APS is a free extension for the phpBB Forum Software Package. + * 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 extension 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 license.txt file. + * The same can be viewed at + */ + +No events required while using at least phpBB 3.2.8 diff --git a/ext/phpbbstudio/aps/docs/FEATURES.md b/ext/phpbbstudio/aps/docs/FEATURES.md new file mode 100644 index 0000000..d174ea5 --- /dev/null +++ b/ext/phpbbstudio/aps/docs/FEATURES.md @@ -0,0 +1,9 @@ +# phpBB Studio - Advanced Points System + +- Fully integrated Points System for phpBB 3.2 +- Set up point values on a global or per-forum basis. +- All major native phpBB actions are available. +- User, Moderator and Administrator permissions. +- Notification to user on adjustment by moderator.
    *(with anonymity)* +- Integrated and fully extendable overview page.
    *(customisable by user)* +- Fully extendable by other extensions.
    *(with detailed explanation and examples)* diff --git a/ext/phpbbstudio/aps/docs/README.md b/ext/phpbbstudio/aps/docs/README.md new file mode 100644 index 0000000..3b70601 --- /dev/null +++ b/ext/phpbbstudio/aps/docs/README.md @@ -0,0 +1,81 @@ +

    Advanced Points System

    +

    An extension for the phpBB Forum Software.

    + +

    + GPLv2 License +

    + +## Table of Contents +> - [Install](#install) +> - [Uninstall](#uninstall) +> - [Support](#support) +> - [Translations](#translations) +> - [Features](#features) +> - [Other extensions](#other-extensions) +> - [Extending APS](#extending-aps) +> - [You might also like](#you-might-also-like) +> - [License](#license) + +## Install +1. Download the latest validated release +2. Unzip the downloaded release and copy it to the `ext` directory of your phpBB board. +3. Navigate in the ***ACP*** to `Customise » Extension management » Manage extensions`. +4. Look for `phpBB Studio - Advanced Points System` under the **Disabled Extensions** list, and click its **`Enable`** link. +5. Set up and configure `Advanced Points System` by navigating in the ***ACP*** to `Extensions » Advanced Points System`. + +> *Read more about [installing phpBB Extensions](https://www.phpbb.com/extensions/installing/#installing).* + +## Uninstall +1. Navigate in the ***ACP*** to `Customise » Extension management » Manage extensions`. +2. Look for `phpBB Studio - Advanced Points System` under the **Enabled Extensions** list, and click its **`Disable`** link. +3. To permanently uninstall, click **`Delete Data`** and then delete the `/ext/phpbbstudio/aps` directory. + +> *Read more about [uninstalling phpBB Extensions](https://www.phpbb.com/extensions/installing/#removing).* + +## Support +- **Important: Only official release versions validated by the phpBB Extensions Team should be installed on a live forum. Pre-release (beta, RC) versions downloaded from this repository are only to be used for testing on offline/development forums and are not officially supported.** +- Report bugs and other issues to our **[Issue Tracker](https://github.com/phpBB-Studio/AdvancedPointsSystem/issues)**. +- Support requests can be posted and discussed in the **[Extension support](https://phpbbstudio.com/viewforum.php?f=5)** forum over at the [phpBB Studio](https://www.phpbbstudio.com). +- Support requests can be posted and discussed in the **[Development topic](https://www.phpbb.com/community/viewforum.php?f=456)** over at [phpBB.com](https://www.phpbb.com). + +## Translations +- Translations should be posted in the corresponding forum in **[Extension support](https://phpbbstudio.com/viewforum.php?f=5)** over at the [phpBB Studio](https://www.phpbbstudio.com). +- Each translation should be created in a **separate** topic. +- The topic should either contain a **zip archive** as an attachment or a link to your **GitHub repository**. +- Translations should ***not*** be posted in the Development topic over at [phpBB.com](https://www.phpbb.com). +- Translations should ***not*** be created as Pull Requests over at the [GitHub](https://github.com/phpBB-Studio/) repository. + +## Features +- Fully integrated Points System for phpBB 3.2 +- Set up point values on a global or per-forum basis. +- All major native phpBB actions are available. +- User, Moderator and Administrator permissions. +- Notification to user on adjustment by moderator.
    *(with anonymity)* +- Integrated and fully extendable overview page.
    *(customisable by user)* +- Fully extendable by other extensions.
    *(with detailed explanation and examples)* + +## Other extensions +- [Advanced Shop System](https://github.com/phpBB-Studio/AdvancedShopSystem) +- [Advanced Points System · Purchases](https://phpbbstudio.com/extensions/advanced-points-system-purchases) +- [Advanced Points System · Auto Groups](https://github.com/phpBB-Studio/AdvancedShopSystemAutoGroups) +- Bank _(To be determined)_ +- Lottery _(To be determined)_ + +## Extending APS +For the extension developers amongst us, we have written a comprehensive Wiki that should describe everything in detail. +You can read about [Extending APS](https://github.com/phpBB-Studio/AdvancedPointsSystem/wiki/Extending-APS) and all [the possibilities](https://github.com/phpBB-Studio/AdvancedPointsSystem/wiki/Extending-possibilities) there are. If there are still any questions, feel free to ask. + +## You might also like +- Dice Rolls +- Highlight Posts +- Who Read What +- Sub Global Topic +- Topic Cement Style +- Topic Events + + +## License +GNU General Public License, version 2 ([GPLv2](../license.txt)). + +--- +> [phpbbstudio.com](https://www.phpbbstudio.com) · GitHub [phpbb-studio](https://github.com/phpbb-studio/) · phpBB [3Di](https://www.phpbb.com/community/memberlist.php?mode=viewprofile&u=177467) / [mrgoldy](https://www.phpbb.com/community/memberlist.php?mode=viewprofile&u=1114105) diff --git a/ext/phpbbstudio/aps/docs/images/aps.png b/ext/phpbbstudio/aps/docs/images/aps.png new file mode 100644 index 0000000..76f5c65 Binary files /dev/null and b/ext/phpbbstudio/aps/docs/images/aps.png differ diff --git a/ext/phpbbstudio/aps/docs/images/dice_rolls.png b/ext/phpbbstudio/aps/docs/images/dice_rolls.png new file mode 100644 index 0000000..881f732 Binary files /dev/null and b/ext/phpbbstudio/aps/docs/images/dice_rolls.png differ diff --git a/ext/phpbbstudio/aps/docs/images/highlight_posts.png b/ext/phpbbstudio/aps/docs/images/highlight_posts.png new file mode 100644 index 0000000..3373c3e Binary files /dev/null and b/ext/phpbbstudio/aps/docs/images/highlight_posts.png differ diff --git a/ext/phpbbstudio/aps/docs/images/subglobal_topic.png b/ext/phpbbstudio/aps/docs/images/subglobal_topic.png new file mode 100644 index 0000000..84c1ce3 Binary files /dev/null and b/ext/phpbbstudio/aps/docs/images/subglobal_topic.png differ diff --git a/ext/phpbbstudio/aps/docs/images/topic_cement.png b/ext/phpbbstudio/aps/docs/images/topic_cement.png new file mode 100644 index 0000000..141dd84 Binary files /dev/null and b/ext/phpbbstudio/aps/docs/images/topic_cement.png differ diff --git a/ext/phpbbstudio/aps/docs/images/topic_events.png b/ext/phpbbstudio/aps/docs/images/topic_events.png new file mode 100644 index 0000000..55c9cd2 Binary files /dev/null and b/ext/phpbbstudio/aps/docs/images/topic_events.png differ diff --git a/ext/phpbbstudio/aps/docs/images/who_read_what.png b/ext/phpbbstudio/aps/docs/images/who_read_what.png new file mode 100644 index 0000000..8b0267f Binary files /dev/null and b/ext/phpbbstudio/aps/docs/images/who_read_what.png differ diff --git a/ext/phpbbstudio/aps/docs/index.html b/ext/phpbbstudio/aps/docs/index.html new file mode 100644 index 0000000..ad5473b --- /dev/null +++ b/ext/phpbbstudio/aps/docs/index.html @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/ext/phpbbstudio/aps/event/acp.php b/ext/phpbbstudio/aps/event/acp.php new file mode 100644 index 0000000..86a46bb --- /dev/null +++ b/ext/phpbbstudio/aps/event/acp.php @@ -0,0 +1,266 @@ +acp = $acp; + $this->auth = $auth; + $this->config = $config; + $this->functions = $functions; + $this->helper = $helper; + $this->language = $language; + $this->log = $log; + $this->log_aps = $log_aps; + $this->request = $request; + $this->template = $template; + $this->user = $user; + } + + /** + * Assign functions defined in this class to event listeners in the core. + * + * @static + * @return array + * @access public + */ + static public function getSubscribedEvents() + { + return [ + 'core.acp_language_after_delete' => 'delete_name', + + 'core.acp_users_display_overview' => 'display_user', + + 'core.acp_manage_forums_display_form' => 'display_data', + 'core.acp_manage_forums_update_data_after' => 'request_data', + ]; + } + + /** + * Delete a localised points name upon language deletion. + * + * @event core.acp_language_after_delete + * @param \phpbb\event\data $event The event object + * @return void + * @access public + */ + public function delete_name(\phpbb\event\data $event) + { + $this->config->delete('aps_points_name_' . $event['lang_iso'], true); + } + + /** + * Display a user's points when managing a specific user. + * + * @event core.acp_users_display_overview + * @param \phpbb\event\data $event The event object + * @return void + * @access public + */ + public function display_user(\phpbb\event\data $event) + { + $this->template->assign_var('APS_POINTS', $event['user_row']['user_points']); + } + + /** + * Display a points list when adding/creating a forum. + * + * @event core.acp_manage_forums_display_form + * @param \phpbb\event\data $event The event object + * @return void + * @access public + */ + public function display_data(\phpbb\event\data $event) + { + $this->log_aps->load_lang(); + + // Only display a points list if the administrator is authorised to edit the points + if ($s_auth = $this->auth->acl_get('a_aps_points')) + { + // Build a points list for this forum + $this->acp->build((int) $event['forum_id']); + + // Request any action (ajax) + $action = $this->request->variable('aps_action', ''); + + // Ajaxify the copy points action + if (!empty($action) && $this->request->is_ajax()) + { + $json_response = new \phpbb\json_response; + + $forum_id = $this->request->variable('f', 0); + + switch ($action) + { + case 'copy': + $copy = $this->request->variable('aps_points_copy', 0); + + if (empty($copy)) + { + $json_response->send([ + 'MESSAGE_TITLE' => $this->language->lang('ERROR'), + 'MESSAGE_TEXT' => $this->language->lang('ACP_APS_POINTS_COPY_EMPTY_FROM'), + ]); + } + + $fields = $this->acp->get_fields(); + $fields = array_flip($fields[0]); + + $this->acp->copy_points($copy, $forum_id, $fields); + + $json_response->send([ + 'MESSAGE_TITLE' => $this->language->lang('INFORMATION'), + 'MESSAGE_TEXT' => $this->language->lang('ACP_APS_POINTS_COPY_SUCCESS', $this->functions->get_name()), + 'APS_VALUES' => $this->acp->assign_values($forum_id), + ]); + break; + + case 'reset': + if (confirm_box(true)) + { + $this->acp->delete_points($forum_id); + + $json_response->send([ + 'MESSAGE_TITLE' => $this->language->lang('INFORMATION'), + 'MESSAGE_TEXT' => $this->language->lang('ACP_APS_POINTS_RESET_SUCCESS', $this->functions->get_name()), + ]); + } + else + { + confirm_box(false, $this->language->lang('ACP_APS_POINTS_RESET_CONFIRM', $this->functions->get_name()), build_hidden_fields([ + 'aps_action' => $action, + 'forum_id' => $forum_id, + ])); + } + break; + } + } + } + + $this->template->assign_vars([ + 'S_APS_POINTS' => (bool) $s_auth, + 'U_APS_RESET' => $this->helper->get_current_url() . '&aps_action=reset', + ]); + } + + /** + * Request and set the points when adding/editing a forum. + * + * @event core.acp_manage_forums_update_data_after + * @param \phpbb\event\data $event The event object + * @return void + * @access public + */ + public function request_data(\phpbb\event\data $event) + { + $this->log_aps->load_lang(); + + // Only set the points when the administrator is authorised to edit the points + if (!$this->auth->acl_get('a_aps_points')) + { + return; + } + + $forum_id = !empty($event['forum_data']['forum_id']) ? (int) $event['forum_data']['forum_id'] : 0; + + $copy = $this->request->variable('aps_points_copy', 0); + $reset = $this->request->variable('aps_points_reset', 0); + $values = $this->request->variable('aps_values', ['' => 0.00]); + + if (!empty($reset) && !empty($forum_id)) + { + $this->acp->delete_points($forum_id); + + $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_ACP_APS_POINTS_RESET', time(), [$event['forum_data']['forum_name'], $this->functions->get_name()]); + } + else if (!empty($copy) && $copy != $forum_id) + { + $this->acp->copy_points($copy, $forum_id, $values); + + $forum_name = $this->functions->forum_name($copy); + + $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_ACP_APS_POINTS_COPIED', time(), [$forum_name, $event['forum_data']['forum_name'], $this->functions->get_name()]); + } + else + { + $this->acp->set_points($values, $forum_id); + } + } +} diff --git a/ext/phpbbstudio/aps/event/actions.php b/ext/phpbbstudio/aps/event/actions.php new file mode 100644 index 0000000..b19b546 --- /dev/null +++ b/ext/phpbbstudio/aps/event/actions.php @@ -0,0 +1,528 @@ +auth = $auth; + $this->config = $config; + $this->functions = $functions; + $this->manager = $manager; + $this->request = $request; + $this->user = $user; + + $this->root_path = $root_path; + $this->php_ext = $php_ext; + } + + /** + * Assign functions defined in this class to event listeners in the core. + * + * @static + * @return array + * @access public + */ + static public function getSubscribedEvents() + { + return [ + /* User actions */ + 'core.modify_posting_auth' => 'bump', + 'core.submit_post_end' => 'post', + 'core.delete_post_after' => 'post_delete', + 'core.viewtopic_modify_poll_ajax_data' => 'vote', + + /* Moderator actions */ + 'core.mcp_main_modify_fork_sql' => 'copy', + 'core.mcp_change_poster_after' => 'change', + 'core.delete_topics_before_query' => 'delete', + 'core.posting_modify_submit_post_before' => 'lock_and_type', + 'core.mcp_lock_unlock_after' => 'lock', + 'core.move_posts_before' => 'move_posts', + 'core.move_topics_before_query' => 'move_topics', + 'core.approve_posts_after' => 'queue', + 'core.approve_topics_after' => 'queue', + 'core.disapprove_posts_after' => 'queue', + 'core.mcp_forum_merge_topics_after' => 'merge', + + /* Global actions */ + 'core.ucp_register_register_after' => 'register', + 'core.mcp_warn_post_after' => 'warn', + 'core.mcp_warn_user_after' => 'warn', + 'core.submit_pm_after' => 'pm', + ]; + } + + /** + * Trigger Advanced Points System action: “bump”! + * + * @event core.modify_posting_auth + * @param \phpbb\event\data $event The event object + * @since 1.0.0 + * @return void + * @access public + */ + public function bump(\phpbb\event\data $event) + { + if ( + ($event['mode'] !== 'bump') + || + (!$event['is_authed'] || !empty($event['error']) || $event['post_data']['forum_type'] != FORUM_POST) + || + (($event['post_data']['forum_status'] == ITEM_LOCKED || (isset($event['post_data']['topic_status']) && $event['post_data']['topic_status'] == ITEM_LOCKED)) && !$this->auth->acl_get('m_edit', $event['forum_id'])) + ) + { + return; + } + + if ($bump_time = bump_topic_allowed($event['forum_id'], $event['post_data']['topic_bumped'], $event['post_data']['topic_last_post_time'], $event['post_data']['topic_poster'], $event['post_data']['topic_last_poster_id']) + && check_link_hash($this->request->variable('hash', ''), "topic_{$event['post_data']['topic_id']}")) + { + $this->manager->trigger('topic', $this->user->data['user_id'], $event, $event['forum_id']); + } + } + + /** + * Trigger Advanced Points System action: “post” or “topic”! + * + * @event core.submit_post_end + * @param \phpbb\event\data $event The event object + * @since 1.0.0 + * @return void + * @access public + */ + public function post(\phpbb\event\data $event) + { + if ($event['mode'] === 'edit' && $event['data']['poster_id'] != $this->user->data['user_id']) + { + $this->manager->trigger('edit', $event['data']['poster_id'], $event, $event['data']['forum_id']); + + return; + } + + switch ($event['mode']) + { + case 'edit': + $action = $event['data']['topic_first_post_id'] == $event['data']['post_id'] ? 'topic' : 'post'; + break; + + case 'post': + $action = 'topic'; + break; + + case 'reply': + case 'quote': + $action = 'post'; + break; + + default: + return; + break; + } + + $this->manager->trigger($action, $this->user->data['user_id'], $event, $event['data']['forum_id']); + } + + /** + * Trigger Advanced Points System action: “delete” or “post”! + * + * @event core.delete_post_after + * @param \phpbb\event\data $event The event object + * @since 1.0.0 + * @return void + * @access public + */ + public function post_delete(\phpbb\event\data $event) + { + if ($this->user->data['user_id'] == $event['data']['poster_id']) + { + $data = array_merge($this->manager->clean_event($event), [ + 'mode' => ($event['is_soft'] ? 'soft_' : '') . 'delete', + 'post_data' => ['topic_type' => POST_NORMAL], + ]); + + $this->manager->trigger('post', $event['data']['poster_id'], $data, $event['forum_id']); + } + else + { + $data = [ + 'action' => 'post', + 'is_soft' => $event['is_soft'], + 'posts' => [ + 0 => [ + 'forum_id' => $event['forum_id'], + 'topic_id' => $event['topic_id'], + 'post_id' => $event['post_id'], + 'poster_id' => $event['data']['poster_id'], + ], + ], + ]; + + $this->manager->trigger('delete', $event['data']['poster_id'], $data, $event['forum_id']); + } + } + + /** + * Trigger Advanced Points System action: “vote”! + * + * @event core.viewtopic_modify_poll_ajax_data + * @param \phpbb\event\data $event The event object + * @since 1.0.0 + * @return void + * @access public + */ + public function vote(\phpbb\event\data $event) + { + $this->manager->trigger('vote', $this->user->data['user_id'], $event, $event['forum_id']); + } + + /** + * Trigger Advanced Points System action: “copy”! + * + * @event core.mcp_main_modify_fork_sql + * @param \phpbb\event\data $event The event object + * @since 1.0.0 + * @return void + * @access public + */ + public function copy(\phpbb\event\data $event) + { + $this->manager->trigger('copy', $event['topic_row']['topic_poster'], $event, [(int) $event['topic_row']['forum_id'], (int) $event['sql_ary']['forum_id']]); + } + + /** + * Trigger Advanced Points System action: “change”! + * + * @event core.mcp_change_poster_after + * @param \phpbb\event\data $event The event object + * @since 1.0.0 + * @return void + * @access public + */ + public function change(\phpbb\event\data $event) + { + $this->manager->trigger('change', [$event['userdata']['user_id'], $event['post_info']['poster_id']], $event, $event['post_info']['forum_id']); + } + + /** + * Trigger Advanced Points System action: “delete”! + * + * @event core.delete_topics_before_query + * @param \phpbb\event\data $event The event object + * @since 1.0.0 + * @return void + * @access public + */ + public function delete(\phpbb\event\data $event) + { + // Check for chain triggering events + if (!$this->config['aps_chain_merge_delete'] && $this->request->variable('action', '', true) === 'merge_topics') + { + return; + } + + if (!function_exists('phpbb_get_topic_data')) + { + /** @noinspection PhpIncludeInspection */ + include $this->root_path . 'includes/functions_mcp.' . $this->php_ext; + } + + $topics = phpbb_get_topic_data($event['topic_ids']); + + $forum_ids = $this->manager->get_identifiers($topics, 'forum_id'); + $user_ids = $this->manager->get_identifiers($topics, 'topic_poster'); + + $data = array_merge($this->manager->clean_event($event), [ + 'action' => 'topic', + 'topics' => $topics, + ]); + + $this->manager->trigger('delete', $user_ids, $data, $forum_ids); + } + + /** + * Trigger Advanced Points System action: “lock” and/or “type”! + * + * @event core.posting_modify_submit_post_before + * @param \phpbb\event\data $event The event object + * @since 1.0.0 + * @return void + * @access public + */ + public function lock_and_type(\phpbb\event\data $event) + { + if ($event['mode'] === 'edit') + { + if ($this->user->data['user_id'] != $event['data']['poster_id']) + { + $row = $this->functions->topic_post_locked($event['data']['post_id']); + + if ($row['post_edit_locked'] != $event['data']['post_edit_locked']) + { + $data = array_merge($this->manager->clean_event($event), [ + 'action' => $event['data']['post_edit_locked'] ? 'lock_post' : 'unlock_post', + 'data' => [$event['data']], + ]); + + $this->manager->trigger('lock', $event['data']['poster_id'], $data, $event['data']['forum_id']); + } + + if ($row['topic_status'] != $event['data']['topic_status']) + { + $data = array_merge($this->manager->clean_event($event), [ + 'action' => $event['data']['topic_status'] ? 'unlock' : 'lock', + 'data' => [$event['data'] + ['topic_poster' => (int) $row['topic_poster']]], + ]); + + $this->manager->trigger('lock', $row['topic_poster'], $data, $event['data']['forum_id']); + } + } + + if ($event['post_data']['orig_topic_type'] != $event['post_data']['topic_type']) + { + $data = array_merge($this->manager->clean_event($event), [ + 'type_from' => $event['post_data']['orig_topic_type'], + 'type_to' => $event['post_data']['topic_type'], + ]); + + $this->manager->trigger('topic_type', $event['post_data']['topic_poster'], $data, $event['data']['forum_id']); + } + } + } + + /** + * Trigger Advanced Points System action: “lock”! + * + * @event core.mcp_lock_unlock_after + * @param \phpbb\event\data $event The event object + * @since 1.0.0 + * @return void + * @access public + */ + public function lock(\phpbb\event\data $event) + { + $s_user = in_array($event['action'], ['lock', 'unlock']) ? 'topic_poster' : 'poster_id'; + + $forum_ids = $this->manager->get_identifiers($event['data'], 'forum_id'); + $user_ids = $this->manager->get_identifiers($event['data'] , $s_user); + + $this->manager->trigger('lock', $user_ids, $event, $forum_ids); + } + + /** + * Trigger Advanced Points System action: “move”! + * + * @event core.move_posts_before + * @param \phpbb\event\data $event The event object + * @since 1.0.0 + * @return void + * @access public + */ + public function move_posts(\phpbb\event\data $event) + { + // Check for chain triggering events + if (!$this->config['aps_chain_merge_move'] && $this->request->variable('action', '', true) === 'merge_topics') + { + return; + } + + if (!function_exists('phpbb_get_topic_data')) + { + /** @noinspection PhpIncludeInspection */ + include $this->root_path . 'includes/functions_mcp.' . $this->php_ext; + } + + $posts = phpbb_get_post_data($event['post_ids']); + + $forum_ids = $this->manager->get_identifiers($posts, 'forum_id'); + $user_ids = $this->manager->get_identifiers($posts, 'poster_id'); + + $data = array_merge($this->manager->clean_event($event), [ + 'action' => 'post', + 'posts' => $posts, + ]); + + $this->manager->trigger('move', $user_ids, $data, $forum_ids); + } + + /** + * Trigger Advanced Points System action: “move”! + * + * @event core.move_topics_before_query + * @param \phpbb\event\data $event The event object + * @since 1.0.0 + * @return void + * @access public + */ + public function move_topics(\phpbb\event\data $event) + { + if (!function_exists('phpbb_get_topic_data')) + { + /** @noinspection PhpIncludeInspection */ + include $this->root_path . 'includes/functions_mcp.' . $this->php_ext; + } + + $topics = phpbb_get_topic_data($event['topic_ids']); + + $forum_ids = $this->manager->get_identifiers($topics, 'forum_id'); + $user_ids = $this->manager->get_identifiers($topics, 'topic_poster'); + + $data = array_merge($this->manager->clean_event($event), [ + 'action' => 'topic', + 'topics' => $topics, + ]); + + $this->manager->trigger('move', $user_ids, $data, $forum_ids); + } + + /** + * Trigger Advanced Points System action: “queue”! + * + * @event core.approve_posts_after + * @event core.approve_topics_after + * @event core.disapprove_posts_after + * @param \phpbb\event\data $event The event object + * @since 1.0.0 + * @return void + * @access public + */ + public function queue(\phpbb\event\data $event) + { + $data = array_merge($this->manager->clean_event($event), [ + 'mode' => isset($event['action']) ? $event['action'] : 'disapprove', + ]); + + $posts = isset($event['post_info']) ? $event['post_info'] : $event['topic_info']; + + $forum_ids = $this->manager->get_identifiers($posts, 'forum_id'); + $user_ids = $this->manager->get_identifiers($posts, 'poster_id'); + + $this->manager->trigger('queue', $user_ids, $data, $forum_ids); + } + + /** + * Trigger Advanced Points System action: “merge”! + * + * @event core.mcp_forum_merge_topics_after + * @param \phpbb\event\data $event The event object + * @since 1.0.0 + * @return void + * @access public + */ + public function merge(\phpbb\event\data $event) + { + $user_ids = $this->manager->get_identifiers($event['all_topic_data'], 'topic_poster'); + + $this->manager->trigger('merge', $user_ids, $event, $event['all_topic_data'][$event['to_topic_id']]['forum_id']); + } + + /** + * Trigger Advanced Points System action: “register”! + * + * @event core.ucp_register_register_after + * @param \phpbb\event\data $event The event object + * @since 1.0.0 + * @return void + * @access public + */ + public function register(\phpbb\event\data $event) + { + $this->manager->trigger('register', $event['user_id'], $event); + } + + /** + * Trigger Advanced Points System action: “warn”! + * + * @event core.mcp_warn_post_after + * @event core.mcp_warn_user_after + * @param \phpbb\event\data $event The event object + * @since 1.0.0 + * @return void + * @access public + */ + public function warn(\phpbb\event\data $event) + { + $this->manager->trigger('warn', $event['user_row']['user_id'], $event, 0); + } + + /** + * Trigger Advanced Points System action: “pm”! + * + * @event core.submit_pm_after + * @param \phpbb\event\data $event The event object + * @since 1.0.0 + * @return void + * @access public + */ + public function pm(\phpbb\event\data $event) + { + // Check for chain triggering events + if (!$this->config['aps_chain_warn_pm'] && $this->request->variable('mode', '', true) === 'warn_user') + { + return; + } + + $this->manager->trigger('pm', [], $event, 0); + } +} diff --git a/ext/phpbbstudio/aps/event/check.php b/ext/phpbbstudio/aps/event/check.php new file mode 100644 index 0000000..2a224af --- /dev/null +++ b/ext/phpbbstudio/aps/event/check.php @@ -0,0 +1,275 @@ +functions = $functions; + $this->language = $language; + $this->template = $template; + $this->user = $user; + $this->valuator = $valuator; + + $this->min = $config['aps_points_min'] !== '' ? (double) $config['aps_points_min'] : false; + } + + /** + * Assign functions defined in this class to event listeners in the core. + * + * @static + * @return array + * @access public + */ + static public function getSubscribedEvents() + { + return [ + 'core.viewtopic_modify_page_title' => 'check_bump', + 'core.handle_post_delete_conditions' => 'check_delete', + 'core.modify_posting_auth' => 'check_post', + 'core.viewtopic_modify_poll_data' => 'check_vote', + ]; + } + + /** + * Check the action: "Bump". + * + * @param \phpbb\event\data $event The event object + * @return void + * @access public + */ + public function check_bump(\phpbb\event\data $event) + { + if ($this->min === false) + { + return; + } + + // If there already is no bump link, return + if ($this->template->retrieve_var('U_BUMP_TOPIC') === '') + { + return; + } + + // Get the value + $value = $this->get_value('aps_bump', $event['forum_id']); + + // Check if the value is negative + if ($value >= 0) + { + return; + } + + if ($this->below_min($value)) + { + $this->template->assign_var('U_BUMP_TOPIC', ''); + } + } + + /** + * Check the action: "Delete". + * + * @param \phpbb\event\data $event The event object + * @return void + * @access public + */ + public function check_delete(\phpbb\event\data $event) + { + if ($this->min === false) + { + return; + } + + if (confirm_box(true)) + { + $data = $this->functions->post_data($event['post_id']); + + if ($this->user->data['user_id'] != $data['poster_id']) + { + return; + } + + $field = 'aps_post_delete' . ($event['is_soft'] ? '_soft' : ''); + + if ($value = $this->check_value($field, $event['forum_id'])) + { + $event['error'] = array_merge($event['error'], [ + $this->language->lang('APS_POINTS_TOO_LOW', $this->functions->get_name()) . '
    ' . + $this->language->lang('APS_POINTS_ACTION_COST', $this->functions->display_points($value)) + ]); + } + } + } + + /** + * Check the action: "Post" and "Topic". + * + * @param \phpbb\event\data $event The event object + * @return void + * @access public + */ + public function check_post(\phpbb\event\data $event) + { + if ($this->min === false) + { + return; + } + + switch ($event['mode']) + { + default: + return; + break; + + case 'post': + $field = 'aps_topic_base'; + break; + + case 'reply': + case 'quote': + $field = 'aps_post_base'; + break; + + case 'edit': + $data = $this->functions->post_data($event['post_id']); + + if ($this->user->data['user_id'] != $data['poster_id']) + { + return; + } + + $type = $data['topic_first_post_id'] == $event['post_id'] ? 'topic' : 'post'; + $field = 'aps_' . $type . '_edit'; + break; + } + + if ($value = $this->check_value($field, $event['forum_id'])) + { + $event['error'] = array_merge($event['error'], [ + $this->language->lang('APS_POINTS_TOO_LOW', $this->functions->get_name()) . '
    ' . + $this->language->lang('APS_POINTS_ACTION_COST', $this->functions->display_points($value)) + ]); + } + } + + /** + * Check the action: "Vote". + * + * @param \phpbb\event\data $event The event object + * @return void + * @access public + */ + public function check_vote(\phpbb\event\data $event) + { + if ($this->min === false) + { + return; + } + + if ($this->check_value('aps_vote', $event['forum_id'])) + { + $event['s_can_vote'] = false; + } + } + + /** + * Verify the user has enough points to perform an action. + * + * @param int $field The points field + * @param int $forum_id The forum identifier + * @return double|false The points value + * @access protected + */ + protected function check_value($field, $forum_id) + { + $value = $this->get_value($field, $forum_id); + + $check = $value < 0 && $this->below_min($value) ? $value : false; + + return $check; + } + + /** + * Get the base value for a points action. + * + * @param int $field The points field + * @param int $forum_id The forum identifier + * @return double The points value + * @access protected + */ + protected function get_value($field, $forum_id) + { + $fields = [0 => [$field]]; + + $values = $this->valuator->get_points($fields, $forum_id, false); + + $value = isset($values[$forum_id][$field]) ? $values[$forum_id][$field] : 0.00; + + return (double) $value; + } + + /** + * Check whether or not the value is below the points minimum. + * + * @param double $value The points value + * @return bool Whether or not the value is below the minimum + * @access protected + */ + protected function below_min($value) + { + $points = $this->functions->equate_points($this->user->data['user_points'], $value); + + return (bool) ($points < $this->min); + } +} diff --git a/ext/phpbbstudio/aps/event/display.php b/ext/phpbbstudio/aps/event/display.php new file mode 100644 index 0000000..7ccce18 --- /dev/null +++ b/ext/phpbbstudio/aps/event/display.php @@ -0,0 +1,200 @@ +functions = $functions; + $this->helper = $helper; + $this->language = $language; + $this->template = $template; + + $this->php_ext = $php_ext; + } + + /** + * Assign functions defined in this class to event listeners in the core. + * + * @static + * @return array + * @access public + */ + static public function getSubscribedEvents() + { + return [ + 'core.user_setup' => 'load_lang', + 'core.page_header_after' => 'display_links', + + 'core.viewonline_overwrite_location' => 'view_online', + + 'core.ucp_pm_view_message' => 'display_pm', + 'core.viewtopic_post_rowset_data' => 'set_post', + 'core.viewtopic_cache_user_data' => 'cache_post', + 'core.viewtopic_modify_post_row' => 'display_post', + 'core.memberlist_prepare_profile_data' => 'display_profile', + ]; + } + + /** + * Load language during user set up. + * + * @event core.user_setup + * @return void + * @access public + */ + public function load_lang() + { + $this->language->add_lang('aps_common', 'phpbbstudio/aps'); + } + + public function display_links() + { + $locations = array_filter($this->functions->get_link_locations()); + + if ($locations) + { + $this->template->assign_vars(array_combine(array_map(function($key) { + return 'S_APS_' . strtoupper($key); + }, array_keys($locations)), $locations)); + } + } + + /** + * Display the points page when viewing the Who is Online page. + * + * @event core.viewonline_overwrite_location + * @param \phpbb\event\data $event The event object + * @return void + * @access public + */ + public function view_online(\phpbb\event\data $event) + { + if ($event['on_page'][1] === 'app' && strrpos($event['row']['session_page'], 'app.' . $this->php_ext . '/points') === 0) + { + $event['location'] = $this->language->lang('APS_VIEWING_POINTS_PAGE', $this->functions->get_name()); + $event['location_url'] = $this->helper->route('phpbbstudio_aps_display'); + } + } + + /** + * Display the user points when viewing a Private Message. + * + * @event core.ucp_pm_view_message + * @param \phpbb\event\data $event The event object + * @return void + * @access public + */ + public function display_pm(\phpbb\event\data $event) + { + $event['msg_data'] = array_merge($event['msg_data'], [ + 'AUTHOR_POINTS' => $event['user_info']['user_points'], + ]); + } + + /** + * Set the user points after being retrieved from the database. + * + * @event core.viewtopic_post_rowset_data + * @param \phpbb\event\data $event The event object + * @return void + * @access public + */ + public function set_post(\phpbb\event\data $event) + { + $event['rowset_data'] = array_merge($event['rowset_data'], [ + 'user_points' => $event['row']['user_points'], + ]); + } + + /** + * Cache the user points when displaying a post. + * + * @event core.viewtopic_cache_user_data + * @param \phpbb\event\data $event The event object + * @return void + * @access public + */ + public function cache_post(\phpbb\event\data $event) + { + $event['user_cache_data'] = array_merge($event['user_cache_data'], [ + 'user_points' => $event['row']['user_points'], + ]); + } + + /** + * Display the user points when displaying a post. + * + * @event core.viewtopic_modify_post_row + * @param \phpbb\event\data $event The event object + * @return void + * @access public + */ + public function display_post(\phpbb\event\data $event) + { + $event['post_row'] = array_merge($event['post_row'], [ + 'POSTER_POINTS' => $event['user_cache'][$event['poster_id']]['user_points'], + ]); + } + + /** + * Display the user points when display a profile. + * + * @event core.memberlist_prepare_profile_data + * @param \phpbb\event\data $event The event object + * @return void + * @access public + */ + public function display_profile(\phpbb\event\data $event) + { + $event['template_data'] = array_merge($event['template_data'], [ + 'USER_POINTS' => $event['data']['user_points'], + ]); + } +} diff --git a/ext/phpbbstudio/aps/event/modules.php b/ext/phpbbstudio/aps/event/modules.php new file mode 100644 index 0000000..998c253 --- /dev/null +++ b/ext/phpbbstudio/aps/event/modules.php @@ -0,0 +1,83 @@ +functions = $functions; + $this->language = $language; + } + + /** + * Assign functions defined in this class to event listeners in the core. + * + * @static + * @return array + * @access public + */ + static public function getSubscribedEvents() + { + return [ + 'core.modify_module_row' => 'module_names', + ]; + } + + /** + * Localise the APS module titles. + * + * @event core.modify_module_row + * @param \phpbb\event\data $event The event object + * @return void + * @access public + */ + public function module_names(\phpbb\event\data $event) + { + $module = $event['module_row']; + + $langname = $module['langname']; + + switch ($langname) + { + case 'ACP_APS_MODE_POINTS': + case 'MCP_APS_POINTS': + case 'UCP_APS_POINTS': + $module['lang'] = $this->language->lang($langname, ucfirst($this->functions->get_name())); + break; + + default: + return; + break; + } + + $event['module_row'] = $module; + } +} diff --git a/ext/phpbbstudio/aps/event/permissions.php b/ext/phpbbstudio/aps/event/permissions.php new file mode 100644 index 0000000..598a42b --- /dev/null +++ b/ext/phpbbstudio/aps/event/permissions.php @@ -0,0 +1,67 @@ + 'permissions', + ]; + } + + /** + * Add Advanced Points System permissions + * + * @event core.permissions + * @param \phpbb\event\data $event The event object + * @return void + * @access public + */ + public function permissions(\phpbb\event\data $event) + { + $categories = $event['categories']; + $permissions = $event['permissions']; + + if (empty($categories['phpbb_studio'])) + { + $categories['phpbb_studio'] = 'ACL_CAT_PHPBB_STUDIO'; + + $event['categories'] = $categories; + } + + $perms = [ + 'a_aps_logs', 'a_aps_points', 'a_aps_reasons', 'a_aps_display', 'a_aps_settings', + 'm_aps_adjust_custom', 'm_aps_adjust_reason', + 'u_aps_view_build', 'u_aps_view_build_other', 'u_aps_view_logs', 'u_aps_view_logs_other', 'u_aps_view_mod', + ]; + + foreach ($perms as $permission) + { + $permissions[$permission] = ['lang' => 'ACL_' . utf8_strtoupper($permission), 'cat' => 'phpbb_studio']; + } + + $event['permissions'] = $permissions; + } +} diff --git a/ext/phpbbstudio/aps/ext.php b/ext/phpbbstudio/aps/ext.php new file mode 100644 index 0000000..28f9032 --- /dev/null +++ b/ext/phpbbstudio/aps/ext.php @@ -0,0 +1,159 @@ +=') && phpbb_version_compare(PHPBB_VERSION, '4.0.0@dev', '<'))) + { + if (phpbb_version_compare(PHPBB_VERSION, '3.3.0@dev', '<')) + { + $user = $this->container->get('user'); + $user->add_lang_ext('phpbbstudio/aps', 'aps_ext'); + + $lang = $user->lang; + + $lang['EXTENSION_NOT_ENABLEABLE'] .= '
    ' . $user->lang('APS_PHPBB_VERSION', '3.2.8', '4.0.0@dev'); + + $user->lang = $lang; + + return false; + } + + if (phpbb_version_compare(PHPBB_VERSION, '3.3.0@dev', '>')) + { + $language= $this->container->get('language'); + $language->add_lang('aps_ext', 'phpbbstudio/aps'); + + return $language->lang('APS_PHPBB_VERSION', '3.2.8', '4.0.0@dev'); + } + } + + /** + * Now if Ultimate Points is enabled already. + */ + $ext_manager = $this->container->get('ext.manager'); + $is_ups_enabled = $ext_manager->is_enabled('dmzx/ultimatepoints'); + + if ($is_ups_enabled) + { + if (phpbb_version_compare(PHPBB_VERSION, '3.3.0@dev', '<')) + { + $user = $this->container->get('user'); + $user->add_lang_ext('phpbbstudio/aps', 'aps_ext'); + + $lang = $user->lang; + + $lang['EXTENSION_NOT_ENABLEABLE'] .= '
    ' . $user->lang('APS_UP_INSTALLED'); + + $user->lang = $lang; + + return false; + } + + if (phpbb_version_compare(PHPBB_VERSION, '3.3.0@dev', '>')) + { + $language= $this->container->get('language'); + $language->add_lang('aps_ext', 'phpbbstudio/aps'); + + return $language->lang('APS_UP_INSTALLED'); + } + } + + return true; + } + + /** + * Enable notifications for the extension + * + * @param mixed $old_state State returned by previous call of this method + * + * @return mixed Returns false after last step, otherwise temporary state + */ + public function enable_step($old_state) + { + if ($old_state === false) + { + $this->container->get('notification_manager') + ->enable_notifications('phpbbstudio.aps.notification.type.adjust'); + + return 'notification'; + } + + return parent::enable_step($old_state); + } + + /** + * Disable notifications for the extension + * + * @param mixed $old_state State returned by previous call of this method + * + * @return mixed Returns false after last step, otherwise temporary state + */ + public function disable_step($old_state) + { + if ($old_state === false) + { + try + { + if ($this->container->hasParameter('phpbbstudio.aps.extended')) + { + $language = $this->container->get('language'); + $language->add_lang('aps_ext', 'phpbbstudio/aps'); + + $message = $language->lang('APS_DISABLE_EXTENDED', $this->container->getParameter('phpbbstudio.aps.extended')); + + // Trigger error for the ACP + @trigger_error($message, E_USER_WARNING); + + // Throw an exception for the CLI + throw new \RuntimeException($message); + } + } + catch (\InvalidArgumentException $e) + { + // Continue + } + + $this->container->get('notification_manager') + ->disable_notifications('phpbbstudio.aps.notification.type.adjust'); + + return 'notification'; + } + + return parent::disable_step($old_state); + } + + /** + * Purge notifications for the extension + * + * @param mixed $old_state State returned by previous call of this method + * + * @return mixed Returns false after last step, otherwise temporary state + */ + public function purge_step($old_state) + { + if ($old_state === false) + { + $this->container->get('notification_manager') + ->purge_notifications('phpbbstudio.aps.notification.type.adjust'); + + return 'notification'; + } + + return parent::purge_step($old_state); + } +} diff --git a/ext/phpbbstudio/aps/language/en/aps_acp_common.php b/ext/phpbbstudio/aps/language/en/aps_acp_common.php new file mode 100644 index 0000000..c2b3a51 --- /dev/null +++ b/ext/phpbbstudio/aps/language/en/aps_acp_common.php @@ -0,0 +1,148 @@ + 'This lists all %s actions across the board. There are various sorting and searching options available.', + + 'ACP_APS_LOGS_DELETED' => [ + 1 => 'You have successfully deleted the log entry.', + 2 => 'You have successfully deleted the log entries.', + ], + + // Points mode + 'ACP_APS_POINTS_EXPLAIN' => 'Here you can set %1$s values for global actions. You can also manage the preset reasons used in adjusting a user’s %1$s.', + 'ACP_APS_POINTS_SUCCESS' => 'Advanced Points System %s updated successfully.', + + 'ACP_APS_REASON_ADD' => 'Add reason', + 'ACP_APS_REASON_EDIT' => 'Edit reason', + 'ACP_APS_REASON_DELETE' => 'Delete reason', + 'ACP_APS_REASON_DELETE_CONFIRM' => 'Are you sure you wish to delete this reason?', + 'ACP_APS_REASON_DELETE_SUCCESS' => 'You have successfully deleted this reason.', + 'ACP_APS_REASON_SAVED' => 'The reason has successfully been saved.', + + 'ACP_APS_REASON_EMPTY_SUBJECT' => 'The reason subject can not be empty.', + 'ACP_APS_REASON_EMPTY_POINTS' => 'The reason %s can not be empty.', + + // Display mode + 'ACP_APS_DISPLAY_EXPLAIN' => 'Here you can determine the availability of display blocks and define some functionality.', + 'ACP_APS_DISPLAY_SUCCESS' => 'Advanced Points System display settings updated successfully.', + + 'ACP_APS_DISPLAY_TOP_COUNT' => 'Top users count', + 'ACP_APS_DISPLAY_TOP_COUNT_DESC' => 'The default amount of users to show for the “Top users” block.', + 'ACP_APS_DISPLAY_TOP_CHANGE' => 'Allow changing top user count', + 'ACP_APS_DISPLAY_TOP_CHANGE_DESC' => 'Whether users are allowed to increase the “Top users” count.', + 'ACP_APS_DISPLAY_ADJUSTMENTS' => 'Adjustments count', + 'ACP_APS_DISPLAY_ADJUSTMENTS_DESC' => 'The default amount of adjustments to show for the “Recent adjustments” block.', + 'ACP_APS_DISPLAY_GRAPH_TIME' => 'Graph animation time', + 'ACP_APS_DISPLAY_GRAPH_TIME_DESC' => 'The default animation time in milliseconds when displaying graph blocks.', + + // Settings mode + 'ACP_APS_SETTINGS_EXPLAIN' => 'Here you can determine the basic %1$s settings of your board, give it a fitting name and formatting, and among other settings adjust the default values for minimum and maximum %1$s.', + 'ACP_APS_SETTINGS_SUCCESS' => 'Advanced Points System settings updated successfully.', + + 'ACP_APS_POINTS_CLEAN' => 'Clean %s table', + 'ACP_APS_POINTS_CLEAN_CONFIRM' => 'Are you sure you wish to clean the %s table?', + 'ACP_APS_POINTS_CLEAN_SUCCESS' => 'You have successfully cleaned the %s table.', + + 'ACP_APS_POINTS_COPY_EMPTY' => 'You need to select at least 1 “from” forum and one “to” forum.', + 'ACP_APS_POINTS_COPY_TO' => 'Copy %s to', + + 'ACP_APS_POINTS_DECIMALS' => 'Decimal amount', + + 'ACP_APS_POINTS_DISPLAY_PM' => 'Display on view private message page', + 'ACP_APS_POINTS_DISPLAY_PM_DESC' => 'Should %s be displayed in the mini-profile on the private message page.', + 'ACP_APS_POINTS_DISPLAY_POST' => 'Display on viewtopic page', + 'ACP_APS_POINTS_DISPLAY_POST_DESC' => 'Should %s be displayed in the mini-profile on the topic page.', + 'ACP_APS_POINTS_DISPLAY_PROFILE' => 'Display on profile page', + 'ACP_APS_POINTS_DISPLAY_PROFILE_DESC' => 'Should %s be displayed in a user’s profile page.', + + 'ACP_APS_POINTS_EXCLUDE_CHARS' => 'Exclude characters', + 'ACP_APS_POINTS_EXCLUDE_CHARS_DESC' => 'This will not count the characters from the excluded words when calculating %s.', + 'ACP_APS_POINTS_EXCLUDE_WORDS' => 'Exclude words', + 'ACP_APS_POINTS_EXCLUDE_WORDS_DESC' => 'This will not count the words with equal or less than X characters when calculating %s.', + + 'ACP_APS_POINTS_ICON' => 'Icon', + 'ACP_APS_POINTS_ICON_IMG' => 'Icon image', + 'ACP_APS_POINTS_ICON_IMG_DESC' => 'Setting an image will override the icon selected above.
    Images can be selected from the /images folder.', + 'ACP_APS_POINTS_ICON_IMG_NO' => 'No image', + 'ACP_APS_POINTS_ICON_POSITION' => 'Icon position', + 'ACP_APS_POINTS_ICON_POSITION_LEFT' => 'Left', + 'ACP_APS_POINTS_ICON_POSITION_RIGHT' => 'Right', + + 'ACP_APS_POINTS_MIN' => 'Minimum user %s', + 'ACP_APS_POINTS_MIN_DESC' => 'If set, users’ %s can not go lower than this amount.', + 'ACP_APS_POINTS_MAX' => 'Maximum user %s', + 'ACP_APS_POINTS_MAX_DESC' => 'If set, users’ %s can not go higher than this amount.', + + 'ACP_APS_POINTS_NAMES' => 'Points names', + + 'ACP_APS_POINTS_PER_PAGE' => '%s actions per page', + 'ACP_APS_POINTS_PER_PAGE_DESC' => 'The amount of %s actions that should be displayed per page.', + + 'ACP_APS_POINTS_SAFE_MODE' => 'Safe mode', + 'ACP_APS_POINTS_SAFE_MODE_DESC' => 'Turning this on will catch and log any errors during point calculations.
    When testing and developing custom actions this should be turned off.', + + 'ACP_APS_FORMATTING' => 'Formatting', + + 'ACP_APS_IGNORE_SETTINGS' => 'Ignore settings', + 'ACP_APS_IGNORE_CRITERIA' => 'Ignore criteria', + 'ACP_APS_IGNORE_CRITERIA_DESC' => 'What criteria should checked to see if a post will not receive points.', + 'ACP_APS_IGNORE_MIN_CHARS' => 'Minimum characters', + 'ACP_APS_IGNORE_MIN_CHARS_DESC' => 'Posts with less characters than this will not receive points.', + 'ACP_APS_IGNORE_MIN_WORDS' => 'Minimum words', + 'ACP_APS_IGNORE_MIN_WORDS_DESC' => 'Posts with less words than this will not receive points.', + 'ACP_APS_IGNORE_EXCLUDED_CHARS' => 'Ignore excluded characters', + 'ACP_APS_IGNORE_EXCLUDED_CHARS_DESC' => 'Do not count the “excluded characters” towards the “minimum characters” criteria.', + 'ACP_APS_IGNORE_EXCLUDED_WORDS' => 'Ignore excluded words', + 'ACP_APS_IGNORE_EXCLUDED_WORDS_DESC' => 'Do not count the “excluded words” towards the “minimum words” criteria.', + 'ACP_APS_IGNORE_BOTH' => 'Both', + 'ACP_APS_IGNORE_NONE' => 'None', + 'ACP_APS_IGNORE_CHARS' => 'Chars', + 'ACP_APS_IGNORE_WORDS' => 'Words', + + 'ACP_APS_CHAIN_SETTINGS' => 'Chain settings', + 'ACP_APS_CHAIN_MERGE_DELETE' => 'When “merging” also trigger “delete”', + 'ACP_APS_CHAIN_MERGE_DELETE_DESC' => 'If a topic is merged into an other, the initial topic will be deleted.
    This determines if %s should be calculated for the delete action.', + 'ACP_APS_CHAIN_MERGE_MOVE' => 'When “merging” also trigger “move”', + 'ACP_APS_CHAIN_MERGE_MOVE_DESC' => 'If a topic is merged into an other, the initial topic’s posts will be moved.
    This determines if %s should be calculated for the move action.', + 'ACP_APS_CHAIN_WARN_PM' => 'When “warning” also trigger “pm”', + 'ACP_APS_CHAIN_WARN_PM_DESC' => 'If a user is warned and “Notify user” is checked, a private message is send.
    This determines if %s should be calculated for the private message action.', + + 'ACP_APS_CHARACTERS' => 'character(s)', + + 'ACP_APS_SEPARATOR_DEC' => 'Decimal separator', + 'ACP_APS_SEPARATOR_THOU' => 'Thousands separator', + 'ACP_APS_SEPARATOR_COMMA' => 'Comma', + 'ACP_APS_SEPARATOR_PERIOD' => 'Period', + 'ACP_APS_SEPARATOR_DASH' => 'Dash', + 'ACP_APS_SEPARATOR_UNDERSCORE' => 'Underscore', + 'ACP_APS_SEPARATOR_SPACE' => 'Space', + 'ACP_APS_SEPARATOR_SPACE_NARROW' => 'Narrow space', + + // Locations + 'ACP_APS_LOCATIONS' => 'Link locations', + 'ACP_APS_LOCATIONS_DESC' => 'Determine where the link to the Points page should be displayed.', + 'ACP_APS_LOCATIONS_EXPLAIN' => 'This is an example of a board index. In here you can select where you want the link to show up.
    You can select as many locations as you like, from nowhere to at all places.', + 'ACP_APS_LOCATIONS_SUCCESS' => 'You have successfully updated the link locations.', +]); diff --git a/ext/phpbbstudio/aps/language/en/aps_common.php b/ext/phpbbstudio/aps/language/en/aps_common.php new file mode 100644 index 0000000..9e51f1d --- /dev/null +++ b/ext/phpbbstudio/aps/language/en/aps_common.php @@ -0,0 +1,30 @@ + 'Your %1$s were adjusted', + 'APS_VIEWING_POINTS_PAGE' => 'Viewing the %s page', + + 'APS_POINTS_TOO_LOW' => 'You do not have enough %s to perform this action.', + 'APS_POINTS_ACTION_COST' => 'The cost of this action is %s', +]); diff --git a/ext/phpbbstudio/aps/language/en/aps_display.php b/ext/phpbbstudio/aps/language/en/aps_display.php new file mode 100644 index 0000000..9485cad --- /dev/null +++ b/ext/phpbbstudio/aps/language/en/aps_display.php @@ -0,0 +1,73 @@ + 'Overview', + 'APS_SUCCESS' => 'Success', + 'APS_TOP_USERS' => 'Top users', + + 'APS_ADJUST_USER_POINTS' => 'Adjust user %s', + + 'APS_PURCHASE_POINTS' => 'Purchase %s', + + 'APS_POINTS_ACTION' => '%s action', + 'APS_POINTS_ACTION_SEARCH' => 'Search %s action', + 'APS_POINTS_ACTION_TIME' => '%s action time', + 'APS_POINTS_ACTIONS' => '%s actions', + 'APS_POINTS_ACTIONS_ALL' => 'All %s actions', + 'APS_POINTS_ACTIONS_NONE' => 'There are no %s actions yet.', + 'APS_POINTS_ACTIONS_PAGE' => 'Actions per page', + 'APS_POINTS_ACTIONS_TOTAL' => [ + 1 => '%2$d %1$s action', + 2 => '%2$d %1$s actions', + ], + + 'APS_POINTS_BLOCK_ADD' => '%s block was added!', + 'APS_POINTS_BLOCK_DELETE' => '%s block was removed!', + 'APS_POINTS_BLOCK_MOVE' => '%s block was moved!', + 'APS_POINTS_BLOCK_NO' => 'No blocks', + 'APS_POINTS_BLOCK_NONE' => 'It looks like you do not have any blocks added.', + 'APS_POINTS_BLOCK_NO_CONTENT' => 'Oops! Looks like something went wrong.
    This block does not have any content!

    The required {% block content %}...{% endblock %} is missing!', + + 'APS_POINTS_FORMAT' => '%s format', + + 'APS_POINTS_MAX' => 'Maximum %s', + 'APS_POINTS_MIN' => 'Minimum %s', + + 'APS_POINTS_NAME' => 'Name', + + 'APS_POINTS_DATA_EMPTY' => 'No %s data to display', + 'APS_POINTS_GAINED' => '%s gained', + 'APS_POINTS_GLOBAL' => 'Global', + 'APS_POINTS_GROWTH' => '%s growth', + 'APS_POINTS_LOST' => '%s lost', + 'APS_POINTS_TRADE_OFF' => '%s trade off', + 'APS_POINTS_PER_FORUM' => '%s per forum', + 'APS_POINTS_PER_GROUP' => '%s per group', + + 'APS_RANDOM_USER' => 'Random user', + + 'APS_RECENT_ADJUSTMENTS' => 'Recent adjustments', + 'APS_RECENT_ATTACHMENTS' => 'Recent attachments', + 'APS_RECENT_POLL' => 'Recent poll', +]); diff --git a/ext/phpbbstudio/aps/language/en/aps_ext.php b/ext/phpbbstudio/aps/language/en/aps_ext.php new file mode 100644 index 0000000..fe9ea3d --- /dev/null +++ b/ext/phpbbstudio/aps/language/en/aps_ext.php @@ -0,0 +1,28 @@ + 'Disabling the “Advanced Points System” is not possible as it is still being extended by an other extension. Extension name: “%s”', + 'APS_PHPBB_VERSION' => 'Minimum phpBB version required is %1$s but less than %2$s', + 'APS_UP_INSTALLED' => 'The extension “dmzx/ultimatepoints” is not compatible with this one!', +]); diff --git a/ext/phpbbstudio/aps/language/en/info_acp_aps.php b/ext/phpbbstudio/aps/language/en/info_acp_aps.php new file mode 100644 index 0000000..3370181 --- /dev/null +++ b/ext/phpbbstudio/aps/language/en/info_acp_aps.php @@ -0,0 +1,49 @@ + 'Advanced Points System', + 'ACP_APS_MODE_SETTINGS' => 'Settings', + 'ACP_APS_MODE_DISPLAY' => 'Display', + 'ACP_APS_MODE_POINTS' => '%s', + 'ACP_APS_MODE_LOGS' => 'Logs', + + // ACP log + 'LOG_ACP_APS_SETTINGS' => 'Altered APS settings', + 'LOG_ACP_APS_DISPLAY' => 'Altered APS display settings', + 'LOG_ACP_APS_POINTS' => 'Altered APS %s', + 'LOG_ACP_APS_POINTS_COPIED' => 'Copied APS %3$s for %2$s
    from %1$s', + 'LOG_ACP_APS_POINTS_RESET' => 'Reset APS %2$s
    » %1$s', + 'LOG_ACP_APS_LOCATIONS' => 'Updated APS link locations', + 'LOG_ACP_APS_LOGS_CLEARED' => 'Cleared APS %s actions', + 'LOG_ACP_APS_LOGS_DELETED' => 'Deleted APS %s actions', + 'LOG_ACP_APS_COPIED' => 'Copied APS %s to multiple forums', + 'LOG_ACP_APS_CLEANED' => 'Cleaned the APS %s table', + + 'LOG_ACP_APS_REASON_ADD' => 'Added an APS reason', + 'LOG_ACP_APS_REASON_EDIT' => 'Edited an APS reason', + 'LOG_ACP_APS_REASON_DELETE' => 'Deleted an APS reason', + + 'LOG_ACP_APS_CALCULATION_ERROR' => 'APS - There was an error calculating the %4$s
    Error: %1$s
    File: %2$s
    Line: %3$s', +]); diff --git a/ext/phpbbstudio/aps/language/en/info_mcp_aps.php b/ext/phpbbstudio/aps/language/en/info_mcp_aps.php new file mode 100644 index 0000000..4797242 --- /dev/null +++ b/ext/phpbbstudio/aps/language/en/info_mcp_aps.php @@ -0,0 +1,39 @@ + '%s', + 'MCP_APS_CHANGE' => 'Change', + 'MCP_APS_FRONT' => 'Front', + 'MCP_APS_LOGS' => 'Logs', + + 'MCP_APS_LATEST_ADJUSTED' => 'Latest %d adjustments', + 'MCP_APS_USERS_TOP' => 'Top %d users', + 'MCP_APS_USERS_BOTTOM' => 'Bottom %d users', + + 'MCP_APS_POINTS_CURRENT' => 'Current %s', + 'MCP_APS_POINTS_CHANGE' => 'Change %s', + + 'MCP_APS_POINTS_USER_CHANGE' => 'Are you sure you want to adjust this user’s %s?', + 'MCP_APS_POINTS_USER_CHANGE_SUCCESS' => 'The %s for this user have successfully been adjusted.', +]); diff --git a/ext/phpbbstudio/aps/language/en/info_ucp_aps.php b/ext/phpbbstudio/aps/language/en/info_ucp_aps.php new file mode 100644 index 0000000..e5c3200 --- /dev/null +++ b/ext/phpbbstudio/aps/language/en/info_ucp_aps.php @@ -0,0 +1,26 @@ + '%s', +]); diff --git a/ext/phpbbstudio/aps/language/en/permissions_aps.php b/ext/phpbbstudio/aps/language/en/permissions_aps.php new file mode 100644 index 0000000..dcc182b --- /dev/null +++ b/ext/phpbbstudio/aps/language/en/permissions_aps.php @@ -0,0 +1,41 @@ + 'phpBB Studio', + + 'ACL_A_APS_LOGS' => 'Advanced Points System - Can manage the logs', + 'ACL_A_APS_POINTS' => 'Advanced Points System - Can manage the points', + 'ACL_A_APS_REASONS' => 'Advanced Points System - Can manage the reasons', + 'ACL_A_APS_DISPLAY' => 'Advanced Points System - Can manage the display', + 'ACL_A_APS_SETTINGS' => 'Advanced Points System - Can manage the settings', + + 'ACL_M_APS_ADJUST_CUSTOM' => 'Advanced Points System - Can adjust a user’s points with a custom action', + 'ACL_M_APS_ADJUST_REASON' => 'Advanced Points System - Can adjust a user’s points with a predefined reason', + + 'ACL_U_APS_VIEW_BUILD' => 'Advanced Points System - Can view their augmentation
    Augmentation is the “build up” of the total points.', + 'ACL_U_APS_VIEW_BUILD_OTHER' => 'Advanced Points System - Can view other users’ augmentation
    This requires “Can view their augmentation” to be set to Yes.', + 'ACL_U_APS_VIEW_MOD' => 'Advanced Points System - Can view the moderator', + 'ACL_U_APS_VIEW_LOGS' => 'Advanced Points System - Can view their logs', + 'ACL_U_APS_VIEW_LOGS_OTHER' => 'Advanced Points System - Can view other users’ logs
    This requires “Can view their logs” to be set to Yes.', +]); diff --git a/ext/phpbbstudio/aps/language/en/phpbbstudio_aps_actions.php b/ext/phpbbstudio/aps/language/en/phpbbstudio_aps_actions.php new file mode 100644 index 0000000..8c8e371 --- /dev/null +++ b/ext/phpbbstudio/aps/language/en/phpbbstudio_aps_actions.php @@ -0,0 +1,155 @@ + 'Copy %s from', + 'ACP_APS_POINTS_COPY_EMPTY_FROM' => 'You have to select a “from” forum', + 'ACP_APS_POINTS_COPY_EXPLAIN' => 'If you select to copy %1$s, the forum will have the same %1$s as the one you select here. This will overwrite any %1$s you have previously set for this forum with the %1$s of the forum you select here. If no forum is selected, the current %1$s will be kept.', + 'ACP_APS_POINTS_COPY_NOT' => 'Do not copy %s', + 'ACP_APS_POINTS_COPY_SUCCESS' => 'You have successfully copied the %s.', + 'ACP_APS_POINTS_COPY_TITLE' => 'Copy %s', + + 'ACP_APS_POINTS_RESET' => 'Reset %s', + 'ACP_APS_POINTS_RESET_CONFIRM' => 'Are you sure you wish to to reset the %s for this forum?', + 'ACP_APS_POINTS_RESET_EXPLAIN' => 'If you select to reset %1$s, all values for this forum will be set to 0. This will overwrite any %1$s you have previously set for this forum or any forum you selected below to copy %1$s from.', + 'ACP_APS_POINTS_RESET_SUCCESS' => 'You have successfully reset the %s.', + + 'APS_POINTS_DIFF' => '%s difference', + 'APS_POINTS_OLD' => 'Old %s', + 'APS_POINTS_NEW' => 'New %s', + + # Global + 'APS_POINTS_REGISTER' => 'Registered', + 'APS_POINTS_BIRTHDAY' => 'Celebrated their birthday', + 'APS_POINTS_BIRTHDAY_DESC' => 'This action is ran through the system cron once a day.
    ACP » General » Server settings » Run periodic tasks from system cron', + 'APS_POINTS_MOD_WARN' => 'Warned a user', + 'APS_POINTS_USER_WARN' => 'Received a warning', + 'APS_POINTS_PM' => 'Created a private message', + 'APS_POINTS_PM_PER_RECIPIENT' => 'Per recipient', + + # Misc + 'ACP_APS_POINTS_MISC' => 'Miscellaneous', + 'APS_POINTS_PER_VOTE' => 'Per option voted for', + 'APS_POINTS_VOTE_ADDED' => 'Voted in a poll', + 'APS_POINTS_VOTE_REMOVED' => 'Removed a vote', + 'APS_POINTS_VOTE_AMOUNT' => 'Amount of options voted for', + + # Topics / Posts + 'APS_POINTS_POST' => 'Created a post', + 'APS_POINTS_TOPIC' => 'Created a topic', + 'APS_POINTS_STICKY' => 'Created a sticky', + 'APS_POINTS_ANNOUNCE' => 'Created an announcement', + 'APS_POINTS_GLOBAL' => 'Created a global announcement', + 'APS_POINTS_PER_CHAR' => 'Per character', + 'APS_POINTS_PER_CHAR_DESC' => 'The text is stripped from BBCodes before counting the characters.', + 'APS_POINTS_PER_WORD' => 'Per word', + 'APS_POINTS_PER_WORD_DESC' => 'The text is stripped from BBCodes before counting the words.', + 'APS_POINTS_ATTACH_HAS' => 'Including attachment(s)', + 'APS_POINTS_ATTACH_PER' => 'Per included attachment', + 'APS_POINTS_PER_QUOTE' => 'Per quote', + 'APS_POINTS_PER_QUOTE_DESC' => 'Only the outer most quotes are counted and only if there is an author provided.', + 'APS_POINTS_POLL_HAS' => 'Included a poll', + 'APS_POINTS_POLL_OPTION' => 'Per included poll option', + 'APS_POINTS_EDIT' => 'Edited their post', + 'APS_POINTS_DELETE' => 'Deleted their post', + 'APS_POINTS_DELETE_SOFT' => 'Soft deleted their post', + 'APS_POINTS_BUMP' => 'Bumped a topic', + + # Topic types + 'ACP_APS_TOPIC_TYPES' => 'Topic types', + + 'APS_POINTS_MOD_NORMAL_STICKY' => 'Made a topic a sticky', + 'APS_POINTS_MOD_NORMAL_ANNOUNCE' => 'Made a topic an announcement', + 'APS_POINTS_MOD_NORMAL_GLOBAL' => 'Made a topic a global announcement', + 'APS_POINTS_MOD_STICKY_NORMAL' => 'Made a sticky a normal topic', + 'APS_POINTS_MOD_STICKY_ANNOUNCE' => 'Made a sticky an announcement', + 'APS_POINTS_MOD_STICKY_GLOBAL' => 'Made a sticky a global announcement', + 'APS_POINTS_MOD_ANNOUNCE_NORMAL' => 'Made an announcement a normal topic', + 'APS_POINTS_MOD_ANNOUNCE_STICKY' => 'Made an announcement a sticky', + 'APS_POINTS_MOD_ANNOUNCE_GLOBAL' => 'Made an announcement a global announcement', + 'APS_POINTS_MOD_GLOBAL_NORMAL' => 'Made a global announcement a normal topic', + 'APS_POINTS_MOD_GLOBAL_STICKY' => 'Made a global announcement a sticky', + 'APS_POINTS_MOD_GLOBAL_ANNOUNCE' => 'Made a global announcement an announcement', + + 'APS_POINTS_USER_NORMAL_STICKY' => 'Their topic was made a sticky', + 'APS_POINTS_USER_NORMAL_ANNOUNCE' => 'Their topic was made an announcement', + 'APS_POINTS_USER_NORMAL_GLOBAL' => 'Their topic was made a global announcement', + 'APS_POINTS_USER_STICKY_NORMAL' => 'Their sticky was made a normal topic', + 'APS_POINTS_USER_STICKY_ANNOUNCE' => 'Their sticky was made an announcement', + 'APS_POINTS_USER_STICKY_GLOBAL' => 'Their sticky was made a global announcement', + 'APS_POINTS_USER_ANNOUNCE_NORMAL' => 'Their announcement was made a normal topic', + 'APS_POINTS_USER_ANNOUNCE_STICKY' => 'Their announcement was made a sticky', + 'APS_POINTS_USER_ANNOUNCE_GLOBAL' => 'Their announcement was made a global announcement', + 'APS_POINTS_USER_GLOBAL_NORMAL' => 'Their global announcement was made a normal topic', + 'APS_POINTS_USER_GLOBAL_STICKY' => 'Their global announcement was made a sticky', + 'APS_POINTS_USER_GLOBAL_ANNOUNCE' => 'Their global announcement was made an announcement', + + # Moderation + 'APS_POINTS_MOD_COPY' => 'Copied a topic from this forum', + 'APS_POINTS_USER_COPY' => 'Their topic got copied from this forum', + + 'APS_POINTS_MOD_CHANGE' => 'Changed a post’s author', + 'APS_POINTS_USER_CHANGE_FROM' => 'Removed as a post’s author', + 'APS_POINTS_USER_CHANGE_TO' => 'Became a post’s author', + + 'APS_POINTS_MOD_DELETE_POST' => 'Deleted a post', + 'APS_POINTS_USER_DELETE_POST' => 'Their post got deleted', + 'APS_POINTS_MOD_DELETE_SOFT_POST' => 'Soft deleted a post', + 'APS_POINTS_USER_DELETE_SOFT_POST' => 'Their post got soft deleted', + 'APS_POINTS_MOD_DELETE_TOPIC' => 'Deleted a topic', + 'APS_POINTS_USER_DELETE_TOPIC' => 'Their topic got deleted', + 'APS_POINTS_MOD_DELETE_SOFT_TOPIC' => 'Soft deleted a topic', + 'APS_POINTS_USER_DELETE_SOFT_TOPIC' => 'Their topic got soft deleted', + + 'APS_POINTS_MOD_EDIT' => 'Edited a post', + 'APS_POINTS_USER_EDIT' => 'Their post got edited', + + 'APS_POINTS_MOD_LOCK' => 'Locked a topic', + 'APS_POINTS_USER_LOCK' => 'Their topic got locked', + 'APS_POINTS_MOD_LOCK_POST' => 'Locked a post', + 'APS_POINTS_USER_LOCK_POST' => 'Their post got locked', + 'APS_POINTS_MOD_UNLOCK' => 'Unlocked a topic', + 'APS_POINTS_USER_UNLOCK' => 'Their topic got unlocked', + 'APS_POINTS_MOD_UNLOCK_POST' => 'Unlocked a post', + 'APS_POINTS_USER_UNLOCK_POST' => 'Their post got unlocked', + + 'APS_POINTS_MOD_MERGE' => 'Merged a topic', + 'APS_POINTS_MOD_MERGE_DESC' => 'This will also trigger the “delete” action on the topics that are being merged into an other.', + 'APS_POINTS_USER_MERGE' => 'Their topic got merged', + 'APS_POINTS_USER_MERGE_DESC' => 'This will also trigger the “delete” action on the topics that are being merged into an other.', + + 'APS_POINTS_MOD_MOVE_POST' => 'Moved a post', + 'APS_POINTS_MOD_MOVE_POST_DESC' => 'Moved values are for moving from this forum, not to.', + 'APS_POINTS_USER_MOVE_POST' => 'Their post got moved', + 'APS_POINTS_MOD_MOVE_TOPIC' => 'Moved a topic', + 'APS_POINTS_USER_MOVE_TOPIC' => 'Their topic got moved', + + 'APS_POINTS_MOD_APPROVE' => 'Approved a post', + 'APS_POINTS_MOD_DISAPPROVE' => 'Disapproved a post', + 'APS_POINTS_MOD_RESTORE' => 'Restored a post', + 'APS_POINTS_USER_APPROVE' => 'Their post is approved', + 'APS_POINTS_USER_DISAPPROVE' => 'Their post is disapproved', + 'APS_POINTS_USER_RESTORE' => 'Their post is restored', + + 'APS_POINTS_USER_ADJUSTED' => 'Adjusted by moderator', +]); diff --git a/ext/phpbbstudio/aps/license.txt b/ext/phpbbstudio/aps/license.txt new file mode 100644 index 0000000..b9f6fc2 --- /dev/null +++ b/ext/phpbbstudio/aps/license.txt @@ -0,0 +1,280 @@ +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/ext/phpbbstudio/aps/mcp/main_info.php b/ext/phpbbstudio/aps/mcp/main_info.php new file mode 100644 index 0000000..bfdf813 --- /dev/null +++ b/ext/phpbbstudio/aps/mcp/main_info.php @@ -0,0 +1,42 @@ + '\phpbbstudio\aps\mcp\main_module', + 'title' => 'MCP_APS_POINTS', + 'modes' => [ + 'front' => [ + 'title' => 'MCP_APS_FRONT', + 'auth' => 'ext_phpbbstudio/aps', + 'cat' => ['MCP_APS_POINTS'] + ], + 'change' => [ + 'title' => 'MCP_APS_CHANGE', + 'auth' => 'ext_phpbbstudio/aps && (acl_m_aps_adjust_custom || acl_m_aps_adjust_reason)', + 'cat' => ['MCP_APS_POINTS'] + ], + 'logs' => [ + 'title' => 'MCP_APS_LOGS', + 'auth' => 'ext_phpbbstudio/aps && acl_u_aps_view_logs', + 'cat' => ['MCP_APS_POINTS'] + ], + ], + ]; + } +} diff --git a/ext/phpbbstudio/aps/mcp/main_module.php b/ext/phpbbstudio/aps/mcp/main_module.php new file mode 100644 index 0000000..886f03d --- /dev/null +++ b/ext/phpbbstudio/aps/mcp/main_module.php @@ -0,0 +1,48 @@ +get('phpbbstudio.aps.functions'); + + /** @var \phpbb\language\language $language */ + $language = $phpbb_container->get('language'); + + /** @var \phpbbstudio\aps\controller\mcp_controller $mcp_controller */ + $mcp_controller = $phpbb_container->get('phpbbstudio.aps.controller.mcp'); + + // Set page title and template + $this->tpl_name = 'mcp/mcp_aps_' . $mode; + $this->page_title = $language->lang('MCP_APS_POINTS_' . utf8_strtoupper($mode), $functions->get_name()); + + // Make the custom form action available in the controller and handle the mode + $mcp_controller->set_page_url($this->u_action)->{$mode}(); + } +} diff --git a/ext/phpbbstudio/aps/migrations/install_acp_module.php b/ext/phpbbstudio/aps/migrations/install_acp_module.php new file mode 100644 index 0000000..39a5f49 --- /dev/null +++ b/ext/phpbbstudio/aps/migrations/install_acp_module.php @@ -0,0 +1,78 @@ +table_prefix . "modules + WHERE module_class = 'acp' + AND module_langname = 'ACP_APS_POINTS'"; + $result = $this->db->sql_query($sql); + $module_id = $this->db->sql_fetchfield('module_id'); + $this->db->sql_freeresult($result); + + return $module_id !== false; + } + + /** + * Assign migration file dependencies for this migration. + * + * @return array Array of migration files + * @access public + * @static + */ + static public function depends_on() + { + return ['\phpbb\db\migration\data\v32x\v327']; + } + + /** + * Add the Advanced Points System ACP module to the database. + * + * @return array Array of module data + * @access public + */ + public function update_data() + { + return [ + ['module.add', [ + 'acp', + 'ACP_CAT_DOT_MODS', + 'ACP_APS_POINTS' + ]], + ['module.add', [ + 'acp', + 'ACP_APS_POINTS', + [ + 'module_basename' => '\phpbbstudio\aps\acp\main_module', + 'modes' => ['settings', 'display', 'points', 'logs'], + ], + ]], + ]; + } +} diff --git a/ext/phpbbstudio/aps/migrations/install_configuration.php b/ext/phpbbstudio/aps/migrations/install_configuration.php new file mode 100644 index 0000000..b58f917 --- /dev/null +++ b/ext/phpbbstudio/aps/migrations/install_configuration.php @@ -0,0 +1,84 @@ +config->offsetExists('aps_points_name_en'); + } + + /** + * Assign migration file dependencies for this migration. + * + * @return array Array of migration files + * @access public + * @static + */ + static public function depends_on() + { + return ['\phpbbstudio\aps\migrations\install_user_schema']; + } + + /** + * Add the Advanced Points System configuration to the database. + * + * @return array Array of configuration + * @access public + */ + public function update_data() + { + return [ + ['config.add', ['aps_points_name_en', 'Points']], + ['config.add', ['aps_points_safe_mode', false]], // @todo Change to true upon release + ['config.add', ['aps_points_icon', 'fa-money']], + ['config.add', ['aps_points_icon_position', 1]], + ['config.add', ['aps_points_decimals', 2]], + ['config.add', ['aps_points_separator_dec', ',']], + ['config.add', ['aps_points_separator_thou', htmlspecialchars(' ')]], + ['config.add', ['aps_points_display_pm', true]], + ['config.add', ['aps_points_display_post', true]], + ['config.add', ['aps_points_display_profile', true]], + ['config.add', ['aps_points_min', '']], + ['config.add', ['aps_points_max', '']], + ['config.add', ['aps_points_exclude_words', 1]], + ['config.add', ['aps_points_exclude_chars', 1]], + + ['config.add', ['aps_birthday_last_run', 0, true]], + ['config.add', ['aps_notification_id', 0]], + ['config.add', ['aps_actions_per_page', 10]], + + ['config.add', ['aps_chain_merge_delete', false]], + ['config.add', ['aps_chain_merge_move', false]], + ['config.add', ['aps_chain_warn_pm', false]], + + ['config.add', ['aps_display_top_change', true]], + ['config.add', ['aps_display_top_count', 3]], + ['config.add', ['aps_display_adjustments', 5]], + ['config.add', ['aps_display_graph_time', 1500]], + ]; + } +} diff --git a/ext/phpbbstudio/aps/migrations/install_mcp_module.php b/ext/phpbbstudio/aps/migrations/install_mcp_module.php new file mode 100644 index 0000000..b059f15 --- /dev/null +++ b/ext/phpbbstudio/aps/migrations/install_mcp_module.php @@ -0,0 +1,78 @@ +table_prefix . "modules + WHERE module_class = 'mcp' + AND module_langname = 'MCP_APS_POINTS'"; + $result = $this->db->sql_query($sql); + $module_id = $this->db->sql_fetchfield('module_id'); + $this->db->sql_freeresult($result); + + return $module_id !== false; + } + + /** + * Assign migration file dependencies for this migration. + * + * @return array Array of migration files + * @access public + * @static + */ + static public function depends_on() + { + return ['\phpbbstudio\aps\migrations\install_acp_module']; + } + + /** + * Add the Advanced Points System MCP module to the database. + * + * @return array Array of module data + * @access public + */ + public function update_data() + { + return [ + ['module.add', [ + 'mcp', + 0, + 'MCP_APS_POINTS' + ]], + ['module.add', [ + 'mcp', + 'MCP_APS_POINTS', + [ + 'module_basename' => '\phpbbstudio\aps\mcp\main_module', + 'modes' => ['front', 'change', 'logs'], + ], + ]], + ]; + } +} diff --git a/ext/phpbbstudio/aps/migrations/install_permissions.php b/ext/phpbbstudio/aps/migrations/install_permissions.php new file mode 100644 index 0000000..e593b9c --- /dev/null +++ b/ext/phpbbstudio/aps/migrations/install_permissions.php @@ -0,0 +1,117 @@ +role_exists('ROLE_USER_STANDARD')) + { + $data[] = ['permission.permission_set', ['ROLE_USER_STANDARD', 'u_aps_view_build']]; + $data[] = ['permission.permission_set', ['ROLE_USER_STANDARD', 'u_aps_view_logs']]; + // Can NOT view the moderator's name + } + + if ($this->role_exists('ROLE_USER_FULL')) + { + $data[] = ['permission.permission_set', ['ROLE_USER_FULL', 'u_aps_view_build']]; + $data[] = ['permission.permission_set', ['ROLE_USER_FULL', 'u_aps_view_logs']]; + $data[] = ['permission.permission_set', ['ROLE_USER_FULL', 'u_aps_view_mod']]; + } + + if ($this->role_exists('ROLE_MOD_STANDARD')) + { + $data[] = ['permission.permission_set', ['ROLE_MOD_STANDARD', 'm_aps_adjust_reason']]; + // Can NOT adjust a user's points with a custom action, only admin defined ones + } + + if ($this->role_exists('ROLE_MOD_FULL')) + { + $data[] = ['permission.permission_set', ['ROLE_MOD_FULL', 'm_aps_adjust_custom']]; + $data[] = ['permission.permission_set', ['ROLE_MOD_FULL', 'm_aps_adjust_reason']]; + } + + if ($this->role_exists('ROLE_ADMIN_STANDARD')) + { + $data[] = ['permission.permission_set', ['ROLE_ADMIN_STANDARD', 'a_aps_logs']]; + $data[] = ['permission.permission_set', ['ROLE_ADMIN_STANDARD', 'a_aps_points']]; + $data[] = ['permission.permission_set', ['ROLE_ADMIN_STANDARD', 'a_aps_reasons']]; + $data[] = ['permission.permission_set', ['ROLE_ADMIN_STANDARD', 'a_aps_display']]; + $data[] = ['permission.permission_set', ['ROLE_ADMIN_STANDARD', 'a_aps_settings']]; + } + + if ($this->role_exists('ROLE_ADMIN_FULL')) + { + $data[] = ['permission.permission_set', ['ROLE_ADMIN_FULL', 'a_aps_logs']]; + $data[] = ['permission.permission_set', ['ROLE_ADMIN_FULL', 'a_aps_points']]; + $data[] = ['permission.permission_set', ['ROLE_ADMIN_FULL', 'a_aps_reasons']]; + $data[] = ['permission.permission_set', ['ROLE_ADMIN_FULL', 'a_aps_display']]; + $data[] = ['permission.permission_set', ['ROLE_ADMIN_FULL', 'a_aps_settings']]; + } + + return $data; + } + + /** + * Checks whether the given role does exist or not. + * + * @param string $role The name of the role + * @return bool True if the role exists, false otherwise + */ + private function role_exists($role) + { + $sql = 'SELECT role_id + FROM ' . ACL_ROLES_TABLE . ' + WHERE role_name = "' . $this->db->sql_escape($role) . '"'; + $result = $this->db->sql_query_limit($sql, 1); + $role_id = $this->db->sql_fetchfield('role_id'); + $this->db->sql_freeresult($result); + + return (bool) $role_id; + } +} diff --git a/ext/phpbbstudio/aps/migrations/install_ucp_module.php b/ext/phpbbstudio/aps/migrations/install_ucp_module.php new file mode 100644 index 0000000..f071b41 --- /dev/null +++ b/ext/phpbbstudio/aps/migrations/install_ucp_module.php @@ -0,0 +1,79 @@ +table_prefix . "modules + WHERE module_class = 'ucp' + AND module_langname = 'UCP_APS_POINTS'"; + $result = $this->db->sql_query($sql); + $module_id = $this->db->sql_fetchfield('module_id'); + $this->db->sql_freeresult($result); + + return $module_id !== false; + } + + /** + * Assign migration file dependencies for this migration. + * + * @return array Array of migration files + * @access public + * @static + */ + static public function depends_on() + { + return ['\phpbbstudio\aps\migrations\install_acp_module']; + } + + /** + * Add the Advanced Points System UCP module to the database. + * + * @return array Array of module data + * @access public + */ + public function update_data() + { + return [ + ['module.add', [ + 'ucp', + 0, + [ + 'module_enabled' => 1, + 'module_display' => 1, + 'module_basename' => '', + 'module_class' => 'ucp', + 'parent_id' => 0, + 'module_langname' => 'UCP_APS_POINTS', + 'module_mode' => '', + 'module_auth' => 'ext_phpbbstudio/aps', + ] + ]], + ]; + } +} diff --git a/ext/phpbbstudio/aps/migrations/install_user_schema.php b/ext/phpbbstudio/aps/migrations/install_user_schema.php new file mode 100644 index 0000000..8123689 --- /dev/null +++ b/ext/phpbbstudio/aps/migrations/install_user_schema.php @@ -0,0 +1,142 @@ +db_tools->sql_column_exists($this->table_prefix . 'users', 'user_points'); + } + + /** + * Assign migration file dependencies for this migration. + * + * @return array Array of migration files + * @access public + * @static + */ + static public function depends_on() + { + return ['\phpbbstudio\aps\migrations\install_acp_module']; + } + + /** + * Add the Advanced Points System tables and columns to the database. + * + * @return array Array of tables and columns data + * @access public + */ + public function update_schema() + { + return [ + 'add_columns' => [ + $this->table_prefix . 'users' => [ + 'user_points' => ['DECIMAL:14', 0.00], + ], + ], + 'add_tables' => [ + $this->table_prefix . 'aps_display' => [ + 'COLUMNS' => [ + 'user_id' => ['ULINT', 0], + 'aps_display' => ['MTEXT_UNI', ''], + ], + 'PRIMARY_KEY' => 'user_id', + ], + $this->table_prefix . 'aps_logs' => [ + 'COLUMNS' => [ + 'log_id' => ['ULINT', null, 'auto_increment'], + 'log_action' => ['TEXT_UNI', ''], + 'log_actions' => ['MTEXT_UNI', ''], + 'log_time' => ['TIMESTAMP', 0], + 'log_approved' => ['BOOL', 1], + 'forum_id' => ['ULINT', 0], + 'topic_id' => ['ULINT', 0], + 'post_id' => ['ULINT', 0], + 'user_id' => ['ULINT', 0], + 'reportee_id' => ['ULINT', 0], + 'reportee_ip' => ['VCHAR:40', ''], + 'points_old' => ['DECIMAL:14', 0.00], + 'points_sum' => ['DECIMAL:14', 0.00], + 'points_new' => ['DECIMAL:14', 0.00], + ], + 'PRIMARY_KEY' => 'log_id', + 'KEYS' => [ + 'forum_id' => ['INDEX', 'forum_id'], + 'topic_id' => ['INDEX', 'topic_id'], + 'post_id' => ['INDEX', 'post_id'], + 'user_id' => ['INDEX', 'user_id'], + 'reportee_id' => ['INDEX', 'reportee_id'], + ], + ], + $this->table_prefix . 'aps_points' => [ + 'COLUMNS' => [ + 'points_name' => ['VCHAR_UNI', ''], + 'points_value' => ['DECIMAL:6', 0.00], + 'forum_id' => ['ULINT', 0], + ], + 'PRIMARY_KEY' => ['points_name', 'forum_id'], + 'KEYS' => [ + 'forum_id' => ['INDEX', 'forum_id'], + ], + ], + $this->table_prefix . 'aps_reasons' => [ + 'COLUMNS' => [ + 'reason_id' => ['ULINT', null, 'auto_increment'], + 'reason_title' => ['VCHAR_UNI', ''], + 'reason_desc' => ['TEXT_UNI', ''], + 'reason_points' => ['DECIMAL:14', 0.00], + 'reason_order' => ['UINT', 0], + ], + 'PRIMARY_KEY' => 'reason_id', + ], + ], + ]; + } + + /** + * Reverts the database schema by providing a set of change instructions + * + * @return array Array of schema changes + * (compatible with db_tools->perform_schema_changes()) + * @access public + */ + public function revert_schema() + { + return [ + 'drop_columns' => [ + $this->table_prefix . 'users' => [ + 'user_points', + ], + ], + 'drop_tables' => [ + $this->table_prefix . 'aps_display', + $this->table_prefix . 'aps_logs', + $this->table_prefix . 'aps_points', + $this->table_prefix . 'aps_reasons', + ], + ]; + } +} diff --git a/ext/phpbbstudio/aps/migrations/update_configuration.php b/ext/phpbbstudio/aps/migrations/update_configuration.php new file mode 100644 index 0000000..5af0916 --- /dev/null +++ b/ext/phpbbstudio/aps/migrations/update_configuration.php @@ -0,0 +1,58 @@ +config->offsetExists('aps_points_icon_img'); + } + + /** + * Assign migration file dependencies for this migration. + * + * @return array Array of migration files + * @access public + * @static + */ + static public function depends_on() + { + return ['\phpbbstudio\aps\migrations\install_configuration']; + } + + /** + * Add the Advanced Points System configuration to the database. + * + * @return array Array of configuration + * @access public + */ + public function update_data() + { + return [ + ['config.add', ['aps_points_icon_img', '']], + ]; + } +} diff --git a/ext/phpbbstudio/aps/migrations/update_permissions.php b/ext/phpbbstudio/aps/migrations/update_permissions.php new file mode 100644 index 0000000..65314db --- /dev/null +++ b/ext/phpbbstudio/aps/migrations/update_permissions.php @@ -0,0 +1,75 @@ +role_exists('ROLE_USER_STANDARD')) + { + $data[] = ['permission.permission_set', ['ROLE_USER_STANDARD', 'u_aps_view_build_other']]; + $data[] = ['permission.permission_set', ['ROLE_USER_STANDARD', 'u_aps_view_logs_other']]; + } + + if ($this->role_exists('ROLE_USER_FULL')) + { + $data[] = ['permission.permission_set', ['ROLE_USER_FULL', 'u_aps_view_build_other']]; + $data[] = ['permission.permission_set', ['ROLE_USER_FULL', 'u_aps_view_logs_other']]; + } + + return $data; + } + + /** + * Checks whether the given role does exist or not. + * + * @param string $role The name of the role + * @return bool True if the role exists, false otherwise + */ + private function role_exists($role) + { + $sql = 'SELECT role_id + FROM ' . ACL_ROLES_TABLE . ' + WHERE role_name = "' . $this->db->sql_escape($role) . '"'; + $result = $this->db->sql_query_limit($sql, 1); + $role_id = $this->db->sql_fetchfield('role_id'); + $this->db->sql_freeresult($result); + + return (bool) $role_id; + } +} diff --git a/ext/phpbbstudio/aps/migrations/v105_configuration.php b/ext/phpbbstudio/aps/migrations/v105_configuration.php new file mode 100644 index 0000000..6ce4635 --- /dev/null +++ b/ext/phpbbstudio/aps/migrations/v105_configuration.php @@ -0,0 +1,58 @@ +config->offsetExists('aps_ignore_criteria'); + } + + /** + * Assign migration file dependencies for this migration. + * + * @return array Array of migration files + * @access public + * @static + */ + static public function depends_on() + { + return ['\phpbbstudio\aps\migrations\update_configuration']; + } + + /** + * Add the Advanced Points System configuration to the database. + * + * @return array Array of configuration + * @access public + */ + public function update_data() + { + return [ + ['config.add', ['aps_link_locations', 32]], + ['config.add', ['aps_ignore_criteria', 0]], + ['config.add', ['aps_ignore_min_chars', 0]], + ['config.add', ['aps_ignore_min_words', 0]], + ['config.add', ['aps_ignore_excluded_chars', 0]], + ['config.add', ['aps_ignore_excluded_words', 0]], + ]; + } +} diff --git a/ext/phpbbstudio/aps/notification/type/adjust.php b/ext/phpbbstudio/aps/notification/type/adjust.php new file mode 100644 index 0000000..6e6028d --- /dev/null +++ b/ext/phpbbstudio/aps/notification/type/adjust.php @@ -0,0 +1,250 @@ +auth = $auth; + } + + /** + * Set the controller helper object. + * + * @param \phpbb\controller\helper $helper Controller helper object + * @return void + * @access public + */ + public function set_controller_helper(\phpbb\controller\helper $helper) + { + $this->helper = $helper; + } + + /** + * Set the user loader object. + * + * @param \phpbb\user_loader $user_loader User loader object + * @return void + * @access public + */ + public function set_user_loader(\phpbb\user_loader $user_loader) + { + $this->user_loader = $user_loader; + } + + /** + * Get notification type name. + * + * @return string The notification name as defined in services.yml + * @access public + */ + public function get_type() + { + return 'phpbbstudio.aps.notification.type.adjust'; + } + + /** + * 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') + * @access public + * @static + */ + public static $notification_option = false; + + /** + * 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 + * @access public + */ + public function is_available() + { + return false; + } + + /** + * Get the id of the notification. + * + * @param array $data The notification type specific data + * @return int Identifier of the notification + * @access public + */ + public static function get_item_id($data) + { + return $data['notification_id']; + } + + /** + * Get the id of the parent. + * + * @param array $data The type notification specific data + * @return int Identifier of the parent + * @access public + */ + public static function get_item_parent_id($data) + { + // No parent + return 0; + } + + /** + * Find the users who want to receive notifications. + * + * @param array $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 Array of user identifiers with their notification method(s) + * @access public + */ + public function find_users_for_notification($data, $options = []) + { + // Return an array of users to be notified, storing the user_ids as the array keys + $users = []; + + foreach ($data['user_ids'] as $user_id) + { + $users[(int) $user_id] = $this->notification_manager->get_default_methods(); + } + + return $users; + } + + /** + * Users needed to query before this notification can be displayed. + * + * @return array Array of user identifiers to query. + * @access public + */ + public function users_to_query() + { + if ($this->auth->acl_get('u_aps_view_mod')) + { + return [$this->get_data('moderator_id')]; + } + + return []; + } + + /** + * Get the user's avatar. + * + * @return string The HTML formatted avatar + */ + public function get_avatar() + { + return $this->auth->acl_get('u_aps_view_mod') ? $this->user_loader->get_avatar($this->get_data('moderator_id'), false, true) : ''; + } + + /** + * Get the HTML formatted title of this notification. + * + * @return string The HTML formatted title + * @access public + */ + public function get_title() + { + return $this->language->lang('APS_NOTIFICATION_ADJUSTED', $this->get_data('name')); + } + + /** + * Get the HTML formatted reference of the notification. + * + * @return string The HTML formatted reference + * @access public + */ + public function get_reference() + { + if ($reason = $this->get_data('reason')) + { + $moderator = $this->auth->acl_get('u_aps_view_mod') ? $this->get_data('moderator') : $this->language->lang('MODERATOR'); + return $moderator . $this->language->lang('COLON') . ' '. $this->language->lang('NOTIFICATION_REFERENCE', censor_text($reason)); + } + + return ''; + } + + /** + * Get the url to this item. + * + * @return string URL to the APS Display page + * @access public + */ + public function get_url() + { + return $this->helper->route('phpbbstudio_aps_display'); + } + + /** + * Get email template. + * + * @return string|bool Whether or not this notification has an email option template + * @access public + */ + public function get_email_template() + { + return false; + } + + /** + * Get email template variables. + * + * @return array Array of variables that can be used in the email template + * @access public + */ + public function get_email_template_variables() + { + return []; + } + + /** + * Function for preparing the data for insertion in an SQL query. + * (The service handles insertion) + * + * @param array $data The type specific data + * @param array $pre_create_data Data from pre_create_insert_array() + * @return void + * @access public + */ + public function create_insert_array($data, $pre_create_data = []) + { + $this->set_data('name', $data['name']); + $this->set_data('reason', $data['reason']); + $this->set_data('moderator', $data['moderator']); + $this->set_data('moderator_id', $data['moderator_id']); + + parent::create_insert_array($data, $pre_create_data); + } +} diff --git a/ext/phpbbstudio/aps/points/blockader.php b/ext/phpbbstudio/aps/points/blockader.php new file mode 100644 index 0000000..bcd0a8d --- /dev/null +++ b/ext/phpbbstudio/aps/points/blockader.php @@ -0,0 +1,158 @@ +db = $db; + $this->blocks_table = $blocks_table; + } + + /** + * User identifier user for admin desired blocks. + * + * @return int The admin identifier + * @access public + */ + public function get_admin_id() + { + return $this->admin_id; + } + + /** + * Fetch a row from the database for the provided user identifier. + * + * @param int $user_id The user identifier + * @return array The json decoded database row + * @access public + */ + public function row($user_id) + { + $sql = 'SELECT aps_display FROM ' . $this->blocks_table . ' WHERE user_id = ' . (int) $user_id; + $result = $this->db->sql_query_limit($sql, 1); + $display = $this->db->sql_fetchfield('aps_display'); + $this->db->sql_freeresult($result); + + return $display ? (array) json_decode($display, true) : []; + } + + /** + * Fetch a rowset from the database for the provided user identifier and admin identifier. + * + * @param int $user_id The user identifier + * @return array The json decoded database rowset + * @access public + */ + public function rowset($user_id) + { + $rowset = []; + + $sql = 'SELECT user_id, aps_display + FROM ' . $this->blocks_table . ' + WHERE ' . $this->db->sql_in_set('user_id', [$this->admin_id, (int) $user_id]); + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if ($row['user_id'] != ANONYMOUS) + { + $rowset[(int) $row['user_id']] = (array) json_decode($row['aps_display'], true); + } + } + $this->db->sql_freeresult($result); + + return (array) $rowset; + } + + /** + * Set the user desired blocks in the database. + * + * @param int $user_id The user identifier + * @param array $blocks The user desired blocks + * @param bool $insert Whether to insert or update + * @return bool|int Bool on update, integer on insert + * @access public + */ + public function set_blocks($user_id, array $blocks, $insert) + { + if ($user_id == ANONYMOUS) + { + return false; + } + + if ($insert) + { + return $this->insert($user_id, $blocks); + } + else + { + return $this->update($user_id, $blocks); + } + } + + /** + * Insert a user desired blocks into the database. + * + * @param int $user_id The user identifier + * @param array $blocks The user desired blocks + * @return int + * @access public + */ + public function insert($user_id, array $blocks) + { + $sql = 'INSERT INTO ' . $this->blocks_table . ' ' . $this->db->sql_build_array('INSERT', [ + 'user_id' => (int) $user_id, + 'aps_display' => json_encode($blocks), + ]); + $this->db->sql_query($sql); + + return (int) $this->db->sql_nextid(); + } + + /** + * Update a user desired blocks in the database. + * + * @param int $user_id The user identifier + * @param array $blocks The user desired blocks + * @return bool + * @access public + */ + public function update($user_id, array $blocks) + { + $sql = 'UPDATE ' . $this->blocks_table . ' SET ' . $this->db->sql_build_array('UPDATE', [ + 'aps_display' => json_encode($blocks), + ]) . ' WHERE user_id = ' . (int) $user_id; + $this->db->sql_query($sql); + + return (bool) $this->db->sql_affectedrows(); + } +} diff --git a/ext/phpbbstudio/aps/points/distributor.php b/ext/phpbbstudio/aps/points/distributor.php new file mode 100644 index 0000000..6d476ca --- /dev/null +++ b/ext/phpbbstudio/aps/points/distributor.php @@ -0,0 +1,206 @@ +config = $config; + $this->db = $db; + $this->dispatcher = $dispatcher; + $this->functions = $functions; + $this->log = $log; + $this->user = $user; + $this->valuator = $valuator; + } + + /** + * Distribute a user's points. + * + * @param int $user_id The user identifier + * @param double $points The points gained + * @param array $logs The logs array + * @param null $user_points The current user's points, if available. Has to be null as 0 is a valid current points value. + * @return bool Whether the user points were updated or not + * @access public + */ + public function distribute($user_id, $points, $logs, $user_points = null) + { + // Calculate the new total for this user + $total = $this->total($user_id, $points, $user_points); + + // If logging was successful + if ($this->log->add_multi($logs)) + { + // Update the points for this user + return $this->update_points($total, $user_id); + } + + // Points were not updated, return false (logs were invalid) + return false; + } + + /** + * Update a user's points. + * + * @param double $points The user points + * @param int $user_id The user identifier + * @return bool Whether or not the user's row was updated + * @access public + */ + public function update_points($points, $user_id = 0) + { + $user_id = $user_id ? $user_id : $this->user->data['user_id']; + + $sql = 'UPDATE ' . $this->functions->table('users') . ' + SET user_points = ' . (double) $points . ' + WHERE user_id = ' . (int) $user_id; + $this->db->sql_query($sql); + $success = (bool) $this->db->sql_affectedrows(); + + /** + * Event to perform additional actions after APS Points have been distributed. + * + * @event phpbbstudio.aps.update_points + * @var int user_id The user identifier + * @var double points The user points + * @var bool success Whether or not the points were updated + * @since 1.0.3 + */ + $vars = ['user_id', 'points', 'success']; + extract($this->dispatcher->trigger_event('phpbbstudio.aps.update_points', compact($vars))); + + return $success; + } + + /** + * Approve log entries and distribute the on-hold points for a certain user. + * + * @param int $user_id The user identifier + * @param array $post_ids The post identifiers + * @param null $user_points The current user's points, if available. Has to be null as 0 is a valid current points value. + * @return void + * @access public + */ + public function approve($user_id, array $post_ids, $user_points = null) + { + // Get points gained from the log entries + $points = $this->log->get_values($user_id, $post_ids, false); + + // Equate the points gained to a single value + $points = $this->functions->equate_array($points); + + // Calculate the new total for this user + $total = $this->total($user_id, $user_points, $points); + + $this->db->sql_transaction('begin'); + + // Approve the log entries + $this->log->approve($user_id, $post_ids); + + // Update the points for this user + $this->update_points($total, $user_id); + + $this->db->sql_transaction('commit'); + } + + /** + * Disapprove log entries for a certain user. + * + * @param int $user_id The user identifier + * @param array $post_ids The post identifiers + * @return void + * @access public + */ + public function disapprove($user_id, array $post_ids) + { + // Delete the log entries + $this->log->delete([ + 'log_approved' => (int) false, + 'user_id' => (int) $user_id, + 'post_id' => [ + 'IN' => (array) $post_ids, + ], + ]); + } + + /** + * Calculate the new total (current points + gained points) for a specific user. + * + * @param int $user_id The user identifier + * @param double $points The user's gained points + * @param double $user_points The user's current points + * @return double The new total for this user + * @access public + */ + public function total($user_id, $points, $user_points = null) + { + // If the current user's points is null, get it from the database + if ($user_points === null) + { + $user_points = $this->valuator->user((int) $user_id); + } + + // Calculate the new total for this user + $total = $this->functions->equate_points($user_points, $points); + + // Check total boundaries (not higher than X, not lower than X) + $total = $this->functions->boundaries($total); + + return $total; + } +} diff --git a/ext/phpbbstudio/aps/points/reasoner.php b/ext/phpbbstudio/aps/points/reasoner.php new file mode 100644 index 0000000..3aa210a --- /dev/null +++ b/ext/phpbbstudio/aps/points/reasoner.php @@ -0,0 +1,193 @@ +db = $db; + $this->reasons_table = $reasons_table; + } + + /** + * Insert a reason in the database for the first time. + * + * @param array $reason The array to insert + * @return int The new reason identifier + * @access public + */ + public function insert(array $reason) + { + unset($reason['reason_id']); + + $sql = 'SELECT MAX(reason_order) as reason_order FROM ' . $this->reasons_table; + $result = $this->db->sql_query($sql); + $order = $this->db->sql_fetchfield('reason_order'); + $this->db->sql_freeresult($result); + + $reason['reason_order'] = ++$order; + + $sql = 'INSERT INTO ' . $this->reasons_table . ' ' . $this->db->sql_build_array('INSERT', $reason); + $this->db->sql_query($sql); + + return (int) $this->db->sql_nextid(); + } + + /** + * Updates an already existing reason in the database. + * + * @param array $reason The array to update + * @param int $reason_id The reason identifier + * @return bool Whether the reason's row in the database was updated or not + * @access public + */ + public function update(array $reason, $reason_id) + { + unset($reason['reason_id']); + + $sql = 'UPDATE ' . $this->reasons_table . ' SET ' . $this->db->sql_build_array('UPDATE', $reason) . ' WHERE reason_id = ' . (int) $reason_id; + $this->db->sql_query($sql); + + return (bool) $this->db->sql_affectedrows(); + } + + /** + * Deletes a reason row from the database. + * + * @param int $reason_id The reason identifier + * @return bool Whether or not the reason's row was deleted from the database. + * @access public + */ + public function delete($reason_id) + { + $sql = 'DELETE FROM ' . $this->reasons_table . ' WHERE reason_id = ' . (int) $reason_id; + $this->db->sql_query($sql); + + return (bool) $this->db->sql_affectedrows(); + } + + /** + * Retrieves a reason row from the database. + * + * @param int $reason_id The reason identifier + * @return mixed The reason row or false if the row does not exists. + * @access public + */ + public function row($reason_id) + { + if (empty($reason_id)) + { + return []; + } + + $sql = 'SELECT * FROM ' . $this->reasons_table . ' WHERE reason_id = ' . (int) $reason_id; + $result = $this->db->sql_query_limit($sql, 1); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + return (array) $row; + } + + /** + * Retrieves all the reason rows from the database. + * + * @return array The reasons rowset + * @access public + */ + public function rowset() + { + $rowset = []; + + $sql = 'SELECT * FROM ' . $this->reasons_table . ' ORDER BY reason_order ASC'; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $rowset[$row['reason_id']] = $row; + } + $this->db->sql_freeresult($result); + + return $rowset; + } + + /** + * Fills a reason row to make sure all values are set. + * + * @param array $reason The reason array to fill + * @return array The filled reason array + * @access public + */ + public function fill(array $reason) + { + $reason = !empty($reason) ? $reason : []; + + $fields = [ + 'title' => '', + 'desc' => '', + 'points' => 0.00, + 'order' => 0, + ]; + + foreach ($fields as $field => $default) + { + $reason['reason_' . $field] = !empty($reason['reason_' . $field]) ? $reason['reason_' . $field] : $default; + } + + return $reason; + } + + /** + * Re-orders a reason row. + * + * @param int $reason_id The reason identifier + * @param string $direction The direction to move it (up|down). + * @return void + * @access public + */ + public function order($reason_id, $direction) + { + // Select the current order + $sql = 'SELECT reason_order FROM ' . $this->reasons_table . ' WHERE reason_id = ' . (int) $reason_id; + $result = $this->db->sql_query_limit($sql, 1); + $order = (int) $this->db->sql_fetchfield('reason_order'); + $this->db->sql_freeresult($result); + + // Set the new (other) order + $other_order = $direction === 'up' ? $order - 1 : $order + 1; + + // Select the other reason identifier (with which it is being swapped) + $sql = 'SELECT reason_id FROM ' . $this->reasons_table . ' WHERE reason_order = ' . (int) $other_order; + $result = $this->db->sql_query_limit($sql, 1); + $other_id = (int) $this->db->sql_fetchfield('reason_id'); + $this->db->sql_freeresult($result); + + // Update both the reason rows + $this->update(['reason_order' => $other_order], $reason_id); + $this->update(['reason_order' => $order], $other_id); + } +} diff --git a/ext/phpbbstudio/aps/points/valuator.php b/ext/phpbbstudio/aps/points/valuator.php new file mode 100644 index 0000000..2f5ae3b --- /dev/null +++ b/ext/phpbbstudio/aps/points/valuator.php @@ -0,0 +1,378 @@ +db = $db; + $this->functions = $functions; + $this->user = $user; + + $this->values_table = $values_table; + } + + /** + * Get the points for the provided user identifier. + * + * @param int $user_id The user identifier + * @return double The current user's points + * @access public + */ + public function user($user_id) + { + if ($user_id == $this->user->data['user_id']) + { + return (double) $this->user->data['user_points']; + } + + $sql = 'SELECT user_points + FROM ' . $this->functions->table('users') . ' + WHERE user_id = ' . (int) $user_id . ' + AND user_type <> ' . USER_IGNORE; + $result = $this->db->sql_query_limit($sql, 1); + $user_points = $this->db->sql_fetchfield('user_points'); + $this->db->sql_freeresult($result); + + return (double) $user_points; + } + + /** + * Get the points for the provided user identifiers. + * + * @param array|int $user_ids The user identifier(s) + * @return array|float Array with the users' point values or Double if an integer was provided + * @access public + */ + public function users($user_ids) + { + // If it's just a single user + if (!is_array($user_ids)) + { + return $this->user($user_ids); + } + + // Make sure the array is full with integers + $user_ids = array_map('intval', $user_ids); + + // Set up base array + $user_points = []; + + $sql = 'SELECT user_id, user_points + FROM ' . $this->functions->table('users') . ' + WHERE ' . $this->db->sql_in_set('user_id', $user_ids) . ' + AND user_type <> ' . USER_IGNORE; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $user_points[(int) $row['user_id']] = (double) $row['user_points']; + } + $this->db->sql_freeresult($result); + + return $user_points; + } + + /** + * Retrieve point values from the database. + * + * @param array $fields Array of action type fields + * @param array|int $forum_ids The forum identifier(s) + * @param bool $fill Whether the point values should be filled + * @return array + * @access public + */ + public function get_points(array $fields, $forum_ids, $fill = true) + { + // Set up base arrays + $sql_where = $values = []; + + // Iterate over the fields + foreach ($fields as $scope => $fields_array) + { + // If the fields array is not empty, add it to the SQL WHERE clause + if (!empty($fields_array)) + { + $sql_where[] = $this->get_sql_where($scope, $fields_array, $forum_ids); + } + } + + $sql = 'SELECT * + FROM ' . $this->values_table . ' + WHERE ' . implode(' OR ', $sql_where); + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $f = (int) $row['forum_id']; + $n = (string) $row['points_name']; + $v = (double) $row['points_value']; + + $values[$f][$n] = $v; + } + $this->db->sql_freeresult($result); + + // Make sure all values are set + if ($fill) + { + $this->fill_values($values, $fields, $forum_ids); + } + + return $values; + } + + /** + * Deletes all points from the database that do not belong to any of the action types. + * + * @param array $fields Array of action types fields + * @param int $forum_id The forum identifier + * @return void + * @access public + */ + public function clean_points($fields, $forum_id) + { + $sql = 'DELETE FROM ' . $this->values_table . ' + WHERE forum_id = ' . (int) $forum_id . ' + AND ' . $this->db->sql_in_set('points_name', $fields, true, true); + $this->db->sql_query($sql); + } + + /** + * Deletes all points from the database that do not belong to any of the action types. + * + * @param array $fields Array of action types fields + * @return void + * @access public + */ + public function clean_all_points($fields) + { + // Set up base arrays + $sql_where = $forum_ids = []; + + $sql = 'SELECT forum_id FROM ' . $this->functions->table('forums'); + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $forum_ids[] = (int) $row['forum_id']; + } + $this->db->sql_freeresult($result); + + // Iterate over the fields + foreach ($fields as $scope => $fields_array) + { + // If the fields array is not empty, add it to the SQL WHERE clause + if (!empty($fields_array)) + { + $sql_where[] = $this->get_sql_where($scope, $fields_array, $forum_ids, true); + } + } + + $sql = 'DELETE FROM ' . $this->values_table . ' + WHERE points_value = 0 + OR ' . $this->db->sql_in_set('forum_id', ($forum_ids + [0]), true) . ' + OR ' . implode(' OR ', $sql_where); + $this->db->sql_query($sql); + } + + /** + * Delete the point values in the database for a specific forum. + * + * @param int $forum_id The forum identifier + * @return bool Whether the values were deleted or not. + * @access public + */ + public function delete_points($forum_id) + { + $sql = 'DELETE FROM ' . $this->values_table . ' + WHERE forum_id = ' . (int) $forum_id; + $this->db->sql_query($sql); + + return (bool) $this->db->sql_affectedrows(); + } + + /** + * Set the point values in the database. + * + * @param array $points Array of point values + * @param int $forum_id The forum identifier + * @return void + * @access public + */ + public function set_points($points, $forum_id) + { + $existing = [(int) empty($forum_id) => array_keys($points)]; + $existing = $this->get_points($existing, $forum_id, false); + $existing = $existing ? array_keys($existing[(int) $forum_id]) : []; + + $this->db->sql_transaction('begin'); + + foreach ($points as $name => $value) + { + // If the value already exists in the database, update it + if (in_array($name, $existing)) + { + $sql = 'UPDATE ' . $this->values_table . ' + SET points_value = ' . (double) $value . ' + WHERE points_name = "' . $this->db->sql_escape($name) . '" + AND forum_id = ' . (int) $forum_id; + } + else + { + // Otherwise insert it for the first time + $row['forum_id'] = (int) $forum_id; + + $sql = 'INSERT INTO ' . $this->values_table . ' ' . $this->db->sql_build_array('INSERT', [ + 'points_name' => (string) $name, + 'points_value' => (double) $value, + 'forum_id' => (int) $forum_id, + ]); + } + + $this->db->sql_query($sql); + } + + $this->db->sql_transaction('commit'); + } + + /** + * Copy the point values from one forum to an other. + * + * @param int $from The "from" forum identifier + * @param array|int $to The "to" forum identifier(s) + * @return void + * @access public + */ + public function copy_points($from, $to) + { + $this->db->sql_transaction('begin'); + + // Select "from" points + $sql = 'SELECT points_name, points_value + FROM ' . $this->values_table . ' + WHERE forum_id = ' . (int) $from; + $result = $this->db->sql_query($sql); + $rowset = $this->db->sql_fetchrowset($result); + $this->db->sql_freeresult($result); + + $to = array_map('intval', array_unique(array_filter($to))); + + // Delete "to" points + $sql = 'DELETE FROM ' . $this->values_table . ' + WHERE ' . $this->db->sql_in_set('forum_id', $to); + $this->db->sql_query($sql); + + foreach ($to as $forum_id) + { + for ($i = 0; $i < count($rowset); $i++) + { + $rowset[$i]['forum_id'] = (int) $forum_id; + } + + $this->db->sql_multi_insert($this->values_table, $rowset); + } + + $this->db->sql_transaction('commit'); + } + + /** + * Create a SQL WHERE clause based on the scope (local|global) and the provided fields. + * + * @param int $scope The scope for the fields (local|global) + * @param array $fields Array of action types fields + * @param array|int $forum_ids The forum identifier(s) + * @param bool $negate Whether it should be a SQL IN or SQL NOT IN clause + * @return string The SQL WHERE clause + * @access protected + */ + protected function get_sql_where($scope, array $fields, $forum_ids, $negate = false) + { + $sql_where = '('; + $sql_where .= $this->db->sql_in_set('points_name', $fields, $negate); + $sql_where .= ' AND '; + + switch ($scope) + { + // Local + case 0: + $sql_where .= is_array($forum_ids) ? $this->db->sql_in_set('forum_id', array_map('intval', $forum_ids), $negate) : 'forum_id ' . ($negate ? '!= ' : '= ') . (int) $forum_ids; + break; + + // Global + case 1: + $sql_where .= 'forum_id = 0'; + break; + } + + $sql_where .= ')'; + + return $sql_where; + } + + /** + * Fills the values array, meaning that if a point value is not available in the database + * the key is still set with a default value of 0.00 + * + * @param array $values Array of point values + * @param array $fields Array of action types fields + * @param array|int $forum_ids The forum identifier(s) + * @return void + * @access protected + */ + protected function fill_values(array &$values, $fields, $forum_ids) + { + // Make sure all forum ids are set + $fill = is_array($forum_ids) ? array_map('intval', $forum_ids) : [(int) $forum_ids]; + $fill = array_fill_keys($fill, []); + $values = $values + $fill; + + // Iterate over the set values + foreach ($values as $forum_id => $values_array) + { + // The scope: 0 - local | 1 - global + $scope = (int) empty($forum_id); + + // Set up an array with all fields as array and a default value + $requested = array_fill_keys($fields[$scope], 0.00); + + // Merge the set values with the requested values, where the set values take precedence + $values[$forum_id] = array_merge($requested, $values_array); + } + } +} diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/aps_display.html b/ext/phpbbstudio/aps/styles/prosilver/template/aps_display.html new file mode 100644 index 0000000..2c2b4ff --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/aps_display.html @@ -0,0 +1,50 @@ +{% extends '@phpbbstudio_aps/aps_main.html' %} + +{% block nav %} + {% for page in aps_navbar %} +
  • + + {{ page.TITLE }} + +
  • + {% endfor %} +{% endblock %} + +{% block nav_right %} + {% if S_REGISTERED_USER %} + + {% endif %} +{% endblock %} + +{% block main %} +
    + {% for block in aps_blocks %} + {{ include('@phpbbstudio_aps/blocks/base.html', {'block': block}) }} + {% endfor %} +
    +
    +
    +

    {{ lang('APS_POINTS_BLOCK_NONE') }}

    +
    +
    +
    +
    +{% endblock %} + +{% INCLUDEJS '@phpbbstudio_aps/js/chart.bundle.min.js' %} +{% INCLUDEJS '@phpbbstudio_aps/js/jquery-ui-sortable.min.js' %} +{% INCLUDEJS '@phpbbstudio_aps/js/palette.js' %} +{% INCLUDEJS '@phpbbstudio_aps/js/aps_display.js' %} diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/aps_display_search.html b/ext/phpbbstudio/aps/styles/prosilver/template/aps_display_search.html new file mode 100644 index 0000000..31bb978 --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/aps_display_search.html @@ -0,0 +1,36 @@ + diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/aps_display_sort.html b/ext/phpbbstudio/aps/styles/prosilver/template/aps_display_sort.html new file mode 100644 index 0000000..3d313ad --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/aps_display_sort.html @@ -0,0 +1,16 @@ + diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/aps_main.html b/ext/phpbbstudio/aps/styles/prosilver/template/aps_main.html new file mode 100644 index 0000000..e931859 --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/aps_main.html @@ -0,0 +1,79 @@ +{% include 'overall_header.html' %} + +{% if not definition.INCLUDED_APS_DISPLAYCSS %} + {% INCLUDECSS '@phpbbstudio_aps/aps_display.css' %} + {% DEFINE INCLUDED_APS_DISPLAYCSS = true %} +{% endif %} + +{% if not definition.INCLUDED_APS_FORMCSS %} + {% INCLUDECSS '@phpbbstudio_aps/aps_form.css' %} + {% DEFINE INCLUDED_APS_FORMCSS = true %} +{% endif %} + +{% block includes %}{% endblock %} + +
    +
    +
    +
    + {# Reserved: Please do not use this event #} + {% EVENT phpbbstudio_aps_menu_before %} +
    +
    +
    + +
    +
    + +
      + {% EVENT phpbbstudio_aps_menu_right_prepend %} +
    • + + {{ aps_display(user.data.user_points, false) }} + +
    • +
    +
    + + {% set nav = block('nav') is defined ? block('nav')|trim : '' %} + {% set nav_right = block('nav_right') is defined ? block('nav_right')|trim : '' %} + + {% if nav != '' or nav_right != '' %} +
    + {% if nav != '' %} +
      + {{ nav }} +
    + {% endif %} + {% if nav_right != '' %} +
      + {{ nav_right }} +
    + {% endif %} +
    + {% endif %} +
    + +
    + {% block main %}{% endblock %} +
    + + +
    + +{% if not definition.INCLUDED_SWEETALERT2ALLMINJS %} + {% INCLUDEJS '@phpbbstudio_aps/js/sweetalert2.all.min.js' %} + {% DEFINE INCLUDED_SWEETALERT2ALLMINJS = true %} +{% endif %} + +{% include 'overall_footer.html' %} diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/blocks/base.html b/ext/phpbbstudio/aps/styles/prosilver/template/blocks/base.html new file mode 100644 index 0000000..fc16b40 --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/blocks/base.html @@ -0,0 +1,28 @@ +{% set width = block('width', block.TEMPLATE) is defined ? block('width', block.TEMPLATE) : 's12' %} +
    +
    +
    +

    {{ block.TITLE }}

    + {% if S_USER_LOGGED_IN and not S_IS_BOT %} + {% if not block.S_REQUIRED %} + + + + {% endif %} + + {% endif %} +
    +
    + {% if block('content', block.TEMPLATE) is defined %} + {{ block('content', block.TEMPLATE) }} + {% else %} +

    {{ lang('APS_POINTS_BLOCK_NO_CONTENT') }}

    + {% endif %} +
    + {% if block('footer', block.TEMPLATE) is defined %} + + {% endif %} +
    +
    diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/blocks/points_actions.html b/ext/phpbbstudio/aps/styles/prosilver/template/blocks/points_actions.html new file mode 100644 index 0000000..781bb87 --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/blocks/points_actions.html @@ -0,0 +1,104 @@ +{% block width %}s12{% endblock %} + +{% block content %} + {% if aps_actions %} +
    + {% for action in aps_actions %} +
    + {{ APS_ACTIONS_AVATARS[action.USER_ID] is not empty ? APS_ACTIONS_AVATARS[action.USER_ID] : (S_APS_DAE_ENABLED ? '' : APS_ACTIONS_NO_AVATAR) }} +
    +
    + {{ action.USER }} +
    +
    + {{ lang(action.ACTION) }} + {% if not action.S_SELF %} +
    {{ lang('POST_BY_AUTHOR') }} + {% if action.S_MOD and not action.S_AUTH_MOD %} + {{ lang('MODERATOR') }} + {% else %} + {{ action.REPORTEE }} + {% endif %} + {% endif %} +
    + {% if action.S_AUTH_BUILD %} +
    + {% if action.S_AUTH_BUILD_OTHER %} +
    + +
    + + {% endif %} +
    + {% endif %} +
    +
    + {{ aps_display(action.POINTS_OLD, false) }} +
    +
    + {{ aps_display(action.POINTS_SUM, false) }} +
    +
    + {{ aps_display(action.POINTS_NEW, false) }} +
    +
    +
    + {{ user.format_date(action.TIME) }} +
    +
    + {% if action.FORUM_NAME or action.TOPIC_TITLE or action.POST_SUBJECT %} + + {% else %} +
    {{ lang('NA') }}
    + {% endif %} +
    +
    +
    + {% endfor %} +
    + {% else %} +
    {{ lang('APS_POINTS_ACTIONS_NONE', aps_name()) }}
    + {% endif %} +{% endblock %} + +{% block footer %} + {# Pagination #} + + + {# Search and sort #} +
    + {# Sort options #} + {% include '@phpbbstudio_aps/aps_display_sort.html' %} + + {# Search options #} + {% include '@phpbbstudio_aps/aps_display_search.html' %} + + {# Search keywords #} + + +
    +{% endblock %} diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/blocks/points_adjustments.html b/ext/phpbbstudio/aps/styles/prosilver/template/blocks/points_adjustments.html new file mode 100644 index 0000000..f06a779 --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/blocks/points_adjustments.html @@ -0,0 +1,79 @@ +{% block width %}s12{% endblock %} + +{% block content %} + {% if aps_adjustments %} +
    + {% for action in aps_adjustments %} +
    + {{ APS_ADJUSTMENTS_AVATARS[action.USER_ID] is not empty ? APS_ADJUSTMENTS_AVATARS[action.USER_ID] : (S_APS_DAE_ENABLED ? '' : APS_ADJUSTMENTS_NO_AVATAR) }} +
    +
    + {{ action.USER }} +
    +
    + {{ lang(action.ACTION) }} +
    + {% if action.S_AUTH_BUILD %} +
    + {% if action.S_AUTH_BUILD_OTHER %} +
    + +
    + + {% endif %} +
    + {% endif %} +
    +
    + {{ aps_display(action.POINTS_OLD, false) }} +
    +
    + {{ aps_display(action.POINTS_SUM, false) }} +
    +
    + {{ aps_display(action.POINTS_NEW, false) }} +
    +
    +
    + {{ user.format_date(action.TIME) }} + {% if not action.S_SELF %} + {{ lang('POST_BY_AUTHOR') }} + {% if action.S_MOD and not action.S_AUTH_MOD %} + {{ lang('MODERATOR') }} + {% else %} + {{ action.REPORTEE }} + {% endif %} + {% endif %} +
    +
    + {% if action.FORUM_NAME or action.TOPIC_TITLE or action.POST_SUBJECT %} + + {% else %} +
    {{ lang('NA') }}
    + {% endif %} +
    +
    +
    + {% endfor %} +
    + {% else %} +
    {{ lang('APS_POINTS_ACTIONS_NONE', aps_name()) }}
    + {% endif %} +{% endblock %} diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/blocks/points_forums.html b/ext/phpbbstudio/aps/styles/prosilver/template/blocks/points_forums.html new file mode 100644 index 0000000..6076cf4 --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/blocks/points_forums.html @@ -0,0 +1,18 @@ +{% block width %}s12 m6 aps-js{% endblock %} + +{% block content %} + +

    {{ lang('APS_POINTS_PER_FORUM', aps_name()) }}

    +
    + +{% endblock %} diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/blocks/points_groups.html b/ext/phpbbstudio/aps/styles/prosilver/template/blocks/points_groups.html new file mode 100644 index 0000000..a0d5997 --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/blocks/points_groups.html @@ -0,0 +1,16 @@ +{% block width %}s12 m6 aps-js{% endblock %} + +{% block content %} + +

    {{ lang('APS_POINTS_PER_GROUP', aps_name()) }}

    +
    + +{% endblock %} diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/blocks/points_growth.html b/ext/phpbbstudio/aps/styles/prosilver/template/blocks/points_growth.html new file mode 100644 index 0000000..e9bea8a --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/blocks/points_growth.html @@ -0,0 +1,22 @@ +{% block width %}s12 m6 aps-js{% endblock %} + +{% block content %} + +

    {{ lang('APS_POINTS_GROWTH', aps_name()) }}

    +
    + +{% endblock %} diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/blocks/points_random.html b/ext/phpbbstudio/aps/styles/prosilver/template/blocks/points_random.html new file mode 100644 index 0000000..5a20409 --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/blocks/points_random.html @@ -0,0 +1,13 @@ +{% block width %}s12 m6{% endblock %} + +{% block content %} +
    +
    + {{ APS_RANDOM_USER_AVATAR ? APS_RANDOM_USER_AVATAR : (S_APS_DAE_ENABLED ? '' : APS_RANDOM_NO_AVATAR) }} +
    +
    + {{ APS_RANDOM_USER_FULL }}
    + {{ aps_display(APS_RANDOM_USER_POINTS) }} +
    +
    +{% endblock %} diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/blocks/points_search.html b/ext/phpbbstudio/aps/styles/prosilver/template/blocks/points_search.html new file mode 100644 index 0000000..75af260 --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/blocks/points_search.html @@ -0,0 +1,29 @@ +{% block width %}s12 m6{% endblock %} + +{% block content %} +
    +
    + {{ APS_SEARCH_USER_AVATAR ? APS_SEARCH_USER_AVATAR : (S_APS_DAE_ENABLED ? '' : APS_NO_AVATAR) }} +
    +
    + {{ APS_SEARCH_USER_FULL }}
    + {{ aps_display(APS_SEARCH_USER_POINTS) }}
    + {% set podium = APS_SEARCH_USER_RANK in 1..3 ? APS_SEARCH_USER_RANK : 0 %} + {{ APS_SEARCH_USER_RANK }} + {% if S_APS_USER_ADJUST and U_APS_SEARCH_USER_ADJUST %} +
    + + + + {% endif %} +
    +
    +{% endblock %} + +{% block footer %} +
    + + + +
    +{% endblock %} diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/blocks/points_settings.html b/ext/phpbbstudio/aps/styles/prosilver/template/blocks/points_settings.html new file mode 100644 index 0000000..e23ec6c --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/blocks/points_settings.html @@ -0,0 +1,18 @@ +{% block width %}s12 m6{% endblock %} + +{% block content %} + {% set min = aps_config('aps_points_min') %} + {% set max = aps_config('aps_points_max') %} + {% set def = aps_config('default_lang') %} + {% set name = aps_config('aps_points_name_' ~ def) %} + {% set name = user.lang_name != def and name != aps_name() ? name : '' %} + +
    +
    {{ lang('APS_POINTS_NAME') ~ lang('COLON') }}
    {{ aps_name() }}{% if name %} ({{ name }}){% endif %}
    +
    {{ lang('APS_POINTS_FORMAT', aps_name()) ~ lang('COLON') }}
    {{ aps_display(12345.00) }}
    +
    {{ lang('APS_POINTS_MIN', aps_name()) ~ lang('COLON') }}
    {{ min ? min : lang('NA') }}
    +
    {{ lang('APS_POINTS_MAX', aps_name()) ~ lang('COLON') }}
    {{ max ? max : lang('NA') }}
    +
    {{ lang('APS_POINTS_ACTIONS_PAGE') ~ lang('COLON') }}
    {{ aps_config('aps_actions_per_page') }}
    + {% EVENT phpbbstudio_aps_display_settings_append %} +
    +{% endblock %} diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/blocks/points_top.html b/ext/phpbbstudio/aps/styles/prosilver/template/blocks/points_top.html new file mode 100644 index 0000000..df34fc2 --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/blocks/points_top.html @@ -0,0 +1,36 @@ +{% block width %}s12 m6{% endblock %} + +{% block content %} +
    + {% set last_place = 0 %} + {% set last_points = 0 %} + {% for user in top_users %} + {% set same_points = last_points == user.POINTS %} + {% set last_place = same_points ? last_place : loop.index %} + {% set last_points = same_points ? last_points : user.POINTS %} +
    + {{ user.AVATAR ? user.AVATAR : (S_APS_DAE_ENABLED ? '' : APS_NO_AVATAR) }} + {{ user.NAME }} +

    {{ aps_display(user.POINTS) }}

    + {% if S_APS_USER_ADJUST %} + + + + {% elseif last_place < 4 %} + + {% else %} + {{ last_place }} + {% endif %} +
    + {% endfor %} +
    +{% endblock %} + +{% block footer %} +
    + + + {% if aps_config('aps_display_top_change') %}{% endif %} +
    +{% endblock %} + diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/blocks/points_trade_off.html b/ext/phpbbstudio/aps/styles/prosilver/template/blocks/points_trade_off.html new file mode 100644 index 0000000..82af6b9 --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/blocks/points_trade_off.html @@ -0,0 +1,28 @@ +{% block width %}s12 m6 aps-js{% endblock %} + +{% block content %} + +

    {{ lang('APS_POINTS_TRADE_OFF', aps_name()) }}

    +
    + + +{% endblock %} diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/event/memberlist_view_user_statistics_before.html b/ext/phpbbstudio/aps/styles/prosilver/template/event/memberlist_view_user_statistics_before.html new file mode 100644 index 0000000..2ba7eba --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/event/memberlist_view_user_statistics_before.html @@ -0,0 +1,2 @@ +{% if aps_config('aps_points_display_profile') %}
    {{ aps_name() ~ lang('COLON') }}
    {{ aps_display(USER_POINTS) }}
    {% endif %} +{% EVENT phpbbstudio_aps_profile_append %} diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/event/navbar_header_profile_list_after.html b/ext/phpbbstudio/aps/styles/prosilver/template/event/navbar_header_profile_list_after.html new file mode 100644 index 0000000..29daad8 --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/event/navbar_header_profile_list_after.html @@ -0,0 +1,7 @@ +{% if S_APS_NAVBAR_HEADER_PROFILE_LIST_AFTER %} +
  • + + {{ aps_icon() }}{{ aps_display(user.data.user_points, false) }} + +
  • +{% endif %} diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/event/navbar_header_profile_list_before.html b/ext/phpbbstudio/aps/styles/prosilver/template/event/navbar_header_profile_list_before.html new file mode 100644 index 0000000..e67ae7b --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/event/navbar_header_profile_list_before.html @@ -0,0 +1,7 @@ +{% if S_APS_NAVBAR_HEADER_PROFILE_LIST_BEFORE %} +
  • + + {{ aps_icon() }}{{ aps_display(user.data.user_points, false) }} + +
  • +{% endif %} diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/event/navbar_header_quick_links_after.html b/ext/phpbbstudio/aps/styles/prosilver/template/event/navbar_header_quick_links_after.html new file mode 100644 index 0000000..d7b8303 --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/event/navbar_header_quick_links_after.html @@ -0,0 +1,7 @@ +{% if S_APS_NAVBAR_HEADER_QUICK_LINKS_AFTER %} +
  • + + {{ aps_icon() }}{{ aps_display(user.data.user_points, false) }} + +
  • +{% endif %} diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/event/navbar_header_quick_links_before.html b/ext/phpbbstudio/aps/styles/prosilver/template/event/navbar_header_quick_links_before.html new file mode 100644 index 0000000..a86a00e --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/event/navbar_header_quick_links_before.html @@ -0,0 +1,7 @@ +{% if S_APS_NAVBAR_HEADER_QUICK_LINKS_BEFORE %} +
  • + + {{ aps_icon() }}{{ aps_display(user.data.user_points, false) }} + +
  • +{% endif %} diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/event/navbar_header_user_profile_append.html b/ext/phpbbstudio/aps/styles/prosilver/template/event/navbar_header_user_profile_append.html new file mode 100644 index 0000000..b976a08 --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/event/navbar_header_user_profile_append.html @@ -0,0 +1,7 @@ +{% if S_APS_NAVBAR_HEADER_USER_PROFILE_APPEND %} +
  • + + {{ aps_icon() }}{{ aps_display(user.data.user_points, false) }} + +
  • +{% endif %} diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/event/navbar_header_user_profile_prepend.html b/ext/phpbbstudio/aps/styles/prosilver/template/event/navbar_header_user_profile_prepend.html new file mode 100644 index 0000000..3f87089 --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/event/navbar_header_user_profile_prepend.html @@ -0,0 +1,7 @@ +{% if S_APS_NAVBAR_HEADER_USER_PROFILE_PREPEND %} +
  • + + {{ aps_icon() }}{{ aps_display(user.data.user_points, false) }} + +
  • +{% endif %} diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/event/overall_footer_breadcrumb_append.html b/ext/phpbbstudio/aps/styles/prosilver/template/event/overall_footer_breadcrumb_append.html new file mode 100644 index 0000000..97ecf81 --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/event/overall_footer_breadcrumb_append.html @@ -0,0 +1,7 @@ +{% if S_APS_OVERALL_FOOTER_BREADCRUMB_APPEND %} +
  • + + {{ aps_icon() }}{{ aps_display(user.data.user_points, false) }} + +
  • +{% endif %} diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/event/overall_footer_teamlink_after.html b/ext/phpbbstudio/aps/styles/prosilver/template/event/overall_footer_teamlink_after.html new file mode 100644 index 0000000..974bf50 --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/event/overall_footer_teamlink_after.html @@ -0,0 +1,7 @@ +{% if S_APS_OVERALL_FOOTER_TEAMLINK_AFTER %} +
  • + + {{ aps_icon() }}{{ aps_display(user.data.user_points, false) }} + +
  • +{% endif %} diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/event/overall_footer_teamlink_before.html b/ext/phpbbstudio/aps/styles/prosilver/template/event/overall_footer_teamlink_before.html new file mode 100644 index 0000000..5d76531 --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/event/overall_footer_teamlink_before.html @@ -0,0 +1,7 @@ +{% if S_APS_OVERALL_FOOTER_TEAMLINK_BEFORE %} +
  • + + {{ aps_icon() }}{{ aps_display(user.data.user_points, false) }} + +
  • +{% endif %} diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/event/overall_footer_timezone_after.html b/ext/phpbbstudio/aps/styles/prosilver/template/event/overall_footer_timezone_after.html new file mode 100644 index 0000000..6b3ab42 --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/event/overall_footer_timezone_after.html @@ -0,0 +1,7 @@ +{% if S_APS_OVERALL_FOOTER_TIMEZONE_AFTER %} +
  • + + {{ aps_icon() }}{{ aps_display(user.data.user_points, false) }} + +
  • +{% endif %} diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/event/overall_footer_timezone_before.html b/ext/phpbbstudio/aps/styles/prosilver/template/event/overall_footer_timezone_before.html new file mode 100644 index 0000000..8021c4b --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/event/overall_footer_timezone_before.html @@ -0,0 +1,7 @@ +{% if S_APS_OVERALL_FOOTER_TIMEZONE_BEFORE %} +
  • + + {{ aps_icon() }}{{ aps_display(user.data.user_points, false) }} + +
  • +{% endif %} diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/event/overall_header_navigation_append.html b/ext/phpbbstudio/aps/styles/prosilver/template/event/overall_header_navigation_append.html new file mode 100644 index 0000000..99ac3ba --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/event/overall_header_navigation_append.html @@ -0,0 +1,7 @@ +{% if S_APS_OVERALL_HEADER_NAVIGATION_APPEND %} +
  • + + {{ aps_icon() }}{{ aps_display(user.data.user_points, false) }} + +
  • +{% endif %} diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/event/overall_header_navigation_prepend.html b/ext/phpbbstudio/aps/styles/prosilver/template/event/overall_header_navigation_prepend.html new file mode 100644 index 0000000..d2f5a60 --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/event/overall_header_navigation_prepend.html @@ -0,0 +1,7 @@ +{% if S_APS_OVERALL_HEADER_NAVIGATION_PREPEND %} +
  • + + {{ aps_icon() }}{{ aps_display(user.data.user_points, false) }} + +
  • +{% endif %} diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/event/ucp_pm_viewmessage_rank_after.html b/ext/phpbbstudio/aps/styles/prosilver/template/event/ucp_pm_viewmessage_rank_after.html new file mode 100644 index 0000000..361e114 --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/event/ucp_pm_viewmessage_rank_after.html @@ -0,0 +1,2 @@ +{% if aps_config('aps_points_display_pm') %}
    {{ aps_name() ~ lang('COLON') }} {{ aps_display(AUTHOR_POINTS) }}
    {% endif %} +{% EVENT phpbbstudio_aps_pm_append %} diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/event/viewtopic_body_postrow_rank_after.html b/ext/phpbbstudio/aps/styles/prosilver/template/event/viewtopic_body_postrow_rank_after.html new file mode 100644 index 0000000..31138ff --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/event/viewtopic_body_postrow_rank_after.html @@ -0,0 +1,2 @@ +{% if aps_config('aps_points_display_post') %}
    {{ aps_name() ~ lang('COLON') }} {{ aps_display(postrow.POSTER_POINTS) }}
    {% endif %} +{% EVENT phpbbstudio_aps_post_append %} diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/js/aps_display.js b/ext/phpbbstudio/aps/styles/prosilver/template/js/aps_display.js new file mode 100644 index 0000000..db97528 --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/js/aps_display.js @@ -0,0 +1,255 @@ +/** @function jQuery */ +jQuery(function($) { + let aps = { + body: $('.aps-body'), + empty: $('[data-aps-empty-panel]'), + darken: $('#darkenwrapper'), + classes: { + column: '.aps-col', + content: '.aps-panel-content', + panel: '.aps-panel', + pagination: '.aps-panel-footer .pagination' + }, + add: { + el: $('.aps-panel-add'), + class: 'aps-panel-add-pulse', + content: '.dropdown-contents', + trigger: '.dropdown-trigger' + }, + sortable: { + el: $('[data-aps-sort]'), + url: 'aps-sort', + attr: 'data-aps-id', + data: { + delay: 150, + cursor: 'grabbing', + tolerance: 'pointer', + handle: '.aps-panel-move', + placeholder: 'aps-panel-placeholder', + forcePlaceholderSize: true + } + }, + augmentation: { + selector: '[data-aps-augmentation]' + }, + charts: { + selector: '[data-aps-chart]', + data: { + chart: 'aps-chart', + colour: 'aps-colour', + border: 'aps-colour-border', + point: 'aps-colour-point', + label: 'aps-label', + labels: 'aps-labels', + time: 'aps-time', + value: 'aps-value' + } + } + }; + + aps.add.pulse = function() { + this.el.children(this.trigger).toggleClass(this.class, !aps.empty.is(':hidden')); + }; + + aps.ajaxify = function(context) { + $('[data-ajax]', context).each(function() { + let $this = $(this), + ajax = $this.data('ajax'), + filter = $this.data('filter'); + + if (ajax !== 'false') { + phpbb.ajaxify({ + selector: this, + callback: ajax !== 'true' ? ajax : null, + refresh: aps.defined($this.data('refresh')), + filter: aps.defined(filter) ? phpbb.getFunctionByName(filter) : null + }) + } + }); + }; + + aps.defined = function(operand) { + return typeof operand !== 'undefined'; + }; + + aps.message = function(title, text, type, time) { + swal({ + title: title, + text: text, + type: type || 'success', + timer: aps.defined(time) ? time : 1500, + showConfirmButton: false + }); + }; + + aps.sortable.el.each(function() { + $(this).sortable($.extend(aps.sortable.data, { + containment: $(this), + update: function() { + $.ajax({ + url: $(this).data(aps.sortable.url), + type: 'POST', + data: { + order: $(this).sortable('toArray', { attribute: aps.sortable.attr }), + }, + error: function() { + aps.message(aps.darken.data('ajax-error-title'), aps.darken.data('ajax-error-text'), 'error', null); + }, + success: function(r) { + aps.message(r.APS_TITLE, r.APS_TEXT); + } + }); + } + })); + }); + + aps.augmentation.register = function(context) { + $(aps.augmentation.selector, context).each(function() { + $(this).on('click', function() { + let $parent = $(this).parent(), + $avatar = $parent.parent().siblings('img'); + + swal({ + title: $parent.siblings().first().html(), + html: $(this).next('.hidden').html(), + imageUrl: $avatar.attr('src'), + imageAlt: $avatar.attr('alt'), + imageWidth: $avatar.attr('width'), + imageHeight: $avatar.attr('height') + }); + }); + }); + }; + + aps.augmentation.register(null); + + aps.charts.draw = function() { + $(aps.charts.selector).each(function() { + let data = { + datasets: [], + labels: [] + }; + + $(this).siblings('.hidden').each(function(i) { + data.datasets[i] = { + backgroundColor: [], + data: [] + }; + + $(this).children().each(function() { + data.datasets[i].data.push($(this).data(aps.charts.data.value)); + + let colour = $(this).data(aps.charts.data.colour), + border = $(this).data(aps.charts.data.border), + point = $(this).data(aps.charts.data.point), + label = $(this).data(aps.charts.data.label), + labels = $(this).data(aps.charts.data.labels); + + if (aps.defined(colour)) { + data.datasets[i].backgroundColor.push(colour); + data.datasets[i].fill = colour; + } + + if (aps.defined(border)) { + data.datasets[i].borderColor = colour; + data.datasets[i].pointBorderColor = colour; + } + + if (aps.defined(point)) { + data.datasets[i].pointBackgroundColor = point; + } + + if (aps.defined(label)) { + data.datasets[i].label = label; + } + + if (aps.defined(labels)) { + data.labels.push(labels); + } + }); + + if (!data.datasets[i].backgroundColor.length) { + data.datasets[i].backgroundColor = palette('tol', data.datasets[i].data.length).map(function(hex) { + return '#' + hex; + }); + } + }); + + new Chart($(this), { + type: $(this).data(aps.charts.data.chart), + data: data, + responsive: true, + maintainAspectRatio: false, + options: { + animation: { duration: $(this).data(aps.charts.data.time) }, + legend: { position: 'bottom' } + } + }); + }); + }; + + /** + * Register a chart plugin. + * This plugin checks for empty charts. + * If the chart is empty, the canvas' data-aps-empty is displayed instead. + */ + Chart.plugins.register({ + afterDraw: function(chart) { + if (!chart.config.data.datasets[0].data.length) { + let ctx = chart.chart.ctx, + width = chart.chart.width, + height = chart.chart.height; + + chart.clear(); + + ctx.save(); + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText(chart.canvas.dataset.apsEmpty, width / 2, height / 2); + ctx.restore(); + } + } + }); + + /** + * Add a "fake" timeout to the Chart drawing animation, + * otherwise the animation does not show up. + */ + setTimeout(aps.charts.draw, 0); + + phpbb.addAjaxCallback('aps_add', function(r) { + if (r.success) { + $(this).parent('li').remove(); + + let panel = $(r.success).insertBefore(aps.empty, null); + + aps.ajaxify(panel); + aps.add.pulse(); + aps.charts.draw(); + aps.body.trigger('click'); + aps.message(r.APS_TITLE, r.APS_TEXT); + } + }); + + phpbb.addAjaxCallback('aps_remove', function(r) { + if (r.success) { + $(this).parents(aps.classes.column).remove(); + + let item = aps.add.el.find(aps.add.content).append(r.success); + + aps.ajaxify(item); + aps.add.pulse(); + aps.message(r.APS_TITLE, r.APS_TEXT); + } + }); + + phpbb.addAjaxCallback('aps_replace', function(r) { + let $old = $(this).parents(aps.classes.panel), + $new = $(r.body); + + $old.find(aps.classes.content).html($new.find(aps.classes.content)); + $old.find(aps.classes.pagination).html($new.find(aps.classes.pagination)); + + aps.augmentation.register($old); + }); +}); diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/js/aps_mcp.js b/ext/phpbbstudio/aps/styles/prosilver/template/js/aps_mcp.js new file mode 100644 index 0000000..80d00e4 --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/js/aps_mcp.js @@ -0,0 +1,23 @@ +jQuery(function($) { + let $action = $('#action'), + $points = $('#points'), + $reason = $('#reason'); + + let apsChangeAction = function() { + let $selected = $(this).find(':selected'), + points = $selected.data('points'), + reason = $selected.data('reason'); + + if (points || $selected.context === undefined) { + $points.prop('disabled', 'disabled').val(points); + $reason.prop('disabled', 'disabled').val(reason); + } else { + $points.prop('disabled', false).val(''); + $reason.prop('disabled', false).val(''); + } + }; + + apsChangeAction(); + + $action.on('change', apsChangeAction); +}); diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/js/chart.bundle.min.js b/ext/phpbbstudio/aps/styles/prosilver/template/js/chart.bundle.min.js new file mode 100644 index 0000000..c157d33 --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/js/chart.bundle.min.js @@ -0,0 +1,10 @@ +/*! + * Chart.js + * http://chartjs.org/ + * Version: 2.7.3 + * + * Copyright 2018 Chart.js Contributors + * Released under the MIT license + * https://github.com/chartjs/Chart.js/blob/master/LICENSE.md + */ +!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).Chart=t()}}(function(){return function r(o,s,l){function u(e,t){if(!s[e]){if(!o[e]){var n="function"==typeof require&&require;if(!t&&n)return n(e,!0);if(d)return d(e,!0);var i=new Error("Cannot find module '"+e+"'");throw i.code="MODULE_NOT_FOUND",i}var a=s[e]={exports:{}};o[e][0].call(a.exports,function(t){return u(o[e][1][t]||t)},a,a.exports,r,o,s,l)}return s[e].exports}for(var d="function"==typeof require&&require,t=0;t>>0,i=0;iwt(t)?(r=t+1,s-wt(t)):(r=t,s),{year:r,dayOfYear:o}}function Bt(t,e,n){var i,a,r=Vt(t.year(),e,n),o=Math.floor((t.dayOfYear()-r-1)/7)+1;return o<1?i=o+Et(a=t.year()-1,e,n):o>Et(t.year(),e,n)?(i=o-Et(t.year(),e,n),a=t.year()+1):(a=t.year(),i=o),{week:i,year:a}}function Et(t,e,n){var i=Vt(t,e,n),a=Vt(t+1,e,n);return(wt(t)-i+a)/7}B("w",["ww",2],"wo","week"),B("W",["WW",2],"Wo","isoWeek"),A("week","w"),A("isoWeek","W"),Y("week",5),Y("isoWeek",5),lt("w",J),lt("ww",J,G),lt("W",J),lt("WW",J,G),ft(["w","ww","W","WW"],function(t,e,n,i){e[i.substr(0,1)]=M(t)});B("d",0,"do","day"),B("dd",0,0,function(t){return this.localeData().weekdaysMin(this,t)}),B("ddd",0,0,function(t){return this.localeData().weekdaysShort(this,t)}),B("dddd",0,0,function(t){return this.localeData().weekdays(this,t)}),B("e",0,0,"weekday"),B("E",0,0,"isoWeekday"),A("day","d"),A("weekday","e"),A("isoWeekday","E"),Y("day",11),Y("weekday",11),Y("isoWeekday",11),lt("d",J),lt("e",J),lt("E",J),lt("dd",function(t,e){return e.weekdaysMinRegex(t)}),lt("ddd",function(t,e){return e.weekdaysShortRegex(t)}),lt("dddd",function(t,e){return e.weekdaysRegex(t)}),ft(["dd","ddd","dddd"],function(t,e,n,i){var a=n._locale.weekdaysParse(t,i,n._strict);null!=a?e.d=a:v(n).invalidWeekday=t}),ft(["d","e","E"],function(t,e,n,i){e[i]=M(t)});var jt="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_");var Ut="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_");var Gt="Su_Mo_Tu_We_Th_Fr_Sa".split("_");var qt=ot;var Zt=ot;var Xt=ot;function Jt(){function t(t,e){return e.length-t.length}var e,n,i,a,r,o=[],s=[],l=[],u=[];for(e=0;e<7;e++)n=p([2e3,1]).day(e),i=this.weekdaysMin(n,""),a=this.weekdaysShort(n,""),r=this.weekdays(n,""),o.push(i),s.push(a),l.push(r),u.push(i),u.push(a),u.push(r);for(o.sort(t),s.sort(t),l.sort(t),u.sort(t),e=0;e<7;e++)s[e]=dt(s[e]),l[e]=dt(l[e]),u[e]=dt(u[e]);this._weekdaysRegex=new RegExp("^("+u.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+l.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+s.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+o.join("|")+")","i")}function $t(){return this.hours()%12||12}function Kt(t,e){B(t,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),e)})}function Qt(t,e){return e._meridiemParse}B("H",["HH",2],0,"hour"),B("h",["hh",2],0,$t),B("k",["kk",2],0,function(){return this.hours()||24}),B("hmm",0,0,function(){return""+$t.apply(this)+W(this.minutes(),2)}),B("hmmss",0,0,function(){return""+$t.apply(this)+W(this.minutes(),2)+W(this.seconds(),2)}),B("Hmm",0,0,function(){return""+this.hours()+W(this.minutes(),2)}),B("Hmmss",0,0,function(){return""+this.hours()+W(this.minutes(),2)+W(this.seconds(),2)}),Kt("a",!0),Kt("A",!1),A("hour","h"),Y("hour",13),lt("a",Qt),lt("A",Qt),lt("H",J),lt("h",J),lt("k",J),lt("HH",J,G),lt("hh",J,G),lt("kk",J,G),lt("hmm",$),lt("hmmss",K),lt("Hmm",$),lt("Hmmss",K),ct(["H","HH"],vt),ct(["k","kk"],function(t,e,n){var i=M(t);e[vt]=24===i?0:i}),ct(["a","A"],function(t,e,n){n._isPm=n._locale.isPM(t),n._meridiem=t}),ct(["h","hh"],function(t,e,n){e[vt]=M(t),v(n).bigHour=!0}),ct("hmm",function(t,e,n){var i=t.length-2;e[vt]=M(t.substr(0,i)),e[bt]=M(t.substr(i)),v(n).bigHour=!0}),ct("hmmss",function(t,e,n){var i=t.length-4,a=t.length-2;e[vt]=M(t.substr(0,i)),e[bt]=M(t.substr(i,2)),e[yt]=M(t.substr(a)),v(n).bigHour=!0}),ct("Hmm",function(t,e,n){var i=t.length-2;e[vt]=M(t.substr(0,i)),e[bt]=M(t.substr(i))}),ct("Hmmss",function(t,e,n){var i=t.length-4,a=t.length-2;e[vt]=M(t.substr(0,i)),e[bt]=M(t.substr(i,2)),e[yt]=M(t.substr(a))});var te,ee=Ct("Hours",!0),ne={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:At,monthsShort:Ft,week:{dow:0,doy:6},weekdays:jt,weekdaysMin:Gt,weekdaysShort:Ut,meridiemParse:/[ap]\.?m?\.?/i},ie={},ae={};function re(t){return t?t.toLowerCase().replace("_","-"):t}function oe(t){var e=null;if(!ie[t]&&void 0!==jn&&jn&&jn.exports)try{e=te._abbr,En("./locale/"+t),se(e)}catch(t){}return ie[t]}function se(t,e){var n;return t&&((n=u(e)?ue(t):le(t,e))?te=n:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+t+" not found. Did you forget to load it?")),te._abbr}function le(t,e){if(null===e)return delete ie[t],null;var n,i=ne;if(e.abbr=t,null!=ie[t])C("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),i=ie[t]._config;else if(null!=e.parentLocale)if(null!=ie[e.parentLocale])i=ie[e.parentLocale]._config;else{if(null==(n=oe(e.parentLocale)))return ae[e.parentLocale]||(ae[e.parentLocale]=[]),ae[e.parentLocale].push({name:t,config:e}),null;i=n._config}return ie[t]=new O(T(i,e)),ae[t]&&ae[t].forEach(function(t){le(t.name,t.config)}),se(t),ie[t]}function ue(t){var e;if(t&&t._locale&&t._locale._abbr&&(t=t._locale._abbr),!t)return te;if(!s(t)){if(e=oe(t))return e;t=[t]}return function(t){for(var e,n,i,a,r=0;r=e&&o(a,n,!0)>=e-1)break;e--}r++}return te}(t)}function de(t){var e,n=t._a;return n&&-2===v(t).overflow&&(e=n[mt]<0||11Ot(n[gt],n[mt])?pt:n[vt]<0||24Et(n,r,o)?v(t)._overflowWeeks=!0:null!=l?v(t)._overflowWeekday=!0:(s=Ht(n,i,a,r,o),t._a[gt]=s.year,t._dayOfYear=s.dayOfYear)}(t),null!=t._dayOfYear&&(r=he(t._a[gt],i[gt]),(t._dayOfYear>wt(r)||0===t._dayOfYear)&&(v(t)._overflowDayOfYear=!0),n=zt(r,0,t._dayOfYear),t._a[mt]=n.getUTCMonth(),t._a[pt]=n.getUTCDate()),e=0;e<3&&null==t._a[e];++e)t._a[e]=o[e]=i[e];for(;e<7;e++)t._a[e]=o[e]=null==t._a[e]?2===e?1:0:t._a[e];24===t._a[vt]&&0===t._a[bt]&&0===t._a[yt]&&0===t._a[xt]&&(t._nextDay=!0,t._a[vt]=0),t._d=(t._useUTC?zt:function(t,e,n,i,a,r,o){var s=new Date(t,e,n,i,a,r,o);return t<100&&0<=t&&isFinite(s.getFullYear())&&s.setFullYear(t),s}).apply(null,o),a=t._useUTC?t._d.getUTCDay():t._d.getDay(),null!=t._tzm&&t._d.setUTCMinutes(t._d.getUTCMinutes()-t._tzm),t._nextDay&&(t._a[vt]=24),t._w&&void 0!==t._w.d&&t._w.d!==a&&(v(t).weekdayMismatch=!0)}}var fe=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,ge=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,me=/Z|[+-]\d\d(?::?\d\d)?/,pe=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],ve=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],be=/^\/?Date\((\-?\d+)/i;function ye(t){var e,n,i,a,r,o,s=t._i,l=fe.exec(s)||ge.exec(s);if(l){for(v(t).iso=!0,e=0,n=pe.length;en.valueOf():n.valueOf()this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},un.isLocal=function(){return!!this.isValid()&&!this._isUTC},un.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},un.isUtc=Ve,un.isUTC=Ve,un.zoneAbbr=function(){return this._isUTC?"UTC":""},un.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},un.dates=n("dates accessor is deprecated. Use date instead.",nn),un.months=n("months accessor is deprecated. Use month instead",Lt),un.years=n("years accessor is deprecated. Use year instead",Dt),un.zone=n("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",function(t,e){return null!=t?("string"!=typeof t&&(t=-t),this.utcOffset(t,e),this):-this.utcOffset()}),un.isDSTShifted=n("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",function(){if(!u(this._isDSTShifted))return this._isDSTShifted;var t={};if(x(t,this),(t=Se(t))._a){var e=t._isUTC?p(t._a):Ce(t._a);this._isDSTShifted=this.isValid()&&0');var n=t.data,i=n.datasets,a=n.labels;if(i.length)for(var r=0;r'),a[r]&&e.push(a[r]),e.push("");return e.push(""),e.join("")},legend:{labels:{generateLabels:function(l){var u=l.data;return u.labels.length&&u.datasets.length?u.labels.map(function(t,e){var n=l.getDatasetMeta(0),i=u.datasets[0],a=n.data[e],r=a&&a.custom||{},o=O.valueAtIndexOrDefault,s=l.options.elements.arc;return{text:t,fillStyle:r.backgroundColor?r.backgroundColor:o(i.backgroundColor,e,s.backgroundColor),strokeStyle:r.borderColor?r.borderColor:o(i.borderColor,e,s.borderColor),lineWidth:r.borderWidth?r.borderWidth:o(i.borderWidth,e,s.borderWidth),hidden:isNaN(i.data[e])||n.data[e].hidden,index:e}}):[]}},onClick:function(t,e){var n,i,a,r=e.index,o=this.chart;for(n=0,i=(o.data.datasets||[]).length;n=Math.PI?-1:f<-Math.PI?1:0))+c,m=Math.cos(f),p=Math.sin(f),v=Math.cos(g),b=Math.sin(g),y=f<=0&&0<=g||f<=2*Math.PI&&2*Math.PI<=g,x=f<=.5*Math.PI&&.5*Math.PI<=g||f<=2.5*Math.PI&&2.5*Math.PI<=g,_=f<=-Math.PI&&-Math.PI<=g||f<=Math.PI&&Math.PI<=g,k=f<=.5*-Math.PI&&.5*-Math.PI<=g||f<=1.5*Math.PI&&1.5*Math.PI<=g,w=h/100,M=_?-1:Math.min(m*(m<0?1:w),v*(v<0?1:w)),S=k?-1:Math.min(p*(p<0?1:w),b*(b<0?1:w)),D=y?1:Math.max(m*(0');var n=t.data,i=n.datasets,a=n.labels;if(i.length)for(var r=0;r'),a[r]&&e.push(a[r]),e.push("");return e.push(""),e.join("")},legend:{labels:{generateLabels:function(s){var l=s.data;return l.labels.length&&l.datasets.length?l.labels.map(function(t,e){var n=s.getDatasetMeta(0),i=l.datasets[0],a=n.data[e].custom||{},r=_.valueAtIndexOrDefault,o=s.options.elements.arc;return{text:t,fillStyle:a.backgroundColor?a.backgroundColor:r(i.backgroundColor,e,o.backgroundColor),strokeStyle:a.borderColor?a.borderColor:r(i.borderColor,e,o.borderColor),lineWidth:a.borderWidth?a.borderWidth:r(i.borderWidth,e,o.borderWidth),hidden:isNaN(i.data[e])||n.data[e].hidden,index:e}}):[]}},onClick:function(t,e){var n,i,a,r=e.index,o=this.chart;for(n=0,i=(o.data.datasets||[]).length;n=e.numSteps?(r.callback(e.onAnimationComplete,[e],n),n.animating=!1,i.splice(a,1)):++a}}},{26:26,46:46}],24:[function(t,e,n){"use strict";var s=t(22),l=t(23),h=t(26),c=t(46),a=t(29),r=t(31),f=t(49),g=t(32),m=t(34),i=t(36);e.exports=function(u){function d(t){return"top"===t||"bottom"===t}u.types={},u.instances={},u.controllers={},c.extend(u.prototype,{construct:function(t,e){var n,i,a=this;(i=(n=(n=e)||{}).data=n.data||{}).datasets=i.datasets||[],i.labels=i.labels||[],n.options=c.configMerge(h.global,h[n.type],n.options||{}),e=n;var r=f.acquireContext(t,e),o=r&&r.canvas,s=o&&o.height,l=o&&o.width;a.id=c.uid(),a.ctx=r,a.canvas=o,a.config=e,a.width=l,a.height=s,a.aspectRatio=s?l/s:null,a.options=e.options,a._bufferedRender=!1,(a.chart=a).controller=a,u.instances[a.id]=a,Object.defineProperty(a,"data",{get:function(){return a.config.data},set:function(t){a.config.data=t}}),r&&o?(a.initialize(),a.update()):console.error("Failed to create chart: can't acquire context from the given item")},initialize:function(){var t=this;return g.notify(t,"beforeInit"),c.retinaScale(t,t.options.devicePixelRatio),t.bindEvents(),t.options.responsive&&t.resize(!0),t.ensureScalesHaveIDs(),t.buildOrUpdateScales(),t.initToolTip(),g.notify(t,"afterInit"),t},clear:function(){return c.canvas.clear(this),this},stop:function(){return l.cancelAnimation(this),this},resize:function(t){var e=this,n=e.options,i=e.canvas,a=n.maintainAspectRatio&&e.aspectRatio||null,r=Math.max(0,Math.floor(c.getMaximumWidth(i))),o=Math.max(0,Math.floor(a?r/a:c.getMaximumHeight(i)));if((e.width!==r||e.height!==o)&&(i.width=e.width=r,i.height=e.height=o,i.style.width=r+"px",i.style.height=o+"px",c.retinaScale(e,n.devicePixelRatio),!t)){var s={width:r,height:o};g.notify(e,"resize",[s]),e.options.onResize&&e.options.onResize(e,s),e.stop(),e.update({duration:e.options.responsiveAnimationDuration})}},ensureScalesHaveIDs:function(){var t=this.options,e=t.scales||{},n=t.scale;c.each(e.xAxes,function(t,e){t.id=t.id||"x-axis-"+e}),c.each(e.yAxes,function(t,e){t.id=t.id||"y-axis-"+e}),n&&(n.id=n.id||"scale")},buildOrUpdateScales:function(){var o=this,t=o.options,s=o.scales||{},e=[],l=Object.keys(s).reduce(function(t,e){return t[e]=!1,t},{});t.scales&&(e=e.concat((t.scales.xAxes||[]).map(function(t){return{options:t,dtype:"category",dposition:"bottom"}}),(t.scales.yAxes||[]).map(function(t){return{options:t,dtype:"linear",dposition:"left"}}))),t.scale&&e.push({options:t.scale,dtype:"radialLinear",isDefault:!0,dposition:"chartArea"}),c.each(e,function(t){var e=t.options,n=e.id,i=c.valueOrDefault(e.type,t.dtype);d(e.position)!==d(t.dposition)&&(e.position=t.dposition),l[n]=!0;var a=null;if(n in s&&s[n].type===i)(a=s[n]).options=e,a.ctx=o.ctx,a.chart=o;else{var r=m.getScaleConstructor(i);if(!r)return;a=new r({id:n,type:i,options:e,ctx:o.ctx,chart:o}),s[a.id]=a}a.mergeTicksOptions(),t.isDefault&&(o.scale=a)}),c.each(l,function(t,e){t||delete s[e]}),o.scales=s,m.addScalesToLayout(this)},buildOrUpdateControllers:function(){var r=this,o=[],s=[];return c.each(r.data.datasets,function(t,e){var n=r.getDatasetMeta(e),i=t.type||r.config.type;if(n.type&&n.type!==i&&(r.destroyDatasetMeta(e),n=r.getDatasetMeta(e)),n.type=i,o.push(n.type),n.controller)n.controller.updateIndex(e),n.controller.linkScales();else{var a=u.controllers[n.type];if(void 0===a)throw new Error('"'+n.type+'" is not a chart type.');n.controller=new a(r,e),s.push(n.controller)}},r),s},resetElements:function(){var n=this;c.each(n.data.datasets,function(t,e){n.getDatasetMeta(e).controller.reset()},n)},reset:function(){this.resetElements(),this.tooltip.initialize()},update:function(t){var e,n,i=this;if(t&&"object"==typeof t||(t={duration:t,lazy:arguments[1]}),n=(e=i).options,c.each(e.scales,function(t){r.removeBox(e,t)}),n=c.configMerge(u.defaults.global,u.defaults[e.config.type],n),e.options=e.config.options=n,e.ensureScalesHaveIDs(),e.buildOrUpdateScales(),e.tooltip._options=n.tooltips,e.tooltip.initialize(),g._invalidate(i),!1!==g.notify(i,"beforeUpdate")){i.tooltip._data=i.data;var a=i.buildOrUpdateControllers();c.each(i.data.datasets,function(t,e){i.getDatasetMeta(e).controller.buildOrUpdateElements()},i),i.updateLayout(),i.options.animation&&i.options.animation.duration&&c.each(a,function(t){t.reset()}),i.updateDatasets(),i.tooltip.initialize(),i.lastActive=[],g.notify(i,"afterUpdate"),i._bufferedRender?i._bufferedRequest={duration:t.duration,easing:t.easing,lazy:t.lazy}:i.render(t)}},updateLayout:function(){!1!==g.notify(this,"beforeLayout")&&(r.update(this,this.width,this.height),g.notify(this,"afterScaleUpdate"),g.notify(this,"afterLayout"))},updateDatasets:function(){if(!1!==g.notify(this,"beforeDatasetsUpdate")){for(var t=0,e=this.data.datasets.length;t=e[t].length&&e[t].push({}),!e[t][a].type||o.type&&o.type!==e[t][a].type?g.merge(e[t][a],[l.getScaleDefaults(r),o]):g.merge(e[t][a],o)}else g._merger(t,e,n,i)}})},g.where=function(t,e){if(g.isArray(t)&&Array.prototype.filter)return t.filter(e);var n=[];return g.each(t,function(t){e(t)&&n.push(t)}),n},g.findIndex=Array.prototype.findIndex?function(t,e,n){return t.findIndex(e,n)}:function(t,e,n){n=void 0===n?t:n;for(var i=0,a=t.length;i=t.length-1?t[0]:t[e+1]:e>=t.length-1?t[t.length-1]:t[e+1]},g.previousItem=function(t,e,n){return n?e<=0?t[t.length-1]:t[e-1]:e<=0?t[0]:t[e-1]},g.niceNum=function(t,e){var n=Math.floor(g.log10(t)),i=t/Math.pow(10,n);return(e?i<1.5?1:i<3?2:i<7?5:10:i<=1?1:i<=2?2:i<=5?5:10)*Math.pow(10,n)},g.requestAnimFrame="undefined"==typeof window?function(t){t()}:window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)},g.getRelativePosition=function(t,e){var n,i,a=t.originalEvent||t,r=t.target||t.srcElement,o=r.getBoundingClientRect(),s=a.touches;i=s&&0n.length){for(var l=0;le&&(e=t.length)}),e},g.color=i?function(t){return t instanceof CanvasGradient&&(t=a.global.defaultColor),i(t)}:function(t){return console.error("Color.js not found!"),t},g.getHoverColor=function(t){return t instanceof CanvasPattern?t:g.color(t).saturate(.5).darken(.1).rgbString()}}},{2:2,26:26,34:34,46:46}],29:[function(t,e,n){"use strict";var i=t(46);function s(t,e){return t.native?{x:t.x,y:t.y}:i.getRelativePosition(t,e)}function l(t,e){var n,i,a,r,o;for(i=0,r=t.data.datasets.length;it.maxHeight){r--;break}r++,l=o*s}t.labelRotation=r},afterCalculateTickRotation:function(){B.callback(this.options.afterCalculateTickRotation,[this])},beforeFit:function(){B.callback(this.options.beforeFit,[this])},fit:function(){var t=this,e=t.minSize={width:0,height:0},n=_(t._ticks),i=t.options,a=i.ticks,r=i.scaleLabel,o=i.gridLines,s=i.display,l=t.isHorizontal(),u=w(a),d=i.gridLines.tickMarkLength;if(e.width=l?t.isFullWidth()?t.maxWidth-t.margins.left-t.margins.right:t.maxWidth:s&&o.drawTicks?d:0,e.height=l?s&&o.drawTicks?d:0:t.maxHeight,r.display&&s){var h=M(r)+B.options.toPadding(r.padding).height;l?e.height+=h:e.width+=h}if(a.display&&s){var c=B.longestText(t.ctx,u.font,n,t.longestTextCache),f=B.numberOfLabelLines(n),g=.5*u.size,m=t.options.ticks.padding;if(l){t.longestLabelWidth=c;var p=B.toRadians(t.labelRotation),v=Math.cos(p),b=Math.sin(p)*c+u.size*f+g*(f-1)+g;e.height=Math.min(t.maxHeight,e.height+b+m),t.ctx.font=u.font;var y=k(t.ctx,n[0],u.font),x=k(t.ctx,n[n.length-1],u.font);0!==t.labelRotation?(t.paddingLeft="bottom"===i.position?v*y+3:v*g+3,t.paddingRight="bottom"===i.position?v*g+3:v*x+3):(t.paddingLeft=y/2+3,t.paddingRight=x/2+3)}else a.mirror?c=0:c+=m+g,e.width=Math.min(t.maxWidth,e.width+c),t.paddingTop=u.size/2,t.paddingBottom=u.size/2}t.handleMargins(),t.width=e.width,t.height=e.height},handleMargins:function(){var t=this;t.margins&&(t.paddingLeft=Math.max(t.paddingLeft-t.margins.left,0),t.paddingTop=Math.max(t.paddingTop-t.margins.top,0),t.paddingRight=Math.max(t.paddingRight-t.margins.right,0),t.paddingBottom=Math.max(t.paddingBottom-t.margins.bottom,0))},afterFit:function(){B.callback(this.options.afterFit,[this])},isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},isFullWidth:function(){return this.options.fullWidth},getRightValue:function(t){if(B.isNullOrUndef(t))return NaN;if("number"==typeof t&&!isFinite(t))return NaN;if(t)if(this.isHorizontal()){if(void 0!==t.x)return this.getRightValue(t.x)}else if(void 0!==t.y)return this.getRightValue(t.y);return t},getLabelForIndex:B.noop,getPixelForValue:B.noop,getValueForPixel:B.noop,getPixelForTick:function(t){var e=this,n=e.options.offset;if(e.isHorizontal()){var i=(e.width-(e.paddingLeft+e.paddingRight))/Math.max(e._ticks.length-(n?0:1),1),a=i*t+e.paddingLeft;n&&(a+=i/2);var r=e.left+Math.round(a);return r+=e.isFullWidth()?e.margins.left:0}var o=e.height-(e.paddingTop+e.paddingBottom);return e.top+t*(o/(e._ticks.length-1))},getPixelForDecimal:function(t){var e=this;if(e.isHorizontal()){var n=(e.width-(e.paddingLeft+e.paddingRight))*t+e.paddingLeft,i=e.left+Math.round(n);return i+=e.isFullWidth()?e.margins.left:0}return e.top+t*e.height},getBasePixel:function(){return this.getPixelForValue(this.getBaseValue())},getBaseValue:function(){var t=this.min,e=this.max;return this.beginAtZero?0:t<0&&e<0?e:0r.width-(r.paddingLeft+r.paddingRight)&&(e=1+Math.floor((h+s.autoSkipPadding)*l/(r.width-(r.paddingLeft+r.paddingRight)))),a&&al.height-e.height&&(h="bottom");var c=(u.left+u.right)/2,f=(u.top+u.bottom)/2;i="center"===h?(n=function(t){return t<=c},function(t){return c=l.width-e.width/2}),a=function(t){return t+e.width+s.caretSize+s.caretPadding>l.width},r=function(t){return t-e.width-s.caretSize-s.caretPadding<0},o=function(t){return t<=f?"top":"bottom"},n(s.x)?(d="left",a(s.x)&&(d="center",h=o(s.y))):i(s.x)&&(d="right",r(s.x)&&(d="center",h=o(s.y)));var g=t._options;return{xAlign:g.xAlign?g.xAlign:d,yAlign:g.yAlign?g.yAlign:h}}(this,T=function(t,e){var n=t._chart.ctx,i=2*e.yPadding,a=0,r=e.body,o=r.reduce(function(t,e){return t+e.before.length+e.lines.length+e.after.length},0);o+=e.beforeBody.length+e.afterBody.length;var s=e.title.length,l=e.footer.length,u=e.titleFontSize,d=e.bodyFontSize,h=e.footerFontSize;i+=s*u,i+=s?(s-1)*e.titleSpacing:0,i+=s?e.titleMarginBottom:0,i+=o*d,i+=o?(o-1)*e.bodySpacing:0,i+=l?e.footerMarginTop:0,i+=l*h,i+=l?(l-1)*e.footerSpacing:0;var c=0,f=function(t){a=Math.max(a,n.measureText(t).width+c)};return n.font=R.fontString(u,e._titleFontStyle,e._titleFontFamily),R.each(e.title,f),n.font=R.fontString(d,e._bodyFontStyle,e._bodyFontFamily),R.each(e.beforeBody.concat(e.afterBody),f),c=e.displayColors?d+2:0,R.each(r,function(t){R.each(t.before,f),R.each(t.lines,f),R.each(t.after,f)}),c=0,n.font=R.fontString(h,e._footerFontStyle,e._footerFontFamily),R.each(e.footer,f),{width:a+=2*e.xPadding,height:i}}(this,M)),i=M,a=T,r=C,o=_._chart,s=i.x,l=i.y,u=i.caretSize,d=i.caretPadding,h=i.cornerRadius,c=r.xAlign,f=r.yAlign,g=u+d,m=h+d,"right"===c?s-=a.width:"center"===c&&((s-=a.width/2)+a.width>o.width&&(s=o.width-a.width),s<0&&(s=0)),"top"===f?l+=g:l-="bottom"===f?a.height+g:a.height/2,"center"===f?"left"===c?s+=g:"right"===c&&(s-=g):"left"===c?s-=m:"right"===c&&(s+=m),P={x:s,y:l}}else M.opacity=0;return M.xAlign=C.xAlign,M.yAlign=C.yAlign,M.x=P.x,M.y=P.y,M.width=T.width,M.height=T.height,M.caretX=O.x,M.caretY=O.y,_._model=M,t&&k.custom&&k.custom.call(_,M),_},drawCaret:function(t,e){var n=this._chart.ctx,i=this._view,a=this.getCaretPosition(t,e,i);n.lineTo(a.x1,a.y1),n.lineTo(a.x2,a.y2),n.lineTo(a.x3,a.y3)},getCaretPosition:function(t,e,n){var i,a,r,o,s,l,u=n.caretSize,d=n.cornerRadius,h=n.xAlign,c=n.yAlign,f=t.x,g=t.y,m=e.width,p=e.height;if("center"===c)s=g+p/2,l="left"===h?(a=(i=f)-u,r=i,o=s+u,s-u):(a=(i=f+m)+u,r=i,o=s-u,s+u);else if(r=(i="left"===h?(a=f+d+u)-u:"right"===h?(a=f+m-d-u)-u:(a=n.caretX)-u,a+u),"top"===c)s=(o=g)-u,l=o;else{s=(o=g+p)+u,l=o;var v=r;r=i,i=v}return{x1:i,x2:a,x3:r,y1:o,y2:s,y3:l}},drawTitle:function(t,e,n,i){var a=e.title;if(a.length){n.textAlign=e._titleAlign,n.textBaseline="top";var r,o,s=e.titleFontSize,l=e.titleSpacing;for(n.fillStyle=c(e.titleFontColor,i),n.font=R.fontString(s,e._titleFontStyle,e._titleFontFamily),r=0,o=a.length;r=n.innerRadius&&r<=n.outerRadius;return l&&u}return!1},getCenterPoint:function(){var t=this._view,e=(t.startAngle+t.endAngle)/2,n=(t.innerRadius+t.outerRadius)/2;return{x:t.x+Math.cos(e)*n,y:t.y+Math.sin(e)*n}},getArea:function(){var t=this._view;return Math.PI*((t.endAngle-t.startAngle)/(2*Math.PI))*(Math.pow(t.outerRadius,2)-Math.pow(t.innerRadius,2))},tooltipPosition:function(){var t=this._view,e=t.startAngle+(t.endAngle-t.startAngle)/2,n=(t.outerRadius-t.innerRadius)/2+t.innerRadius;return{x:t.x+Math.cos(e)*n,y:t.y+Math.sin(e)*n}},draw:function(){var t=this._chart.ctx,e=this._view,n=e.startAngle,i=e.endAngle;t.beginPath(),t.arc(e.x,e.y,e.outerRadius,n,i),t.arc(e.x,e.y,e.innerRadius,i,n,!0),t.closePath(),t.strokeStyle=e.borderColor,t.lineWidth=e.borderWidth,t.fillStyle=e.backgroundColor,t.fill(),t.lineJoin="bevel",e.borderWidth&&t.stroke()}})},{26:26,27:27,46:46}],38:[function(t,e,n){"use strict";var i=t(26),a=t(27),d=t(46),h=i.global;i._set("global",{elements:{line:{tension:.4,backgroundColor:h.defaultColor,borderWidth:3,borderColor:h.defaultColor,borderCapStyle:"butt",borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",capBezierPoints:!0,fill:!0}}}),e.exports=a.extend({draw:function(){var t,e,n,i,a=this._view,r=this._chart.ctx,o=a.spanGaps,s=this._children.slice(),l=h.elements.line,u=-1;for(this._loop&&s.length&&s.push(s[0]),r.save(),r.lineCap=a.borderCapStyle||l.borderCapStyle,r.setLineDash&&r.setLineDash(a.borderDash||l.borderDash),r.lineDashOffset=a.borderDashOffset||l.borderDashOffset,r.lineJoin=a.borderJoinStyle||l.borderJoinStyle,r.lineWidth=a.borderWidth||l.borderWidth,r.strokeStyle=a.borderColor||h.defaultColor,r.beginPath(),u=-1,t=0;t=t.left&&1.01*t.right>=n.x&&n.y>=t.top&&1.01*t.bottom>=n.y)&&(i.strokeStyle=e.borderColor||h,i.lineWidth=d.valueOrDefault(e.borderWidth,u.global.elements.point.borderWidth),i.fillStyle=e.backgroundColor||h,d.canvas.drawPoint(i,a,o,s,l,r))}})},{26:26,27:27,46:46}],40:[function(t,e,n){"use strict";var i=t(26),a=t(27);function l(t){return void 0!==t._view.width}function r(t){var e,n,i,a,r=t._view;if(l(t)){var o=r.width/2;e=r.x-o,n=r.x+o,i=Math.min(r.y,r.base),a=Math.max(r.y,r.base)}else{var s=r.height/2;e=Math.min(r.x,r.base),n=Math.max(r.x,r.base),i=r.y-s,a=r.y+s}return{left:e,top:i,right:n,bottom:a}}i._set("global",{elements:{rectangle:{backgroundColor:i.global.defaultColor,borderColor:i.global.defaultColor,borderSkipped:"bottom",borderWidth:0}}}),e.exports=a.extend({draw:function(){var t,e,n,i,a,r,o,s=this._chart.ctx,l=this._view,u=l.borderWidth;if(o=l.horizontal?(t=l.base,e=l.x,n=l.y-l.height/2,i=l.y+l.height/2,a=t=i.left&&t<=i.right&&e>=i.top&&e<=i.bottom}return n},inLabelRange:function(t,e){if(!this._view)return!1;var n=r(this);return l(this)?t>=n.left&&t<=n.right:e>=n.top&&e<=n.bottom},inXRange:function(t){var e=r(this);return t>=e.left&&t<=e.right},inYRange:function(t){var e=r(this);return t>=e.top&&t<=e.bottom},getCenterPoint:function(){var t,e,n=this._view;return e=l(this)?(t=n.x,(n.y+n.base)/2):(t=(n.x+n.base)/2,n.y),{x:t,y:e}},getArea:function(){var t=this._view;return t.width*Math.abs(t.y-t.base)},tooltipPosition:function(){var t=this._view;return{x:t.x,y:t.y}}})},{26:26,27:27}],41:[function(t,e,n){"use strict";e.exports={},e.exports.Arc=t(37),e.exports.Line=t(38),e.exports.Point=t(39),e.exports.Rectangle=t(40)},{37:37,38:38,39:39,40:40}],42:[function(t,e,n){"use strict";var i=t(43);n=e.exports={clear:function(t){t.ctx.clearRect(0,0,t.width,t.height)},roundedRect:function(t,e,n,i,a,r){if(r){var o=Math.min(r,a/2-1e-7,i/2-1e-7);t.moveTo(e+o,n),t.lineTo(e+i-o,n),t.arcTo(e+i,n,e+i,n+o,o),t.lineTo(e+i,n+a-o),t.arcTo(e+i,n+a,e+i-o,n+a,o),t.lineTo(e+o,n+a),t.arcTo(e,n+a,e,n+a-o,o),t.lineTo(e,n+o),t.arcTo(e,n,e+o,n,o),t.closePath(),t.moveTo(e,n)}else t.rect(e,n,i,a)},drawPoint:function(t,e,n,i,a,r){var o,s,l,u,d,h;if(r=r||0,!e||"object"!=typeof e||"[object HTMLImageElement]"!==(o=e.toString())&&"[object HTMLCanvasElement]"!==o){if(!(isNaN(n)||n<=0)){switch(t.save(),t.translate(i,a),t.rotate(r*Math.PI/180),t.beginPath(),e){default:t.arc(0,0,n,0,2*Math.PI),t.closePath();break;case"triangle":d=(s=3*n/Math.sqrt(3))*Math.sqrt(3)/2,t.moveTo(-s/2,d/3),t.lineTo(s/2,d/3),t.lineTo(0,-2*d/3),t.closePath();break;case"rect":h=1/Math.SQRT2*n,t.rect(-h,-h,2*h,2*h);break;case"rectRounded":var c=n/Math.SQRT2,f=-c,g=-c,m=Math.SQRT2*n;this.roundedRect(t,f,g,m,m,.425*n);break;case"rectRot":h=1/Math.SQRT2*n,t.moveTo(-h,0),t.lineTo(0,h),t.lineTo(h,0),t.lineTo(0,-h),t.closePath();break;case"cross":t.moveTo(0,n),t.lineTo(0,-n),t.moveTo(-n,0),t.lineTo(n,0);break;case"crossRot":l=Math.cos(Math.PI/4)*n,u=Math.sin(Math.PI/4)*n,t.moveTo(-l,-u),t.lineTo(l,u),t.moveTo(-l,u),t.lineTo(l,-u);break;case"star":t.moveTo(0,n),t.lineTo(0,-n),t.moveTo(-n,0),t.lineTo(n,0),l=Math.cos(Math.PI/4)*n,u=Math.sin(Math.PI/4)*n,t.moveTo(-l,-u),t.lineTo(l,u),t.moveTo(-l,u),t.lineTo(l,-u);break;case"line":t.moveTo(-n,0),t.lineTo(n,0);break;case"dash":t.moveTo(0,0),t.lineTo(n,0)}t.fill(),t.stroke(),t.restore()}}else t.drawImage(e,i-e.width/2,a-e.height/2,e.width,e.height)},clipArea:function(t,e){t.save(),t.beginPath(),t.rect(e.left,e.top,e.right-e.left,e.bottom-e.top),t.clip()},unclipArea:function(t){t.restore()},lineTo:function(t,e,n,i){if(n.steppedLine)return"after"===n.steppedLine&&!i||"after"!==n.steppedLine&&i?t.lineTo(e.x,n.y):t.lineTo(n.x,e.y),void t.lineTo(n.x,n.y);n.tension?t.bezierCurveTo(i?e.controlPointPreviousX:e.controlPointNextX,i?e.controlPointPreviousY:e.controlPointNextY,i?n.controlPointNextX:n.controlPointPreviousX,i?n.controlPointNextY:n.controlPointPreviousY,n.x,n.y):t.lineTo(n.x,n.y)}};i.clear=n.clear,i.drawRoundedRectangle=function(t){t.beginPath(),n.roundedRect.apply(n,arguments)}},{43:43}],43:[function(t,e,n){"use strict";var i,d={noop:function(){},uid:(i=0,function(){return i++}),isNullOrUndef:function(t){return null==t},isArray:Array.isArray?Array.isArray:function(t){return"[object Array]"===Object.prototype.toString.call(t)},isObject:function(t){return null!==t&&"[object Object]"===Object.prototype.toString.call(t)},valueOrDefault:function(t,e){return void 0===t?e:t},valueAtIndexOrDefault:function(t,e,n){return d.valueOrDefault(d.isArray(t)?t[e]:t,n)},callback:function(t,e,n){if(t&&"function"==typeof t.call)return t.apply(n,e)},each:function(t,e,n,i){var a,r,o;if(d.isArray(t))if(r=t.length,i)for(a=r-1;0<=a;a--)e.call(n,t[a],a);else for(a=0;a
    ';var a=e.childNodes[0],r=e.childNodes[1];e._reset=function(){a.scrollLeft=1e6,a.scrollTop=1e6,r.scrollLeft=1e6,r.scrollTop=1e6};var o=function(){e._reset(),t()};return y(a,"scroll",o.bind(a,"expand")),y(r,"scroll",o.bind(r,"shrink")),e}((r=!(i=function(){if(h.resizer)return t(x("resize",n))}),o=[],function(){o=Array.prototype.slice.call(arguments),a=a||this,r||(r=!0,f.requestAnimFrame.call(window,function(){r=!1,i.apply(a,o)}))}));l=function(){if(h.resizer){var t=e.parentNode;t&&t!==c.parentNode&&t.insertBefore(c,t.firstChild),c._reset()}},u=(s=e)[g]||(s[g]={}),d=u.renderProxy=function(t){t.animationName===v&&l()},f.each(b,function(t){y(s,t,d)}),u.reflow=!!s.offsetParent,s.classList.add(p)}function r(t){var e,n,i,a=t[g]||{},r=a.resizer;delete a.resizer,n=(e=t)[g]||{},(i=n.renderProxy)&&(f.each(b,function(t){o(e,t,i)}),delete n.renderProxy),e.classList.remove(p),r&&r.parentNode&&r.parentNode.removeChild(r)}e.exports={_enabled:"undefined"!=typeof window&&"undefined"!=typeof document,initialize:function(){var t,e,n,i="from{opacity:0.99}to{opacity:1}";e="@-webkit-keyframes "+v+"{"+i+"}@keyframes "+v+"{"+i+"}."+p+"{-webkit-animation:"+v+" 0.001s;animation:"+v+" 0.001s;}",n=(t=this)._style||document.createElement("style"),t._style||(e="/* Chart.js */\n"+e,(t._style=n).setAttribute("type","text/css"),document.getElementsByTagName("head")[0].appendChild(n)),n.appendChild(document.createTextNode(e))},acquireContext:function(t,e){"string"==typeof t?t=document.getElementById(t):t.length&&(t=t[0]),t&&t.canvas&&(t=t.canvas);var n=t&&t.getContext&&t.getContext("2d");return n&&n.canvas===t?(function(t,e){var n=t.style,i=t.getAttribute("height"),a=t.getAttribute("width");if(t[g]={initial:{height:i,width:a,style:{display:n.display,height:n.height,width:n.width}}},n.display=n.display||"block",null===a||""===a){var r=l(t,"width");void 0!==r&&(t.width=r)}if(null===i||""===i)if(""===t.style.height)t.height=t.width/(e.options.aspectRatio||2);else{var o=l(t,"height");void 0!==r&&(t.height=o)}}(t,e),n):null},releaseContext:function(t){var n=t.canvas;if(n[g]){var i=n[g].initial;["height","width"].forEach(function(t){var e=i[t];f.isNullOrUndef(e)?n.removeAttribute(t):n.setAttribute(t,e)}),f.each(i.style||{},function(t,e){n.style[e]=t}),n.width=n.width,delete n[g]}},addEventListener:function(r,t,o){var e=r.canvas;if("resize"!==t){var n=o[g]||(o[g]={});y(e,t,(n.proxies||(n.proxies={}))[r.id+"_"+t]=function(t){var e,n,i,a;o((n=r,i=s[(e=t).type]||e.type,a=f.getRelativePosition(e,n),x(i,n,a.x,a.y,e)))})}else a(e,o,r)},removeEventListener:function(t,e,n){var i=t.canvas;if("resize"!==e){var a=((n[g]||{}).proxies||{})[t.id+"_"+e];a&&o(i,e,a)}else r(i)}},f.addEvent=y,f.removeEvent=o},{46:46}],49:[function(t,e,n){"use strict";var i=t(46),a=t(47),r=t(48),o=r._enabled?r:a;e.exports=i.extend({initialize:function(){},acquireContext:function(){},releaseContext:function(){},addEventListener:function(){},removeEventListener:function(){}},o)},{46:46,47:47,48:48}],50:[function(t,e,n){"use strict";e.exports={},e.exports.filler=t(51),e.exports.legend=t(52),e.exports.title=t(53)},{51:51,52:52,53:53}],51:[function(t,e,n){"use strict";var u=t(26),c=t(41),d=t(46);u._set("global",{plugins:{filler:{propagate:!0}}});var f={dataset:function(t){var e=t.fill,n=t.chart,i=n.getDatasetMeta(e),a=i&&n.isDatasetVisible(e)&&i.dataset._children||[],r=a.length||0;return r?function(t,e){return e');for(var n=0;n'),t.data.datasets[n].label&&e.push(t.data.datasets[n].label),e.push("");return e.push(""),e.join("")}});var o=i.extend({initialize:function(t){C.extend(this,t),this.legendHitBoxes=[],this.doughnutMode=!1},beforeUpdate:r,update:function(t,e,n){var i=this;return i.beforeUpdate(),i.maxWidth=t,i.maxHeight=e,i.margins=n,i.beforeSetDimensions(),i.setDimensions(),i.afterSetDimensions(),i.beforeBuildLabels(),i.buildLabels(),i.afterBuildLabels(),i.beforeFit(),i.fit(),i.afterFit(),i.afterUpdate(),i.minSize},afterUpdate:r,beforeSetDimensions:r,setDimensions:function(){var t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height),t.paddingLeft=0,t.paddingTop=0,t.paddingRight=0,t.paddingBottom=0,t.minSize={width:0,height:0}},afterSetDimensions:r,beforeBuildLabels:r,buildLabels:function(){var e=this,n=e.options.labels||{},t=C.callback(n.generateLabels,[e.chart],e)||[];n.filter&&(t=t.filter(function(t){return n.filter(t,e.chart.data)})),e.options.reverse&&t.reverse(),e.legendItems=t},afterBuildLabels:r,beforeFit:r,fit:function(){var i=this,t=i.options,a=t.labels,e=t.display,r=i.ctx,n=D.global,o=C.valueOrDefault,s=o(a.fontSize,n.defaultFontSize),l=o(a.fontStyle,n.defaultFontStyle),u=o(a.fontFamily,n.defaultFontFamily),d=C.fontString(s,l,u),h=i.legendHitBoxes=[],c=i.minSize,f=i.isHorizontal();if(c.height=f?(c.width=i.maxWidth,e?10:0):(c.width=e?10:0,i.maxHeight),e)if(r.font=d,f){var g=i.lineWidths=[0],m=i.legendItems.length?s+a.padding:0;r.textAlign="left",r.textBaseline="top",C.each(i.legendItems,function(t,e){var n=P(a,s)+s/2+r.measureText(t.text).width;g[g.length-1]+n+a.padding>=i.width&&(m+=s+a.padding,g[g.length]=i.left),h[e]={left:0,top:0,width:n,height:s},g[g.length-1]+=n+a.padding}),c.height+=m}else{var p=a.padding,v=i.columnWidths=[],b=a.padding,y=0,x=0,_=s+p;C.each(i.legendItems,function(t,e){var n=P(a,s)+s/2+r.measureText(t.text).width;x+_>c.height&&(b+=y+a.padding,v.push(y),x=y=0),y=Math.max(y,n),x+=_,h[e]={left:0,top:0,width:n,height:s}}),b+=y,v.push(y),c.width+=b}i.width=c.width,i.height=c.height},afterFit:r,isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},draw:function(){var h=this,c=h.options,f=c.labels,g=D.global,m=g.elements.line,p=h.width,v=h.lineWidths;if(c.display){var b,y=h.ctx,x=C.valueOrDefault,t=x(f.fontColor,g.defaultFontColor),_=x(f.fontSize,g.defaultFontSize),e=x(f.fontStyle,g.defaultFontStyle),n=x(f.fontFamily,g.defaultFontFamily),i=C.fontString(_,e,n);y.textAlign="left",y.textBaseline="middle",y.lineWidth=.5,y.strokeStyle=t,y.fillStyle=t,y.font=i;var k=P(f,_),w=h.legendHitBoxes,M=h.isHorizontal();b=M?{x:h.left+(p-v[0])/2,y:h.top+f.padding,line:0}:{x:h.left+f.padding,y:h.top+f.padding,line:0};var S=_+f.padding;C.each(h.legendItems,function(t,e){var n,i,a,r,o,s=y.measureText(t.text).width,l=k+_/2+s,u=b.x,d=b.y;M?p<=u+l&&(d=b.y+=S,b.line++,u=b.x=h.left+(p-v[b.line])/2):d+S>h.bottom&&(u=b.x=u+h.columnWidths[b.line]+f.padding,d=b.y=h.top+f.padding,b.line++),function(t,e,n){if(!(isNaN(k)||k<=0)){y.save(),y.fillStyle=x(n.fillStyle,g.defaultColor),y.lineCap=x(n.lineCap,m.borderCapStyle),y.lineDashOffset=x(n.lineDashOffset,m.borderDashOffset),y.lineJoin=x(n.lineJoin,m.borderJoinStyle),y.lineWidth=x(n.lineWidth,m.borderWidth),y.strokeStyle=x(n.strokeStyle,g.defaultColor);var i=0===x(n.lineWidth,m.borderWidth);if(y.setLineDash&&y.setLineDash(x(n.lineDash,m.borderDash)),c.labels&&c.labels.usePointStyle){var a=_*Math.SQRT2/2,r=a/Math.SQRT2,o=t+r,s=e+r;C.canvas.drawPoint(y,n.pointStyle,a,o,s)}else i||y.strokeRect(t,e,k,_),y.fillRect(t,e,k,_);y.restore()}}(u,d,t),w[e].left=u,w[e].top=d,n=t,i=s,r=k+(a=_/2)+u,o=d+a,y.fillText(n.text,r,o),n.hidden&&(y.beginPath(),y.lineWidth=2,y.moveTo(r,o),y.lineTo(r+i,o),y.stroke()),M?b.x+=l+f.padding:b.y+=S})}},handleEvent:function(t){var e=this,n=e.options,i="mouseup"===t.type?"click":t.type,a=!1;if("mousemove"===i){if(!n.onHover)return}else{if("click"!==i)return;if(!n.onClick)return}var r=t.x,o=t.y;if(r>=e.left&&r<=e.right&&o>=e.top&&o<=e.bottom)for(var s=e.legendHitBoxes,l=0;l=u.left&&r<=u.left+u.width&&o>=u.top&&o<=u.top+u.height){if("click"===i){n.onClick.call(e,t.native,e.legendItems[l]),a=!0;break}if("mousemove"===i){n.onHover.call(e,t.native,e.legendItems[l]),a=!0;break}}}return a}});function s(t,e){var n=new o({ctx:t.ctx,options:e,chart:t});a.configure(t,n,e),a.addBox(t,n),t.legend=n}e.exports={id:"legend",_element:o,beforeInit:function(t){var e=t.options.legend;e&&s(t,e)},beforeUpdate:function(t){var e=t.options.legend,n=t.legend;e?(C.mergeIf(e,D.global.legend),n?(a.configure(t,n,e),n.options=e):s(t,e)):n&&(a.removeBox(t,n),delete t.legend)},afterEvent:function(t,e){var n=t.legend;n&&n.handleEvent(e)}}},{26:26,27:27,31:31,46:46}],53:[function(t,e,n){"use strict";var _=t(26),i=t(27),k=t(46),a=t(31),r=k.noop;_._set("global",{title:{display:!1,fontStyle:"bold",fullWidth:!0,lineHeight:1.2,padding:10,position:"top",text:"",weight:2e3}});var o=i.extend({initialize:function(t){k.extend(this,t),this.legendHitBoxes=[]},beforeUpdate:r,update:function(t,e,n){var i=this;return i.beforeUpdate(),i.maxWidth=t,i.maxHeight=e,i.margins=n,i.beforeSetDimensions(),i.setDimensions(),i.afterSetDimensions(),i.beforeBuildLabels(),i.buildLabels(),i.afterBuildLabels(),i.beforeFit(),i.fit(),i.afterFit(),i.afterUpdate(),i.minSize},afterUpdate:r,beforeSetDimensions:r,setDimensions:function(){var t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height),t.paddingLeft=0,t.paddingTop=0,t.paddingRight=0,t.paddingBottom=0,t.minSize={width:0,height:0}},afterSetDimensions:r,beforeBuildLabels:r,buildLabels:r,afterBuildLabels:r,beforeFit:r,fit:function(){var t=k.valueOrDefault,e=this.options,n=e.display,i=t(e.fontSize,_.global.defaultFontSize),a=this.minSize,r=k.isArray(e.text)?e.text.length:1,o=k.options.toLineHeight(e.lineHeight,i),s=n?r*o+2*e.padding:0;this.isHorizontal()?(a.width=this.maxWidth,a.height=s):(a.width=s,a.height=this.maxHeight),this.width=a.width,this.height=a.height},afterFit:r,isHorizontal:function(){var t=this.options.position;return"top"===t||"bottom"===t},draw:function(){var t=this.ctx,e=k.valueOrDefault,n=this.options,i=_.global;if(n.display){var a,r,o,s=e(n.fontSize,i.defaultFontSize),l=e(n.fontStyle,i.defaultFontStyle),u=e(n.fontFamily,i.defaultFontFamily),d=k.fontString(s,l,u),h=k.options.toLineHeight(n.lineHeight,s),c=h/2+n.padding,f=0,g=this.top,m=this.left,p=this.bottom,v=this.right;t.fillStyle=e(n.fontColor,i.defaultFontColor),t.font=d,this.isHorizontal()?(r=m+(v-m)/2,o=g+c,a=v-m):(r="left"===n.position?m+c:v-c,o=g+(p-g)/2,a=p-g,f=Math.PI*("left"===n.position?-.5:.5)),t.save(),t.translate(r,o),t.rotate(f),t.textAlign="center",t.textBaseline="middle";var b=n.text;if(k.isArray(b))for(var y=0,x=0;xo.max&&(o.max=n))})});o.min=isFinite(o.min)&&!isNaN(o.min)?o.min:0,o.max=isFinite(o.max)&&!isNaN(o.max)?o.max:1,this.handleTickRangeOptions()},getTickLimit:function(){var t,e=this.options.ticks;if(this.isHorizontal())t=Math.min(e.maxTicksLimit?e.maxTicksLimit:11,Math.ceil(this.width/50));else{var n=h.valueOrDefault(e.fontSize,i.global.defaultFontSize);t=Math.min(e.maxTicksLimit?e.maxTicksLimit:11,Math.ceil(this.height/(2*n)))}return t},handleDirectionalChanges:function(){this.isHorizontal()||this.ticks.reverse()},getLabelForIndex:function(t,e){return+this.getRightValue(this.chart.data.datasets[e].data[t])},getPixelForValue:function(t){var e=this.start,n=+this.getRightValue(t),i=this.end-e;return this.isHorizontal()?this.left+this.width/i*(n-e):this.bottom-this.height/i*(n-e)},getValueForPixel:function(t){var e=this.isHorizontal(),n=e?this.width:this.height,i=(e?t-this.left:this.bottom-t)/n;return this.start+(this.end-this.start)*i},getPixelForTick:function(t){return this.getPixelForValue(this.ticksAsNumbers[t])}});a.registerScaleType("linear",n,e)}},{26:26,34:34,35:35,46:46}],56:[function(t,e,n){"use strict";var h=t(46),i=t(33);e.exports=function(t){var e=h.noop;t.LinearScaleBase=i.extend({getRightValue:function(t){return"string"==typeof t?+t:i.prototype.getRightValue.call(this,t)},handleTickRangeOptions:function(){var t=this,e=t.options.ticks;if(e.beginAtZero){var n=h.sign(t.min),i=h.sign(t.max);n<0&&i<0?t.max=0:0=t.max&&(a?t.max=t.min+1:t.min=t.max-1),t.min===t.max&&(t.max++,e.beginAtZero||t.min--)},getTickLimit:e,handleDirectionalChanges:e,buildTicks:function(){var t=this,e=t.options.ticks,n=t.getTickLimit(),i={maxTicks:n=Math.max(2,n),min:e.min,max:e.max,precision:e.precision,stepSize:h.valueOrDefault(e.fixedStepSize,e.stepSize)},a=t.ticks=function(t,e){var n,i,a,r=[];if(t.stepSize&&0o.max&&(o.max=n),0!==n&&(null===o.minNotZero||no.r&&(o.r=g.end,s.r=c),m.starto.b&&(o.b=m.end,s.b=c)}t.setReductions(r,o,s)}(this):(t=this,e=Math.min(t.height/2,t.width/2),t.drawingArea=Math.round(e),t.setCenterPoint(0,0,0,0))},setReductions:function(t,e,n){var i=e.l/Math.sin(n.l),a=Math.max(e.r-this.width,0)/Math.sin(n.r),r=-e.t/Math.cos(n.t),o=-Math.max(e.b-this.height,0)/Math.cos(n.b);i=s(i),a=s(a),r=s(r),o=s(o),this.drawingArea=Math.min(Math.round(t-(i+a)/2),Math.round(t-(r+o)/2)),this.setCenterPoint(i,a,r,o)},setCenterPoint:function(t,e,n,i){var a=this,r=a.width-e-a.drawingArea,o=t+a.drawingArea,s=n+a.drawingArea,l=a.height-i-a.drawingArea;a.xCenter=Math.round((o+r)/2+a.left),a.yCenter=Math.round((s+l)/2+a.top)},getIndexAngle:function(t){return t*(2*Math.PI/b(this))+(this.chart.options&&this.chart.options.startAngle?this.chart.options.startAngle:0)*Math.PI*2/360},getDistanceFromCenterForValue:function(t){if(null===t)return 0;var e=this.drawingArea/(this.max-this.min);return this.options.ticks.reverse?(this.max-t)*e:(t-this.min)*e},getPointPosition:function(t,e){var n=this.getIndexAngle(t)-Math.PI/2;return{x:Math.round(Math.cos(n)*e)+this.xCenter,y:Math.round(Math.sin(n)*e)+this.yCenter}},getPointPositionForValue:function(t,e){return this.getPointPosition(t,this.getDistanceFromCenterForValue(e))},getBasePosition:function(){var t=this.min,e=this.max;return this.getPointPositionForValue(0,this.beginAtZero?0:t<0&&e<0?e:0>1)-1]||null,r=t[i],!a)return{lo:null,hi:r};if(r[e]n))return{lo:a,hi:r};s=i-1}}return{lo:r,hi:null}}(t,e,n),r=a.lo?a.hi?a.lo:t[t.length-2]:t[0],o=a.lo?a.hi?a.hi:t[t.length-1]:t[1],s=o[e]-r[e],l=s?(n-r[e])/s:0,u=(o[i]-r[i])*l;return r[i]+u}function M(t,e){var n=e.parser,i=e.parser||e.format;return"function"==typeof n?n(t):"string"==typeof t&&"string"==typeof i?y(t,i):(t instanceof y||(t=y(t)),t.isValid()?t:"function"==typeof i?i(t):t)}function S(t,e){if(p.isNullOrUndef(t))return null;var n=e.options.time,i=M(e.getRightValue(t),n);return i.isValid()?(n.round&&i.startOf(n.round),i.valueOf()):null}function D(t){for(var e=_.indexOf(t)+1,n=_.length;e=_.indexOf(e);a--)if(r=_[a],x[r].common&&o.as(r)>=t.length)return r;return _[e?_.indexOf(e):0]}(b,p.minUnit,c.min,c.max),c._majorUnit=D(c._unit),c._table=function(t,e,n,i){if("linear"===i||!t.length)return[{time:e,pos:0},{time:n,pos:1}];var a,r,o,s,l,u=[],d=[e];for(a=0,r=t.length;aa;a++)for(s in o[a])n=o[a][s],o[a].hasOwnProperty(s)&&void 0!==n&&(e[s]=t.isPlainObject(n)?t.isPlainObject(e[s])?t.widget.extend({},e[s],n):t.widget.extend({},n):n);return e},t.widget.bridge=function(e,s){var n=s.prototype.widgetFullName||e;t.fn[e]=function(o){var a="string"==typeof o,r=i.call(arguments,1),l=this;return a?this.length||"instance"!==o?this.each(function(){var i,s=t.data(this,n);return"instance"===o?(l=s,!1):s?t.isFunction(s[o])&&"_"!==o.charAt(0)?(i=s[o].apply(s,r),i!==s&&void 0!==i?(l=i&&i.jquery?l.pushStack(i.get()):i,!1):void 0):t.error("no such method '"+o+"' for "+e+" widget instance"):t.error("cannot call methods on "+e+" prior to initialization; "+"attempted to call method '"+o+"'")}):l=void 0:(r.length&&(o=t.widget.extend.apply(null,[o].concat(r))),this.each(function(){var e=t.data(this,n);e?(e.option(o||{}),e._init&&e._init()):t.data(this,n,new s(o,this))})),l}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"
    ",options:{classes:{},disabled:!1,create:null},_createWidget:function(i,s){s=t(s||this.defaultElement||this)[0],this.element=t(s),this.uuid=e++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=t(),this.hoverable=t(),this.focusable=t(),this.classesElementLookup={},s!==this&&(t.data(s,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===s&&this.destroy()}}),this.document=t(s.style?s.ownerDocument:s.document||s),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this.options=t.widget.extend({},this.options,this._getCreateOptions(),i),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){var e=this;this._destroy(),t.each(this.classesElementLookup,function(t,i){e._removeClass(i,t)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:t.noop,widget:function(){return this.element},option:function(e,i){var s,n,o,a=e;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof e)if(a={},s=e.split("."),e=s.shift(),s.length){for(n=a[e]=t.widget.extend({},this.options[e]),o=0;s.length-1>o;o++)n[s[o]]=n[s[o]]||{},n=n[s[o]];if(e=s.pop(),1===arguments.length)return void 0===n[e]?null:n[e];n[e]=i}else{if(1===arguments.length)return void 0===this.options[e]?null:this.options[e];a[e]=i}return this._setOptions(a),this},_setOptions:function(t){var e;for(e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return"classes"===t&&this._setOptionClasses(e),this.options[t]=e,"disabled"===t&&this._setOptionDisabled(e),this},_setOptionClasses:function(e){var i,s,n;for(i in e)n=this.classesElementLookup[i],e[i]!==this.options.classes[i]&&n&&n.length&&(s=t(n.get()),this._removeClass(n,i),s.addClass(this._classes({element:s,keys:i,classes:e,add:!0})))},_setOptionDisabled:function(t){this._toggleClass(this.widget(),this.widgetFullName+"-disabled",null,!!t),t&&(this._removeClass(this.hoverable,null,"ui-state-hover"),this._removeClass(this.focusable,null,"ui-state-focus"))},enable:function(){return this._setOptions({disabled:!1})},disable:function(){return this._setOptions({disabled:!0})},_classes:function(e){function i(i,o){var a,r;for(r=0;i.length>r;r++)a=n.classesElementLookup[i[r]]||t(),a=e.add?t(t.unique(a.get().concat(e.element.get()))):t(a.not(e.element).get()),n.classesElementLookup[i[r]]=a,s.push(i[r]),o&&e.classes[i[r]]&&s.push(e.classes[i[r]])}var s=[],n=this;return e=t.extend({element:this.element,classes:this.options.classes||{}},e),this._on(e.element,{remove:"_untrackClassesElement"}),e.keys&&i(e.keys.match(/\S+/g)||[],!0),e.extra&&i(e.extra.match(/\S+/g)||[]),s.join(" ")},_untrackClassesElement:function(e){var i=this;t.each(i.classesElementLookup,function(s,n){-1!==t.inArray(e.target,n)&&(i.classesElementLookup[s]=t(n.not(e.target).get()))})},_removeClass:function(t,e,i){return this._toggleClass(t,e,i,!1)},_addClass:function(t,e,i){return this._toggleClass(t,e,i,!0)},_toggleClass:function(t,e,i,s){s="boolean"==typeof s?s:i;var n="string"==typeof t||null===t,o={extra:n?e:i,keys:n?t:e,element:n?this.element:t,add:s};return o.element.toggleClass(this._classes(o),s),this},_on:function(e,i,s){var n,o=this;"boolean"!=typeof e&&(s=i,i=e,e=!1),s?(i=n=t(i),this.bindings=this.bindings.add(i)):(s=i,i=this.element,n=this.widget()),t.each(s,function(s,a){function r(){return e||o.options.disabled!==!0&&!t(this).hasClass("ui-state-disabled")?("string"==typeof a?o[a]:a).apply(o,arguments):void 0}"string"!=typeof a&&(r.guid=a.guid=a.guid||r.guid||t.guid++);var l=s.match(/^([\w:-]*)\s*(.*)$/),h=l[1]+o.eventNamespace,c=l[2];c?n.on(h,c,r):i.on(h,r)})},_off:function(e,i){i=(i||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,e.off(i).off(i),this.bindings=t(this.bindings.not(e).get()),this.focusable=t(this.focusable.not(e).get()),this.hoverable=t(this.hoverable.not(e).get())},_delay:function(t,e){function i(){return("string"==typeof t?s[t]:t).apply(s,arguments)}var s=this;return setTimeout(i,e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){this._addClass(t(e.currentTarget),null,"ui-state-hover")},mouseleave:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-hover")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){this._addClass(t(e.currentTarget),null,"ui-state-focus")},focusout:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-focus")}})},_trigger:function(e,i,s){var n,o,a=this.options[e];if(s=s||{},i=t.Event(i),i.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],o=i.originalEvent)for(n in o)n in i||(i[n]=o[n]);return this.element.trigger(i,s),!(t.isFunction(a)&&a.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},t.each({show:"fadeIn",hide:"fadeOut"},function(e,i){t.Widget.prototype["_"+e]=function(s,n,o){"string"==typeof n&&(n={effect:n});var a,r=n?n===!0||"number"==typeof n?i:n.effect||i:e;n=n||{},"number"==typeof n&&(n={duration:n}),a=!t.isEmptyObject(n),n.complete=o,n.delay&&s.delay(n.delay),a&&t.effects&&t.effects.effect[r]?s[e](n):r!==e&&s[r]?s[r](n.duration,n.easing,o):s.queue(function(i){t(this)[e](),o&&o.call(s[0]),i()})}}),t.widget,t.extend(t.expr[":"],{data:t.expr.createPseudo?t.expr.createPseudo(function(e){return function(i){return!!t.data(i,e)}}):function(e,i,s){return!!t.data(e,s[3])}}),t.fn.scrollParent=function(e){var i=this.css("position"),s="absolute"===i,n=e?/(auto|scroll|hidden)/:/(auto|scroll)/,o=this.parents().filter(function(){var e=t(this);return s&&"static"===e.css("position")?!1:n.test(e.css("overflow")+e.css("overflow-y")+e.css("overflow-x"))}).eq(0);return"fixed"!==i&&o.length?o:t(this[0].ownerDocument||document)},t.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase());var s=!1;t(document).on("mouseup",function(){s=!1}),t.widget("ui.mouse",{version:"1.12.1",options:{cancel:"input, textarea, button, select, option",distance:1,delay:0},_mouseInit:function(){var e=this;this.element.on("mousedown."+this.widgetName,function(t){return e._mouseDown(t)}).on("click."+this.widgetName,function(i){return!0===t.data(i.target,e.widgetName+".preventClickEvent")?(t.removeData(i.target,e.widgetName+".preventClickEvent"),i.stopImmediatePropagation(),!1):void 0}),this.started=!1},_mouseDestroy:function(){this.element.off("."+this.widgetName),this._mouseMoveDelegate&&this.document.off("mousemove."+this.widgetName,this._mouseMoveDelegate).off("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(e){if(!s){this._mouseMoved=!1,this._mouseStarted&&this._mouseUp(e),this._mouseDownEvent=e;var i=this,n=1===e.which,o="string"==typeof this.options.cancel&&e.target.nodeName?t(e.target).closest(this.options.cancel).length:!1;return n&&!o&&this._mouseCapture(e)?(this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){i.mouseDelayMet=!0},this.options.delay)),this._mouseDistanceMet(e)&&this._mouseDelayMet(e)&&(this._mouseStarted=this._mouseStart(e)!==!1,!this._mouseStarted)?(e.preventDefault(),!0):(!0===t.data(e.target,this.widgetName+".preventClickEvent")&&t.removeData(e.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(t){return i._mouseMove(t)},this._mouseUpDelegate=function(t){return i._mouseUp(t)},this.document.on("mousemove."+this.widgetName,this._mouseMoveDelegate).on("mouseup."+this.widgetName,this._mouseUpDelegate),e.preventDefault(),s=!0,!0)):!0}},_mouseMove:function(e){if(this._mouseMoved){if(t.ui.ie&&(!document.documentMode||9>document.documentMode)&&!e.button)return this._mouseUp(e);if(!e.which)if(e.originalEvent.altKey||e.originalEvent.ctrlKey||e.originalEvent.metaKey||e.originalEvent.shiftKey)this.ignoreMissingWhich=!0;else if(!this.ignoreMissingWhich)return this._mouseUp(e)}return(e.which||e.button)&&(this._mouseMoved=!0),this._mouseStarted?(this._mouseDrag(e),e.preventDefault()):(this._mouseDistanceMet(e)&&this._mouseDelayMet(e)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,e)!==!1,this._mouseStarted?this._mouseDrag(e):this._mouseUp(e)),!this._mouseStarted)},_mouseUp:function(e){this.document.off("mousemove."+this.widgetName,this._mouseMoveDelegate).off("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,e.target===this._mouseDownEvent.target&&t.data(e.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(e)),this._mouseDelayTimer&&(clearTimeout(this._mouseDelayTimer),delete this._mouseDelayTimer),this.ignoreMissingWhich=!1,s=!1,e.preventDefault()},_mouseDistanceMet:function(t){return Math.max(Math.abs(this._mouseDownEvent.pageX-t.pageX),Math.abs(this._mouseDownEvent.pageY-t.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}}),t.widget("ui.sortable",t.ui.mouse,{version:"1.12.1",widgetEventPrefix:"sort",ready:!1,options:{appendTo:"parent",axis:!1,connectWith:!1,containment:!1,cursor:"auto",cursorAt:!1,dropOnEmpty:!0,forcePlaceholderSize:!1,forceHelperSize:!1,grid:!1,handle:!1,helper:"original",items:"> *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3,activate:null,beforeStop:null,change:null,deactivate:null,out:null,over:null,receive:null,remove:null,sort:null,start:null,stop:null,update:null},_isOverAxis:function(t,e,i){return t>=e&&e+i>t},_isFloating:function(t){return/left|right/.test(t.css("float"))||/inline|table-cell/.test(t.css("display"))},_create:function(){this.containerCache={},this._addClass("ui-sortable"),this.refresh(),this.offset=this.element.offset(),this._mouseInit(),this._setHandleClassName(),this.ready=!0},_setOption:function(t,e){this._super(t,e),"handle"===t&&this._setHandleClassName()},_setHandleClassName:function(){var e=this;this._removeClass(this.element.find(".ui-sortable-handle"),"ui-sortable-handle"),t.each(this.items,function(){e._addClass(this.instance.options.handle?this.item.find(this.instance.options.handle):this.item,"ui-sortable-handle")})},_destroy:function(){this._mouseDestroy();for(var t=this.items.length-1;t>=0;t--)this.items[t].item.removeData(this.widgetName+"-item");return this},_mouseCapture:function(e,i){var s=null,n=!1,o=this;return this.reverting?!1:this.options.disabled||"static"===this.options.type?!1:(this._refreshItems(e),t(e.target).parents().each(function(){return t.data(this,o.widgetName+"-item")===o?(s=t(this),!1):void 0}),t.data(e.target,o.widgetName+"-item")===o&&(s=t(e.target)),s?!this.options.handle||i||(t(this.options.handle,s).find("*").addBack().each(function(){this===e.target&&(n=!0)}),n)?(this.currentItem=s,this._removeCurrentsFromItems(),!0):!1:!1)},_mouseStart:function(e,i,s){var n,o,a=this.options;if(this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(e),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},t.extend(this.offset,{click:{left:e.pageX-this.offset.left,top:e.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.helper.css("position","absolute"),this.cssPosition=this.helper.css("position"),this.originalPosition=this._generatePosition(e),this.originalPageX=e.pageX,this.originalPageY=e.pageY,a.cursorAt&&this._adjustOffsetFromHelper(a.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!==this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),a.containment&&this._setContainment(),a.cursor&&"auto"!==a.cursor&&(o=this.document.find("body"),this.storedCursor=o.css("cursor"),o.css("cursor",a.cursor),this.storedStylesheet=t("").appendTo(o)),a.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",a.opacity)),a.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",a.zIndex)),this.scrollParent[0]!==this.document[0]&&"HTML"!==this.scrollParent[0].tagName&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",e,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions(),!s)for(n=this.containers.length-1;n>=0;n--)this.containers[n]._trigger("activate",e,this._uiHash(this));return t.ui.ddmanager&&(t.ui.ddmanager.current=this),t.ui.ddmanager&&!a.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e),this.dragging=!0,this._addClass(this.helper,"ui-sortable-helper"),this._mouseDrag(e),!0},_mouseDrag:function(e){var i,s,n,o,a=this.options,r=!1;for(this.position=this._generatePosition(e),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs),this.options.scroll&&(this.scrollParent[0]!==this.document[0]&&"HTML"!==this.scrollParent[0].tagName?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-e.pageY=0;i--)if(s=this.items[i],n=s.item[0],o=this._intersectsWithPointer(s),o&&s.instance===this.currentContainer&&n!==this.currentItem[0]&&this.placeholder[1===o?"next":"prev"]()[0]!==n&&!t.contains(this.placeholder[0],n)&&("semi-dynamic"===this.options.type?!t.contains(this.element[0],n):!0)){if(this.direction=1===o?"down":"up","pointer"!==this.options.tolerance&&!this._intersectsWithSides(s))break;this._rearrange(e,s),this._trigger("change",e,this._uiHash());break}return this._contactContainers(e),t.ui.ddmanager&&t.ui.ddmanager.drag(this,e),this._trigger("sort",e,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(e,i){if(e){if(t.ui.ddmanager&&!this.options.dropBehaviour&&t.ui.ddmanager.drop(this,e),this.options.revert){var s=this,n=this.placeholder.offset(),o=this.options.axis,a={};o&&"x"!==o||(a.left=n.left-this.offset.parent.left-this.margins.left+(this.offsetParent[0]===this.document[0].body?0:this.offsetParent[0].scrollLeft)),o&&"y"!==o||(a.top=n.top-this.offset.parent.top-this.margins.top+(this.offsetParent[0]===this.document[0].body?0:this.offsetParent[0].scrollTop)),this.reverting=!0,t(this.helper).animate(a,parseInt(this.options.revert,10)||500,function(){s._clear(e)})}else this._clear(e,i);return!1}},cancel:function(){if(this.dragging){this._mouseUp(new t.Event("mouseup",{target:null})),"original"===this.options.helper?(this.currentItem.css(this._storedCSS),this._removeClass(this.currentItem,"ui-sortable-helper")):this.currentItem.show();for(var e=this.containers.length-1;e>=0;e--)this.containers[e]._trigger("deactivate",null,this._uiHash(this)),this.containers[e].containerCache.over&&(this.containers[e]._trigger("out",null,this._uiHash(this)),this.containers[e].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),"original"!==this.options.helper&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),t.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?t(this.domPosition.prev).after(this.currentItem):t(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},t(i).each(function(){var i=(t(e.item||this).attr(e.attribute||"id")||"").match(e.expression||/(.+)[\-=_](.+)/);i&&s.push((e.key||i[1]+"[]")+"="+(e.key&&e.expression?i[1]:i[2]))}),!s.length&&e.key&&s.push(e.key+"="),s.join("&")},toArray:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},i.each(function(){s.push(t(e.item||this).attr(e.attribute||"id")||"")}),s},_intersectsWith:function(t){var e=this.positionAbs.left,i=e+this.helperProportions.width,s=this.positionAbs.top,n=s+this.helperProportions.height,o=t.left,a=o+t.width,r=t.top,l=r+t.height,h=this.offset.click.top,c=this.offset.click.left,u="x"===this.options.axis||s+h>r&&l>s+h,d="y"===this.options.axis||e+c>o&&a>e+c,p=u&&d;return"pointer"===this.options.tolerance||this.options.forcePointerForContainers||"pointer"!==this.options.tolerance&&this.helperProportions[this.floating?"width":"height"]>t[this.floating?"width":"height"]?p:e+this.helperProportions.width/2>o&&a>i-this.helperProportions.width/2&&s+this.helperProportions.height/2>r&&l>n-this.helperProportions.height/2},_intersectsWithPointer:function(t){var e,i,s="x"===this.options.axis||this._isOverAxis(this.positionAbs.top+this.offset.click.top,t.top,t.height),n="y"===this.options.axis||this._isOverAxis(this.positionAbs.left+this.offset.click.left,t.left,t.width),o=s&&n;return o?(e=this._getDragVerticalDirection(),i=this._getDragHorizontalDirection(),this.floating?"right"===i||"down"===e?2:1:e&&("down"===e?2:1)):!1},_intersectsWithSides:function(t){var e=this._isOverAxis(this.positionAbs.top+this.offset.click.top,t.top+t.height/2,t.height),i=this._isOverAxis(this.positionAbs.left+this.offset.click.left,t.left+t.width/2,t.width),s=this._getDragVerticalDirection(),n=this._getDragHorizontalDirection();return this.floating&&n?"right"===n&&i||"left"===n&&!i:s&&("down"===s&&e||"up"===s&&!e)},_getDragVerticalDirection:function(){var t=this.positionAbs.top-this.lastPositionAbs.top;return 0!==t&&(t>0?"down":"up")},_getDragHorizontalDirection:function(){var t=this.positionAbs.left-this.lastPositionAbs.left;return 0!==t&&(t>0?"right":"left")},refresh:function(t){return this._refreshItems(t),this._setHandleClassName(),this.refreshPositions(),this},_connectWith:function(){var t=this.options;return t.connectWith.constructor===String?[t.connectWith]:t.connectWith},_getItemsAsjQuery:function(e){function i(){r.push(this)}var s,n,o,a,r=[],l=[],h=this._connectWith();if(h&&e)for(s=h.length-1;s>=0;s--)for(o=t(h[s],this.document[0]),n=o.length-1;n>=0;n--)a=t.data(o[n],this.widgetFullName),a&&a!==this&&!a.options.disabled&&l.push([t.isFunction(a.options.items)?a.options.items.call(a.element):t(a.options.items,a.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),a]);for(l.push([t.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):t(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]),s=l.length-1;s>=0;s--)l[s][0].each(i);return t(r)},_removeCurrentsFromItems:function(){var e=this.currentItem.find(":data("+this.widgetName+"-item)");this.items=t.grep(this.items,function(t){for(var i=0;e.length>i;i++)if(e[i]===t.item[0])return!1;return!0})},_refreshItems:function(e){this.items=[],this.containers=[this];var i,s,n,o,a,r,l,h,c=this.items,u=[[t.isFunction(this.options.items)?this.options.items.call(this.element[0],e,{item:this.currentItem}):t(this.options.items,this.element),this]],d=this._connectWith();if(d&&this.ready)for(i=d.length-1;i>=0;i--)for(n=t(d[i],this.document[0]),s=n.length-1;s>=0;s--)o=t.data(n[s],this.widgetFullName),o&&o!==this&&!o.options.disabled&&(u.push([t.isFunction(o.options.items)?o.options.items.call(o.element[0],e,{item:this.currentItem}):t(o.options.items,o.element),o]),this.containers.push(o));for(i=u.length-1;i>=0;i--)for(a=u[i][1],r=u[i][0],s=0,h=r.length;h>s;s++)l=t(r[s]),l.data(this.widgetName+"-item",a),c.push({item:l,instance:a,width:0,height:0,left:0,top:0})},refreshPositions:function(e){this.floating=this.items.length?"x"===this.options.axis||this._isFloating(this.items[0].item):!1,this.offsetParent&&this.helper&&(this.offset.parent=this._getParentOffset());var i,s,n,o;for(i=this.items.length-1;i>=0;i--)s=this.items[i],s.instance!==this.currentContainer&&this.currentContainer&&s.item[0]!==this.currentItem[0]||(n=this.options.toleranceElement?t(this.options.toleranceElement,s.item):s.item,e||(s.width=n.outerWidth(),s.height=n.outerHeight()),o=n.offset(),s.left=o.left,s.top=o.top);if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(i=this.containers.length-1;i>=0;i--)o=this.containers[i].element.offset(),this.containers[i].containerCache.left=o.left,this.containers[i].containerCache.top=o.top,this.containers[i].containerCache.width=this.containers[i].element.outerWidth(),this.containers[i].containerCache.height=this.containers[i].element.outerHeight();return this},_createPlaceholder:function(e){e=e||this;var i,s=e.options;s.placeholder&&s.placeholder.constructor!==String||(i=s.placeholder,s.placeholder={element:function(){var s=e.currentItem[0].nodeName.toLowerCase(),n=t("<"+s+">",e.document[0]);return e._addClass(n,"ui-sortable-placeholder",i||e.currentItem[0].className)._removeClass(n,"ui-sortable-helper"),"tbody"===s?e._createTrPlaceholder(e.currentItem.find("tr").eq(0),t("",e.document[0]).appendTo(n)):"tr"===s?e._createTrPlaceholder(e.currentItem,n):"img"===s&&n.attr("src",e.currentItem.attr("src")),i||n.css("visibility","hidden"),n},update:function(t,n){(!i||s.forcePlaceholderSize)&&(n.height()||n.height(e.currentItem.innerHeight()-parseInt(e.currentItem.css("paddingTop")||0,10)-parseInt(e.currentItem.css("paddingBottom")||0,10)),n.width()||n.width(e.currentItem.innerWidth()-parseInt(e.currentItem.css("paddingLeft")||0,10)-parseInt(e.currentItem.css("paddingRight")||0,10)))}}),e.placeholder=t(s.placeholder.element.call(e.element,e.currentItem)),e.currentItem.after(e.placeholder),s.placeholder.update(e,e.placeholder)},_createTrPlaceholder:function(e,i){var s=this;e.children().each(function(){t(" ",s.document[0]).attr("colspan",t(this).attr("colspan")||1).appendTo(i)})},_contactContainers:function(e){var i,s,n,o,a,r,l,h,c,u,d=null,p=null;for(i=this.containers.length-1;i>=0;i--)if(!t.contains(this.currentItem[0],this.containers[i].element[0]))if(this._intersectsWith(this.containers[i].containerCache)){if(d&&t.contains(this.containers[i].element[0],d.element[0]))continue;d=this.containers[i],p=i}else this.containers[i].containerCache.over&&(this.containers[i]._trigger("out",e,this._uiHash(this)),this.containers[i].containerCache.over=0);if(d)if(1===this.containers.length)this.containers[p].containerCache.over||(this.containers[p]._trigger("over",e,this._uiHash(this)),this.containers[p].containerCache.over=1);else{for(n=1e4,o=null,c=d.floating||this._isFloating(this.currentItem),a=c?"left":"top",r=c?"width":"height",u=c?"pageX":"pageY",s=this.items.length-1;s>=0;s--)t.contains(this.containers[p].element[0],this.items[s].item[0])&&this.items[s].item[0]!==this.currentItem[0]&&(l=this.items[s].item.offset()[a],h=!1,e[u]-l>this.items[s][r]/2&&(h=!0),n>Math.abs(e[u]-l)&&(n=Math.abs(e[u]-l),o=this.items[s],this.direction=h?"up":"down"));if(!o&&!this.options.dropOnEmpty)return;if(this.currentContainer===this.containers[p])return this.currentContainer.containerCache.over||(this.containers[p]._trigger("over",e,this._uiHash()),this.currentContainer.containerCache.over=1),void 0;o?this._rearrange(e,o,null,!0):this._rearrange(e,null,this.containers[p].element,!0),this._trigger("change",e,this._uiHash()),this.containers[p]._trigger("change",e,this._uiHash(this)),this.currentContainer=this.containers[p],this.options.placeholder.update(this.currentContainer,this.placeholder),this.containers[p]._trigger("over",e,this._uiHash(this)),this.containers[p].containerCache.over=1}},_createHelper:function(e){var i=this.options,s=t.isFunction(i.helper)?t(i.helper.apply(this.element[0],[e,this.currentItem])):"clone"===i.helper?this.currentItem.clone():this.currentItem;return s.parents("body").length||t("parent"!==i.appendTo?i.appendTo:this.currentItem[0].parentNode)[0].appendChild(s[0]),s[0]===this.currentItem[0]&&(this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")}),(!s[0].style.width||i.forceHelperSize)&&s.width(this.currentItem.width()),(!s[0].style.height||i.forceHelperSize)&&s.height(this.currentItem.height()),s},_adjustOffsetFromHelper:function(e){"string"==typeof e&&(e=e.split(" ")),t.isArray(e)&&(e={left:+e[0],top:+e[1]||0}),"left"in e&&(this.offset.click.left=e.left+this.margins.left),"right"in e&&(this.offset.click.left=this.helperProportions.width-e.right+this.margins.left),"top"in e&&(this.offset.click.top=e.top+this.margins.top),"bottom"in e&&(this.offset.click.top=this.helperProportions.height-e.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var e=this.offsetParent.offset();return"absolute"===this.cssPosition&&this.scrollParent[0]!==this.document[0]&&t.contains(this.scrollParent[0],this.offsetParent[0])&&(e.left+=this.scrollParent.scrollLeft(),e.top+=this.scrollParent.scrollTop()),(this.offsetParent[0]===this.document[0].body||this.offsetParent[0].tagName&&"html"===this.offsetParent[0].tagName.toLowerCase()&&t.ui.ie)&&(e={top:0,left:0}),{top:e.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:e.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"===this.cssPosition){var t=this.currentItem.position();return{top:t.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:t.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e,i,s,n=this.options;"parent"===n.containment&&(n.containment=this.helper[0].parentNode),("document"===n.containment||"window"===n.containment)&&(this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,"document"===n.containment?this.document.width():this.window.width()-this.helperProportions.width-this.margins.left,("document"===n.containment?this.document.height()||document.body.parentNode.scrollHeight:this.window.height()||this.document[0].body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]),/^(document|window|parent)$/.test(n.containment)||(e=t(n.containment)[0],i=t(n.containment).offset(),s="hidden"!==t(e).css("overflow"),this.containment=[i.left+(parseInt(t(e).css("borderLeftWidth"),10)||0)+(parseInt(t(e).css("paddingLeft"),10)||0)-this.margins.left,i.top+(parseInt(t(e).css("borderTopWidth"),10)||0)+(parseInt(t(e).css("paddingTop"),10)||0)-this.margins.top,i.left+(s?Math.max(e.scrollWidth,e.offsetWidth):e.offsetWidth)-(parseInt(t(e).css("borderLeftWidth"),10)||0)-(parseInt(t(e).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,i.top+(s?Math.max(e.scrollHeight,e.offsetHeight):e.offsetHeight)-(parseInt(t(e).css("borderTopWidth"),10)||0)-(parseInt(t(e).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top])},_convertPositionTo:function(e,i){i||(i=this.position);var s="absolute"===e?1:-1,n="absolute"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,o=/(html|body)/i.test(n[0].tagName);return{top:i.top+this.offset.relative.top*s+this.offset.parent.top*s-("fixed"===this.cssPosition?-this.scrollParent.scrollTop():o?0:n.scrollTop())*s,left:i.left+this.offset.relative.left*s+this.offset.parent.left*s-("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():o?0:n.scrollLeft())*s} +},_generatePosition:function(e){var i,s,n=this.options,o=e.pageX,a=e.pageY,r="absolute"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,l=/(html|body)/i.test(r[0].tagName);return"relative"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&this.scrollParent[0]!==this.offsetParent[0]||(this.offset.relative=this._getRelativeOffset()),this.originalPosition&&(this.containment&&(e.pageX-this.offset.click.leftthis.containment[2]&&(o=this.containment[2]+this.offset.click.left),e.pageY-this.offset.click.top>this.containment[3]&&(a=this.containment[3]+this.offset.click.top)),n.grid&&(i=this.originalPageY+Math.round((a-this.originalPageY)/n.grid[1])*n.grid[1],a=this.containment?i-this.offset.click.top>=this.containment[1]&&i-this.offset.click.top<=this.containment[3]?i:i-this.offset.click.top>=this.containment[1]?i-n.grid[1]:i+n.grid[1]:i,s=this.originalPageX+Math.round((o-this.originalPageX)/n.grid[0])*n.grid[0],o=this.containment?s-this.offset.click.left>=this.containment[0]&&s-this.offset.click.left<=this.containment[2]?s:s-this.offset.click.left>=this.containment[0]?s-n.grid[0]:s+n.grid[0]:s)),{top:a-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.scrollParent.scrollTop():l?0:r.scrollTop()),left:o-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():l?0:r.scrollLeft())}},_rearrange:function(t,e,i,s){i?i[0].appendChild(this.placeholder[0]):e.item[0].parentNode.insertBefore(this.placeholder[0],"down"===this.direction?e.item[0]:e.item[0].nextSibling),this.counter=this.counter?++this.counter:1;var n=this.counter;this._delay(function(){n===this.counter&&this.refreshPositions(!s)})},_clear:function(t,e){function i(t,e,i){return function(s){i._trigger(t,s,e._uiHash(e))}}this.reverting=!1;var s,n=[];if(!this._noFinalSort&&this.currentItem.parent().length&&this.placeholder.before(this.currentItem),this._noFinalSort=null,this.helper[0]===this.currentItem[0]){for(s in this._storedCSS)("auto"===this._storedCSS[s]||"static"===this._storedCSS[s])&&(this._storedCSS[s]="");this.currentItem.css(this._storedCSS),this._removeClass(this.currentItem,"ui-sortable-helper")}else this.currentItem.show();for(this.fromOutside&&!e&&n.push(function(t){this._trigger("receive",t,this._uiHash(this.fromOutside))}),!this.fromOutside&&this.domPosition.prev===this.currentItem.prev().not(".ui-sortable-helper")[0]&&this.domPosition.parent===this.currentItem.parent()[0]||e||n.push(function(t){this._trigger("update",t,this._uiHash())}),this!==this.currentContainer&&(e||(n.push(function(t){this._trigger("remove",t,this._uiHash())}),n.push(function(t){return function(e){t._trigger("receive",e,this._uiHash(this))}}.call(this,this.currentContainer)),n.push(function(t){return function(e){t._trigger("update",e,this._uiHash(this))}}.call(this,this.currentContainer)))),s=this.containers.length-1;s>=0;s--)e||n.push(i("deactivate",this,this.containers[s])),this.containers[s].containerCache.over&&(n.push(i("out",this,this.containers[s])),this.containers[s].containerCache.over=0);if(this.storedCursor&&(this.document.find("body").css("cursor",this.storedCursor),this.storedStylesheet.remove()),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex","auto"===this._storedZIndex?"":this._storedZIndex),this.dragging=!1,e||this._trigger("beforeStop",t,this._uiHash()),this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.cancelHelperRemoval||(this.helper[0]!==this.currentItem[0]&&this.helper.remove(),this.helper=null),!e){for(s=0;n.length>s;s++)n[s].call(this,t);this._trigger("stop",t,this._uiHash())}return this.fromOutside=!1,!this.cancelHelperRemoval},_trigger:function(){t.Widget.prototype._trigger.apply(this,arguments)===!1&&this.cancel()},_uiHash:function(e){var i=e||this;return{helper:i.helper,placeholder:i.placeholder||t([]),position:i.position,originalPosition:i.originalPosition,offset:i.positionAbs,item:i.currentItem,sender:e?e.element:null}}})}); \ No newline at end of file diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/js/palette.js b/ext/phpbbstudio/aps/styles/prosilver/template/js/palette.js new file mode 100644 index 0000000..5e53621 --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/js/palette.js @@ -0,0 +1,1501 @@ +/** @license + * + * Colour Palette Generator script. + * Copyright (c) 2014 Google Inc. + * + * 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. + * + * Furthermore, ColorBrewer colour schemes are covered by the following: + * + * Copyright (c) 2002 Cynthia Brewer, Mark Harrower, and + * The Pennsylvania State University. + * + * 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. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions as source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. The end-user documentation included with the redistribution, if any, + * must include the following acknowledgment: "This product includes color + * specifications and designs developed by Cynthia Brewer + * (http://colorbrewer.org/)." Alternately, this acknowledgment may appear + * in the software itself, if and wherever such third-party + * acknowledgments normally appear. + * + * 4. The name "ColorBrewer" must not be used to endorse or promote products + * derived from this software without prior written permission. For written + * permission, please contact Cynthia Brewer at cbrewer@psu.edu. + * + * 5. Products derived from this software may not be called "ColorBrewer", + * nor may "ColorBrewer" appear in their name, without prior written + * permission of Cynthia Brewer. + * + * Furthermore, Solarized colour schemes are covered by the following: + * + * Copyright (c) 2011 Ethan Schoonover + * + * 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. + */ + +'use strict'; + +var palette = (function() { + + var proto = Array.prototype; + var slice = function(arr, opt_begin, opt_end) { + return proto.slice.apply(arr, proto.slice.call(arguments, 1)); + }; + + var extend = function(arr, arr2) { + return proto.push.apply(arr, arr2); + }; + + var function_type = typeof function() {}; + + var INF = 1000000000; // As far as we're concerned, that's infinity. ;) + + + /** + * Generate a colour palette from given scheme. + * + * If scheme argument is not a function it is passed to palettes.listSchemes + * function (along with the number argument). This may result in an array + * of more than one available scheme. If that is the case, scheme at + * opt_index position is taken. + * + * This allows using different palettes for different data without having to + * name the schemes specifically, for example: + * + * palette_for_foo = palette('sequential', 10, 0); + * palette_for_bar = palette('sequential', 10, 1); + * palette_for_baz = palette('sequential', 10, 2); + * + * @param {!palette.SchemeType|string|palette.Palette} scheme Scheme to + * generate palette for. Either a function constructed with + * palette.Scheme object, or anything that palette.listSchemes accepts + * as name argument. + * @param {number} number Number of colours to return. If negative, absolute + * value is taken and colours will be returned in reverse order. + * @param {number=} opt_index If scheme is a name of a group or an array and + * results in more than one scheme, index of the scheme to use. The + * index wraps around. + * @param {...*} varargs Additional arguments to pass to palette or colour + * generator (if the chosen scheme uses those). + * @return {Array} Array of abs(number) 'RRGGBB' strings or null if + * no matching scheme was found. + */ + var palette = function(scheme, number, opt_index, varargs) { + number |= 0; + if (number == 0) { + return []; + } + + if (typeof scheme !== function_type) { + var arr = palette.listSchemes( + /** @type {string|palette.Palette} */ (scheme), number); + if (!arr.length) { + return null; + } + scheme = arr[(opt_index || 0) % arr.length]; + } + + var args = slice(arguments, 2); + args[0] = number; + return scheme.apply(scheme, args); + }; + + + /** + * Returns a callable colour scheme object. + * + * Just after being created, the scheme has no colour palettes and no way of + * generating any, thus generate method will return null. To turn scheme + * into a useful object, addPalette, addPalettes or setColorFunction methods + * need to be used. + * + * To generate a colour palette with given number colours using function + * returned by this method, just call it with desired number of colours. + * + * Since this function *returns* a callable object, it must *not* be used + * with the new operator. + * + * @param {string} name Name of the scheme. + * @param {string|!Array=} opt_groups A group name or list of + * groups the scheme should be categorised under. Three typical groups + * to use are 'qualitative', 'sequential' and 'diverging', but any + * groups may be created. + * @return {!palette.SchemeType} A colour palette generator function, which + * in addition has methods and properties like a regular object. Think + * of it as a callable object. + */ + palette.Scheme = function(name, opt_groups) { + /** + * A map from a number to a colour palettes with given number of colours. + * @type {!Object} + */ + var palettes = {}; + + /** + * The biggest palette in palettes map. + * @type {number} + */ + var palettes_max = 0; + + /** + * The smallest palette in palettes map. + * @type {number} + */ + var palettes_min = INF; + + var makeGenerator = function() { + if (arguments.length <= 1) { + return self.color_func.bind(self); + } else { + var args = slice(arguments); + return function(x) { + args[0] = x; + return self.color_func.apply(self, args); + }; + } + }; + + /** + * Generate a colour palette from the scheme. + * + * If there was a palette added with addPalette (or addPalettes) with + * enough colours, that palette will be used. Otherwise, if colour + * function has been set using setColorFunction method, that function will + * be used to generate the palette. Otherwise null is returned. + * + * @param {number} number Number of colours to return. If negative, + * absolute value is taken and colours will be returned in reverse + * order. + * @param {...*} varargs Additional arguments to pass to palette or colour + * generator (if the chosen scheme uses those). + */ + var self = function(number, varargs) { + number |= 0; + if (!number) { + return []; + } + + var _number = number; + number = Math.abs(number); + + if (number <= palettes_max) { + for (var i = Math.max(number, palettes_min); !(i in palettes); ++i) { + /* nop */ + } + var colors = palettes[i]; + if (i > number) { + var take_head = + 'shrinking_takes_head' in colors ? + colors.shrinking_takes_head : self.shrinking_takes_head; + if (take_head) { + colors = colors.slice(0, number); + i = number; + } else { + return palette.generate( + function(x) { return colors[Math.round(x)]; }, + _number, 0, colors.length - 1); + } + } + colors = colors.slice(); + if (_number < 0) { + colors.reverse(); + } + return colors; + + } else if (self.color_func) { + return palette.generate(makeGenerator.apply(self, arguments), + _number, 0, 1, self.color_func_cyclic); + + } else { + return null; + } + }; + + /** + * The name of the palette. + * @type {string} + */ + self.scheme_name = name; + + /** + * A list of groups the palette belongs to. + * @type {!Array} + */ + self.groups = opt_groups ? + typeof opt_groups === 'string' ? [opt_groups] : opt_groups : []; + + /** + * The biggest palette this scheme can generate. + * @type {number} + */ + self.max = 0; + + /** + * The biggest palette this scheme can generate that is colour-blind + * friendly. + * @type {number} + */ + self.cbf_max = INF; + + + /** + * Adds a colour palette to the colour scheme. + * + * @param {palette.Palette} palette An array of 'RRGGBB' strings + * representing the palette to add. + * @param {boolean=} opt_is_cbf Whether the palette is colourblind friendly. + */ + self.addPalette = function(palette, opt_is_cbf) { + var len = palette.length; + if (len) { + palettes[len] = palette; + palettes_min = Math.min(palettes_min, len); + palettes_max = Math.max(palettes_max, len); + self.max = Math.max(self.max, len); + if (!opt_is_cbf && len != 1) { + self.cbf_max = Math.min(self.cbf_max, len - 1); + } + } + }; + + /** + * Adds number of colour palettes to the colour scheme. + * + * @param {palette.PalettesList} palettes A map or an array of colour + * palettes to add. If map, i.e. object, is used, properties should + * use integer property names. + * @param {number=} opt_max Size of the biggest palette in palettes set. + * If not set, palettes must have a length property which will be used. + * @param {number=} opt_cbf_max Size of the biggest palette which is still + * colourblind friendly. 1 by default. + */ + self.addPalettes = function(palettes, opt_max, opt_cbf_max) { + opt_max = opt_max || palettes.length; + for (var i = 0; i < opt_max; ++i) { + if (i in palettes) { + self.addPalette(palettes[i], true); + } + } + self.cbf_max = Math.min(self.cbf_max, opt_cbf_max || 1); + }; + + /** + * Enable shrinking palettes taking head of the list of colours. + * + * When user requests n-colour palette but the smallest palette added with + * addPalette (or addPalettes) is m-colour one (where n < m), n colours + * across the palette will be returned. For example: + * var ex = palette.Scheme('ex'); + * ex.addPalette(['000000', 'bcbcbc', 'ffffff']); + * var pal = ex(2); + * // pal == ['000000', 'ffffff'] + * + * This works for palettes where the distance between colours is + * correlated to distance in the palette array, which is true in gradients + * such as the one above. + * + * To turn this feature off shrinkByTakingHead can be set to true either + * for all palettes in the scheme (if opt_idx is not given) or for palette + * with given number of colours only. In general, setting the option for + * given palette overwrites whatever has been set for the scheme. The + * default, as described above, is false. + * + * Alternatively, the feature can be enabled by setting shrinking_takes_head + * property for the palette Array or the scheme object. + * + * For example, all of the below give equivalent results: + * var pal = ['ff0000', '00ff00', '0000ff']; + * + * var ex = palette.Scheme('ex'); + * ex.addPalette(pal); // ex(2) == ['ff0000', '0000ff'] + * ex.shrinkByTakingHead(true); // ex(2) == ['ff0000', '00ff00'] + * + * ex = palette.Scheme('ex'); + * ex.addPalette(pal); // ex(2) == ['ff0000', '0000ff'] + * ex.shrinkByTakingHead(true, 3); // ex(2) == ['ff0000', '00ff00'] + * + * ex = palette.Scheme('ex'); + * ex.addPalette(pal); + * ex.addPalette(pal); // ex(2) == ['ff0000', '0000ff'] + * pal.shrinking_takes_head = true; // ex(2) == ['ff0000', '00ff00'] + * + * @param {boolean} enabled Whether to enable or disable the “shrinking + * takes head” feature. It is disabled by default. + * @param {number=} opt_idx If given, the “shrinking takes head” option + * for palette with given number of colours is set. If such palette + * does not exist, nothing happens. + */ + self.shrinkByTakingHead = function(enabled, opt_idx) { + if (opt_idx !== void(0)) { + if (opt_idx in palettes) { + palettes[opt_idx].shrinking_takes_head = !!enabled; + } + } else { + self.shrinking_takes_head = !!enabled; + } + }; + + /** + * Sets a colour generation function of the colour scheme. + * + * The function must accept a singe number argument whose value can be from + * 0.0 to 1.0, and return a colour as an 'RRGGBB' string. This function + * will be used when generating palettes, i.e. if 11-colour palette is + * requested, this function will be called with arguments 0.0, 0.1, …, 1.0. + * + * If the palette generated by the function is colourblind friendly, + * opt_is_cbf should be set to true. + * + * In some cases, it is not desirable to reach 1.0 when generating + * a palette. This happens for hue-rainbows where the 0–1 range corresponds + * to a 0°–360° range in hues, and since hue at 0° is the same as at 360°, + * it's desired to stop short the end of the range when generating + * a palette. To accomplish this, opt_cyclic should be set to true. + * + * @param {palette.ColorFunction} func A colour generator function. + * @param {boolean=} opt_is_cbf Whether palette generate with the function + * is colour-blind friendly. + * @param {boolean=} opt_cyclic Whether colour at 0.0 is the same as the + * one at 1.0. + */ + self.setColorFunction = function(func, opt_is_cbf, opt_cyclic) { + self.color_func = func; + self.color_func_cyclic = !!opt_cyclic; + self.max = INF; + if (!opt_is_cbf && self.cbf_max === INF) { + self.cbf_max = 1; + } + }; + + self.color = function(x, varargs) { + if (self.color_func) { + return self.color_func.apply(this, arguments); + } else { + return null; + } + }; + + return self; + }; + + + /** + * Creates a new palette.Scheme and initialises it by calling addPalettes + * method with the rest of the arguments. + * + * @param {string} name Name of the scheme. + * @param {string|!Array} groups A group name or list of group + * names the scheme belongs to. + * @param {!Object|!Array} + * palettes A map or an array of colour palettes to add. If map, i.e. + * object, is used, properties should use integer property names. + * @param {number=} opt_max Size of the biggest palette in palettes set. + * If not set, palettes must have a length property which will be used. + * @param {number=} opt_cbf_max Size of the biggest palette which is still + * colourblind friendly. 1 by default. + * @return {!palette.SchemeType} A colour palette generator function, which + * in addition has methods and properties like a regular object. Think + * of it as a callable object. + */ + palette.Scheme.fromPalettes = function(name, groups, + palettes, opt_max, opt_cbf_max) { + var scheme = palette.Scheme(name, groups); + scheme.addPalettes.apply(scheme, slice(arguments, 2)); + return scheme; + }; + + + /** + * Creates a new palette.Scheme and initialises it by calling + * setColorFunction method with the rest of the arguments. + * + * @param {string} name Name of the scheme. + * @param {string|!Array} groups A group name or list of group + * names the scheme belongs to. + * @param {palette.ColorFunction} func A colour generator function. + * @param {boolean=} opt_is_cbf Whether palette generate with the function + * is colour-blind friendly. + * @param {boolean=} opt_cyclic Whether colour at 0.0 is the same as the + * one at 1.0. + * @return {!palette.SchemeType} A colour palette generator function, which + * in addition has methods and properties like a regular object. Think + * of it as a callable object. + */ + palette.Scheme.withColorFunction = function(name, groups, + func, opt_is_cbf, opt_cyclic) { + var scheme = palette.Scheme(name, groups); + scheme.setColorFunction.apply(scheme, slice(arguments, 2)); + return scheme; + }; + + + /** + * A map of registered schemes. Maps a scheme or group name to a list of + * scheme objects. Property name is either 'n-' for single scheme + * names or 'g-' for scheme group names. + * + * @type {!Object>} + */ + var registered_schemes = {}; + + + /** + * Registers a new colour scheme. + * + * @param {!palette.SchemeType} scheme The scheme to add. + */ + palette.register = function(scheme) { + registered_schemes['n-' + scheme.scheme_name] = [scheme]; + scheme.groups.forEach(function(g) { + (registered_schemes['g-' + g] = + registered_schemes['g-' + g] || []).push(scheme); + }); + (registered_schemes['g-all'] = + registered_schemes['g-all'] || []).push(scheme); + }; + + + /** + * List all schemes that match given name and number of colours. + * + * name argument can be either a string or an array of strings. In the + * former case, the function acts as if the argument was an array with name + * as a single argument (i.e. “palette.listSchemes('foo')” is exactly the same + * as “palette.listSchemes(['foo'])”). + * + * Each name can be either name of a palette (e.g. 'tol-sq' for Paul Tol's + * sequential palette), or a name of a group (e.g. 'sequential' for all + * sequential palettes). Name can therefore map to a single scheme or + * several schemes. + * + * Furthermore, name can be suffixed with '-cbf' to indicate that only + * schemes that are colourblind friendly should be returned. For example, + * 'rainbow' returns a HSV rainbow scheme, but because it is not colourblind + * friendly, 'rainbow-cbf' returns no schemes. + * + * Some schemes may produce colourblind friendly palettes for some number of + * colours. For example ColorBrewer's Dark2 scheme is colourblind friendly + * if no more than 3 colours are generated. If opt_number is not specified, + * 'qualitative-cbf' will include 'cb-Dark2' but if opt_number is given as, + * say, 5 it won't. + * + * Name can also be 'all' which will return all registered schemes. + * Naturally, 'all-cbf' will return all colourblind friendly schemes. + * + * Schemes are added to the library using palette.register. Schemes are + * created using palette.Scheme function. By default, the following schemes + * are available: + * + * Name Description + * -------------- ----------------------------------------------------- + * tol Paul Tol's qualitative scheme, cbf, max 12 colours. + * tol-dv Paul Tol's diverging scheme, cbf. + * tol-sq Paul Tol's sequential scheme, cbf. + * tol-rainbow Paul Tol's qualitative scheme, cbf. + * + * rainbow A rainbow palette. + * + * cb-YlGn ColorBrewer sequential schemes. + * cb-YlGnBu + * cb-GnBu + * cb-BuGn + * cb-PuBuGn + * cb-PuBu + * cb-BuPu + * cb-RdPu + * cb-PuRd + * cb-OrRd + * cb-YlOrRd + * cb-YlOrBr + * cb-Purples + * cb-Blues + * cb-Greens + * cb-Oranges + * cb-Reds + * cb-Greys + * + * cb-PuOr ColorBrewer diverging schemes. + * cb-BrBG + * cb-PRGn + * cb-PiYG + * cb-RdBu + * cb-RdGy + * cb-RdYlBu + * cb-Spectral + * cb-RdYlGn + * + * cb-Accent ColorBrewer qualitative schemes. + * cb-Dark2 + * cb-Paired + * cb-Pastel1 + * cb-Pastel2 + * cb-Set1 + * cb-Set2 + * cb-Set3 + * + * sol-base Solarized base colours. + * sol-accent Solarized accent colours. + * + * The following groups are also available by default: + * + * Name Description + * -------------- ----------------------------------------------------- + * all All registered schemes. + * sequential All sequential schemes. + * diverging All diverging schemes. + * qualitative All qualitative schemes. + * cb-sequential All ColorBrewer sequential schemes. + * cb-diverging All ColorBrewer diverging schemes. + * cb-qualitative All ColorBrewer qualitative schemes. + * + * You can read more about Paul Tol's palettes at http://www.sron.nl/~pault/. + * You can read more about ColorBrewer at http://colorbrewer2.org. + * + * @param {string|!Array} name A name of a colour scheme, of + * a group of colour schemes, or an array of any of those. + * @param {number=} opt_number When requesting only colourblind friendly + * schemes, number of colours the scheme must provide generating such + * that the palette is still colourblind friendly. 2 by default. + * @return {!Array} An array of colour scheme objects + * matching the criteria. Sorted by scheme name. + */ + palette.listSchemes = function(name, opt_number) { + if (!opt_number) { + opt_number = 2; + } else if (opt_number < 0) { + opt_number = -opt_number; + } + + var ret = []; + (typeof name === 'string' ? [name] : name).forEach(function(n) { + var cbf = n.substring(n.length - 4) === '-cbf'; + if (cbf) { + n = n.substring(0, n.length - 4); + } + var schemes = + registered_schemes['g-' + n] || + registered_schemes['n-' + n] || + []; + for (var i = 0, scheme; (scheme = schemes[i]); ++i) { + if ((cbf ? scheme.cbf : scheme.max) >= opt_number) { + ret.push(scheme); + } + } + }); + + ret.sort(function(a, b) { + return a.scheme_name >= b.scheme_name ? + a.scheme_name > b.scheme_name ? 1 : 0 : -1; + }); + return ret; + }; + + + /** + * Generates a palette using given colour generating function. + * + * The color_func callback must accept a singe number argument whose value + * can vary from 0.0 to 1.0 (or in general from opt_start to opt_end), and + * return a colour as an 'RRGGBB' string. This function will be used when + * generating palettes, i.e. if 11-colour palette is requested, this + * function will be called with arguments 0.0, 0.1, …, 1.0. + * + * In some cases, it is not desirable to reach 1.0 when generating + * a palette. This happens for hue-rainbows where the 0–1 range corresponds + * to a 0°–360° range in hues, and since hue at 0° is the same as at 360°, + * it's desired to stop short the end of the range when generating + * a palette. To accomplish this, opt_cyclic should be set to true. + * + * opt_start and opt_end may be used to change the range the colour + * generation function is called with. opt_end may be less than opt_start + * which will case to traverse the range in reverse. Another way to reverse + * the palette is requesting negative number of colours. The two methods do + * not always lead to the same results (especially if opt_cyclic is set). + * + * @param {palette.ColorFunction} color_func A colours generating callback + * function. + * @param {number} number Number of colours to generate in the palette. If + * number is negative, colours in the palette will be reversed. If only + * one colour is requested, colour at opt_start will be returned. + * @param {number=} opt_start Optional starting point for the palette + * generation function. Zero by default. + * @param {number=} opt_end Optional ending point for the palette generation + * function. One by default. + * @param {boolean=} opt_cyclic If true, function will assume colour at + * point opt_start is the same as one at opt_end. + * @return {palette.Palette} An array of 'RRGGBB' colours. + */ + palette.generate = function(color_func, number, opt_start, opt_end, + opt_cyclic) { + if (Math.abs(number) < 1) { + return []; + } + + opt_start = opt_start === void(0) ? 0 : opt_start; + opt_end = opt_end === void(0) ? 1 : opt_end; + + if (Math.abs(number) < 2) { + return [color_func(opt_start)]; + } + + var i = Math.abs(number); + var x = opt_start; + var ret = []; + var step = (opt_end - opt_start) / (opt_cyclic ? i : (i - 1)); + + for (; --i >= 0; x += step) { + ret.push(color_func(x)); + } + if (number < 0) { + ret.reverse(); + } + return ret; + }; + + + /** + * Clamps value to [0, 1] range. + * @param {number} v Number to limit value of. + * @return {number} If v is inside of [0, 1] range returns v, otherwise + * returns 0 or 1 depending which side of the range v is closer to. + */ + var clamp = function(v) { + return v > 0 ? (v < 1 ? v : 1) : 0; + }; + + /** + * Converts r, g, b triple into RRGGBB hex representation. + * @param {number} r Red value of the colour in the range [0, 1]. + * @param {number} g Green value of the colour in the range [0, 1]. + * @param {number} b Blue value of the colour in the range [0, 1]. + * @return {string} A lower-case RRGGBB representation of the colour. + */ + palette.rgbColor = function(r, g, b) { + return [r, g, b].map(function(v) { + v = Number(Math.round(clamp(v) * 255)).toString(16); + return v.length == 1 ? '0' + v : v; + }).join(''); + }; + + /** + * Converts a linear r, g, b triple into RRGGBB hex representation. + * @param {number} r Linear red value of the colour in the range [0, 1]. + * @param {number} g Linear green value of the colour in the range [0, 1]. + * @param {number} b Linear blue value of the colour in the range [0, 1]. + * @return {string} A lower-case RRGGBB representation of the colour. + */ + palette.linearRgbColor = function(r, g, b) { + // http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_RGB.html + return [r, g, b].map(function(v) { + v = clamp(v); + if (v <= 0.0031308) { + v = 12.92 * v; + } else { + v = 1.055 * Math.pow(v, 1 / 2.4) - 0.055; + } + v = Number(Math.round(v * 255)).toString(16); + return v.length == 1 ? '0' + v : v; + }).join(''); + }; + + /** + * Converts an HSV colours to RRGGBB hex representation. + * @param {number} h Hue in the range [0, 1]. + * @param {number=} opt_s Saturation in the range [0, 1]. One by default. + * @param {number=} opt_v Value in the range [0, 1]. One by default. + * @return {string} An RRGGBB representation of the colour. + */ + palette.hsvColor = function(h, opt_s, opt_v) { + h *= 6; + var s = opt_s === void(0) ? 1 : clamp(opt_s); + var v = opt_v === void(0) ? 1 : clamp(opt_v); + var x = v * (1 - s * Math.abs(h % 2 - 1)); + var m = v * (1 - s); + switch (Math.floor(h) % 6) { + case 0: return palette.rgbColor(v, x, m); + case 1: return palette.rgbColor(x, v, m); + case 2: return palette.rgbColor(m, v, x); + case 3: return palette.rgbColor(m, x, v); + case 4: return palette.rgbColor(x, m, v); + default: return palette.rgbColor(v, m, x); + } + }; + + palette.register(palette.Scheme.withColorFunction( + 'rainbow', 'qualitative', palette.hsvColor, false, true)); + + return palette; +})(); + + +/** @typedef {function(number): string} */ +palette.ColorFunction; + +/** @typedef {!Array} */ +palette.Palette; + +/** @typedef {!Object|!Array} */ +palette.PalettesList; + +/** + * @typedef { + * function(number, ...?): Array| + * { + * scheme_name: string, + * groups: !Array, + * max: number, + * cbf_max: number, + * addPalette: function(!Array, boolean=), + * addPalettes: function(palette.PalettesList, number=, number=), + * shrinkByTakingHead: function(boolean, number=), + * setColorFunction: function(palette.ColorFunction, boolean=, boolean=), + * color: function(number, ...?): ?string}} + */ +palette.SchemeType; + + +/* mpn65 palette start here. ************************************************/ + +/* The ‘mpn65’ palette is designed for systems which show many graphs which + don’t have custom colour palettes chosen by humans or if number of necessary + colours isn’t know a priori. */ + +(function() { + var scheme = palette.Scheme.fromPalettes('mpn65', 'qualitative', [[ + 'ff0029', '377eb8', '66a61e', '984ea3', '00d2d5', 'ff7f00', 'af8d00', + '7f80cd', 'b3e900', 'c42e60', 'a65628', 'f781bf', '8dd3c7', 'bebada', + 'fb8072', '80b1d3', 'fdb462', 'fccde5', 'bc80bd', 'ffed6f', 'c4eaff', + 'cf8c00', '1b9e77', 'd95f02', 'e7298a', 'e6ab02', 'a6761d', '0097ff', + '00d067', '000000', '252525', '525252', '737373', '969696', 'bdbdbd', + 'f43600', '4ba93b', '5779bb', '927acc', '97ee3f', 'bf3947', '9f5b00', + 'f48758', '8caed6', 'f2b94f', 'eff26e', 'e43872', 'd9b100', '9d7a00', + '698cff', 'd9d9d9', '00d27e', 'd06800', '009f82', 'c49200', 'cbe8ff', + 'fecddf', 'c27eb6', '8cd2ce', 'c4b8d9', 'f883b0', 'a49100', 'f48800', + '27d0df', 'a04a9b' + ]]); + scheme.shrinkByTakingHead(true); + palette.register(scheme); +})(); + +/* Paul Tol's schemes start here. *******************************************/ +/* See http://www.sron.nl/~pault/ */ + +(function() { + var rgb = palette.rgbColor; + + /** + * Calculates value of a polynomial at given point. + * For example, poly(x, 1, 2, 3) calculates value of “1 + 2*x + 3*X²”. + * @param {number} x Value to calculate polynomial for. + * @param {...number} varargs Coefficients of the polynomial specified in + * the order of rising powers of x including constant as the first + * variable argument. + */ + var poly = function(x, varargs) { + var i = arguments.length - 1, n = arguments[i]; + while (i > 1) { + n = n * x + arguments[--i]; + } + return n; + }; + + /** + * Calculate approximate value of error function with maximum error of 0.0005. + * See . + * @param {number} x Argument of the error function. + * @return {number} Value of error function for x. + */ + var erf = function(x) { + // https://en.wikipedia.org/wiki/Error_function#Approximation_with_elementary_functions + // This produces a maximum error of 0.0005 which is more then we need. In + // the worst case, that error is multiplied by four and then divided by two + // before being multiplied by 255, so in the end, the error is multiplied by + // 510 which produces 0.255 which is less than a single colour step. + var y = poly(Math.abs(x), 1, 0.278393, 0.230389, 0.000972, 0.078108); + y *= y; // y^2 + y *= y; // y^4 + y = 1 - 1 / y; + return x < 0 ? -y : y; + }; + + palette.register(palette.Scheme.fromPalettes('tol', 'qualitative', [ + ['4477aa'], + ['4477aa', 'cc6677'], + ['4477aa', 'ddcc77', 'cc6677'], + ['4477aa', '117733', 'ddcc77', 'cc6677'], + ['332288', '88ccee', '117733', 'ddcc77', 'cc6677'], + ['332288', '88ccee', '117733', 'ddcc77', 'cc6677', 'aa4499'], + ['332288', '88ccee', '44aa99', '117733', 'ddcc77', 'cc6677', 'aa4499'], + ['332288', '88ccee', '44aa99', '117733', '999933', 'ddcc77', 'cc6677', + 'aa4499'], + ['332288', '88ccee', '44aa99', '117733', '999933', 'ddcc77', 'cc6677', + '882255', 'aa4499'], + ['332288', '88ccee', '44aa99', '117733', '999933', 'ddcc77', '661100', + 'cc6677', '882255', 'aa4499'], + ['332288', '6699cc', '88ccee', '44aa99', '117733', '999933', 'ddcc77', + '661100', 'cc6677', '882255', 'aa4499'], + ['332288', '6699cc', '88ccee', '44aa99', '117733', '999933', 'ddcc77', + '661100', 'cc6677', 'aa4466', '882255', 'aa4499'] + ], 12, 12)); + + /** + * Calculates a colour along Paul Tol's sequential colours axis. + * See figure 7 and equation 1. + * @param {number} x Position of the colour on the axis in the [0, 1] range. + * @return {string} An RRGGBB representation of the colour. + */ + palette.tolSequentialColor = function(x) { + return rgb(1 - 0.392 * (1 + erf((x - 0.869) / 0.255)), + 1.021 - 0.456 * (1 + erf((x - 0.527) / 0.376)), + 1 - 0.493 * (1 + erf((x - 0.272) / 0.309))); + }; + + palette.register(palette.Scheme.withColorFunction( + 'tol-sq', 'sequential', palette.tolSequentialColor, true)); + + /** + * Calculates a colour along Paul Tol's diverging colours axis. + * See figure 8 and equation 2. + * @param {number} x Position of the colour on the axis in the [0, 1] range. + * @return {string} An RRGGBB representation of the colour. + */ + palette.tolDivergingColor = function(x) { + var g = poly(x, 0.572, 1.524, -1.811) / poly(x, 1, -0.291, 0.1574); + return rgb(poly(x, 0.235, -2.13, 26.92, -65.5, 63.5, -22.36), + g * g, + 1 / poly(x, 1.579, -4.03, 12.92, -31.4, 48.6, -23.36)); + }; + + palette.register(palette.Scheme.withColorFunction( + 'tol-dv', 'diverging', palette.tolDivergingColor, true)); + + /** + * Calculates a colour along Paul Tol's rainbow colours axis. + * See figure 13 and equation 3. + * @param {number} x Position of the colour on the axis in the [0, 1] range. + * @return {string} An RRGGBB representation of the colour. + */ + palette.tolRainbowColor = function(x) { + return rgb(poly(x, 0.472, -0.567, 4.05) / poly(x, 1, 8.72, -19.17, 14.1), + poly(x, 0.108932, -1.22635, 27.284, -98.577, 163.3, -131.395, + 40.634), + 1 / poly(x, 1.97, 3.54, -68.5, 243, -297, 125)); + }; + + palette.register(palette.Scheme.withColorFunction( + 'tol-rainbow', 'qualitative', palette.tolRainbowColor, true)); +})(); + + +/* Solarized colour schemes start here. *************************************/ +/* See http://ethanschoonover.com/solarized */ + +(function() { + /* + * Those are not really designed to be used in graphs, but we're keeping + * them here in case someone cares. + */ + palette.register(palette.Scheme.fromPalettes('sol-base', 'sequential', [ + ['002b36', '073642', '586e75', '657b83', '839496', '93a1a1', 'eee8d5', + 'fdf6e3'] + ], 1, 8)); + palette.register(palette.Scheme.fromPalettes('sol-accent', 'qualitative', [ + ['b58900', 'cb4b16', 'dc322f', 'd33682', '6c71c4', '268bd2', '2aa198', + '859900'] + ])); +})(); + + +/* ColorBrewer colour schemes start here. ***********************************/ +/* See http://colorbrewer2.org/ */ + +(function() { + var schemes = { + YlGn: { + type: 'sequential', + cbf: 42, + 3: ['f7fcb9', 'addd8e', '31a354'], + 4: ['ffffcc', 'c2e699', '78c679', '238443'], + 5: ['ffffcc', 'c2e699', '78c679', '31a354', '006837'], + 6: ['ffffcc', 'd9f0a3', 'addd8e', '78c679', '31a354', '006837'], + 7: ['ffffcc', 'd9f0a3', 'addd8e', '78c679', '41ab5d', '238443', + '005a32'], + 8: ['ffffe5', 'f7fcb9', 'd9f0a3', 'addd8e', '78c679', '41ab5d', + '238443', '005a32'], + 9: ['ffffe5', 'f7fcb9', 'd9f0a3', 'addd8e', '78c679', '41ab5d', + '238443', '006837', '004529'] + }, + YlGnBu: { + type: 'sequential', + cbf: 42, + 3: ['edf8b1', '7fcdbb', '2c7fb8'], + 4: ['ffffcc', 'a1dab4', '41b6c4', '225ea8'], + 5: ['ffffcc', 'a1dab4', '41b6c4', '2c7fb8', '253494'], + 6: ['ffffcc', 'c7e9b4', '7fcdbb', '41b6c4', '2c7fb8', '253494'], + 7: ['ffffcc', 'c7e9b4', '7fcdbb', '41b6c4', '1d91c0', '225ea8', + '0c2c84'], + 8: ['ffffd9', 'edf8b1', 'c7e9b4', '7fcdbb', '41b6c4', '1d91c0', + '225ea8', '0c2c84'], + 9: ['ffffd9', 'edf8b1', 'c7e9b4', '7fcdbb', '41b6c4', '1d91c0', + '225ea8', '253494', '081d58'] + }, + GnBu: { + type: 'sequential', + cbf: 42, + 3: ['e0f3db', 'a8ddb5', '43a2ca'], + 4: ['f0f9e8', 'bae4bc', '7bccc4', '2b8cbe'], + 5: ['f0f9e8', 'bae4bc', '7bccc4', '43a2ca', '0868ac'], + 6: ['f0f9e8', 'ccebc5', 'a8ddb5', '7bccc4', '43a2ca', '0868ac'], + 7: ['f0f9e8', 'ccebc5', 'a8ddb5', '7bccc4', '4eb3d3', '2b8cbe', + '08589e'], + 8: ['f7fcf0', 'e0f3db', 'ccebc5', 'a8ddb5', '7bccc4', '4eb3d3', + '2b8cbe', '08589e'], + 9: ['f7fcf0', 'e0f3db', 'ccebc5', 'a8ddb5', '7bccc4', '4eb3d3', + '2b8cbe', '0868ac', '084081'] + }, + BuGn: { + type: 'sequential', + cbf: 42, + 3: ['e5f5f9', '99d8c9', '2ca25f'], + 4: ['edf8fb', 'b2e2e2', '66c2a4', '238b45'], + 5: ['edf8fb', 'b2e2e2', '66c2a4', '2ca25f', '006d2c'], + 6: ['edf8fb', 'ccece6', '99d8c9', '66c2a4', '2ca25f', '006d2c'], + 7: ['edf8fb', 'ccece6', '99d8c9', '66c2a4', '41ae76', '238b45', + '005824'], + 8: ['f7fcfd', 'e5f5f9', 'ccece6', '99d8c9', '66c2a4', '41ae76', + '238b45', '005824'], + 9: ['f7fcfd', 'e5f5f9', 'ccece6', '99d8c9', '66c2a4', '41ae76', + '238b45', '006d2c', '00441b'] + }, + PuBuGn: { + type: 'sequential', + cbf: 42, + 3: ['ece2f0', 'a6bddb', '1c9099'], + 4: ['f6eff7', 'bdc9e1', '67a9cf', '02818a'], + 5: ['f6eff7', 'bdc9e1', '67a9cf', '1c9099', '016c59'], + 6: ['f6eff7', 'd0d1e6', 'a6bddb', '67a9cf', '1c9099', '016c59'], + 7: ['f6eff7', 'd0d1e6', 'a6bddb', '67a9cf', '3690c0', '02818a', + '016450'], + 8: ['fff7fb', 'ece2f0', 'd0d1e6', 'a6bddb', '67a9cf', '3690c0', + '02818a', '016450'], + 9: ['fff7fb', 'ece2f0', 'd0d1e6', 'a6bddb', '67a9cf', '3690c0', + '02818a', '016c59', '014636'] + }, + PuBu: { + type: 'sequential', + cbf: 42, + 3: ['ece7f2', 'a6bddb', '2b8cbe'], + 4: ['f1eef6', 'bdc9e1', '74a9cf', '0570b0'], + 5: ['f1eef6', 'bdc9e1', '74a9cf', '2b8cbe', '045a8d'], + 6: ['f1eef6', 'd0d1e6', 'a6bddb', '74a9cf', '2b8cbe', '045a8d'], + 7: ['f1eef6', 'd0d1e6', 'a6bddb', '74a9cf', '3690c0', '0570b0', + '034e7b'], + 8: ['fff7fb', 'ece7f2', 'd0d1e6', 'a6bddb', '74a9cf', '3690c0', + '0570b0', '034e7b'], + 9: ['fff7fb', 'ece7f2', 'd0d1e6', 'a6bddb', '74a9cf', '3690c0', + '0570b0', '045a8d', '023858'] + }, + BuPu: { + type: 'sequential', + cbf: 42, + 3: ['e0ecf4', '9ebcda', '8856a7'], + 4: ['edf8fb', 'b3cde3', '8c96c6', '88419d'], + 5: ['edf8fb', 'b3cde3', '8c96c6', '8856a7', '810f7c'], + 6: ['edf8fb', 'bfd3e6', '9ebcda', '8c96c6', '8856a7', '810f7c'], + 7: ['edf8fb', 'bfd3e6', '9ebcda', '8c96c6', '8c6bb1', '88419d', + '6e016b'], + 8: ['f7fcfd', 'e0ecf4', 'bfd3e6', '9ebcda', '8c96c6', '8c6bb1', + '88419d', '6e016b'], + 9: ['f7fcfd', 'e0ecf4', 'bfd3e6', '9ebcda', '8c96c6', '8c6bb1', + '88419d', '810f7c', '4d004b'] + }, + RdPu: { + type: 'sequential', + cbf: 42, + 3: ['fde0dd', 'fa9fb5', 'c51b8a'], + 4: ['feebe2', 'fbb4b9', 'f768a1', 'ae017e'], + 5: ['feebe2', 'fbb4b9', 'f768a1', 'c51b8a', '7a0177'], + 6: ['feebe2', 'fcc5c0', 'fa9fb5', 'f768a1', 'c51b8a', '7a0177'], + 7: ['feebe2', 'fcc5c0', 'fa9fb5', 'f768a1', 'dd3497', 'ae017e', + '7a0177'], + 8: ['fff7f3', 'fde0dd', 'fcc5c0', 'fa9fb5', 'f768a1', 'dd3497', + 'ae017e', '7a0177'], + 9: ['fff7f3', 'fde0dd', 'fcc5c0', 'fa9fb5', 'f768a1', 'dd3497', + 'ae017e', '7a0177', '49006a'] + }, + PuRd: { + type: 'sequential', + cbf: 42, + 3: ['e7e1ef', 'c994c7', 'dd1c77'], + 4: ['f1eef6', 'd7b5d8', 'df65b0', 'ce1256'], + 5: ['f1eef6', 'd7b5d8', 'df65b0', 'dd1c77', '980043'], + 6: ['f1eef6', 'd4b9da', 'c994c7', 'df65b0', 'dd1c77', '980043'], + 7: ['f1eef6', 'd4b9da', 'c994c7', 'df65b0', 'e7298a', 'ce1256', + '91003f'], + 8: ['f7f4f9', 'e7e1ef', 'd4b9da', 'c994c7', 'df65b0', 'e7298a', + 'ce1256', '91003f'], + 9: ['f7f4f9', 'e7e1ef', 'd4b9da', 'c994c7', 'df65b0', 'e7298a', + 'ce1256', '980043', '67001f'] + }, + OrRd: { + type: 'sequential', + cbf: 42, + 3: ['fee8c8', 'fdbb84', 'e34a33'], + 4: ['fef0d9', 'fdcc8a', 'fc8d59', 'd7301f'], + 5: ['fef0d9', 'fdcc8a', 'fc8d59', 'e34a33', 'b30000'], + 6: ['fef0d9', 'fdd49e', 'fdbb84', 'fc8d59', 'e34a33', 'b30000'], + 7: ['fef0d9', 'fdd49e', 'fdbb84', 'fc8d59', 'ef6548', 'd7301f', + '990000'], + 8: ['fff7ec', 'fee8c8', 'fdd49e', 'fdbb84', 'fc8d59', 'ef6548', + 'd7301f', '990000'], + 9: ['fff7ec', 'fee8c8', 'fdd49e', 'fdbb84', 'fc8d59', 'ef6548', + 'd7301f', 'b30000', '7f0000'] + }, + YlOrRd: { + type: 'sequential', + cbf: 42, + 3: ['ffeda0', 'feb24c', 'f03b20'], + 4: ['ffffb2', 'fecc5c', 'fd8d3c', 'e31a1c'], + 5: ['ffffb2', 'fecc5c', 'fd8d3c', 'f03b20', 'bd0026'], + 6: ['ffffb2', 'fed976', 'feb24c', 'fd8d3c', 'f03b20', 'bd0026'], + 7: ['ffffb2', 'fed976', 'feb24c', 'fd8d3c', 'fc4e2a', 'e31a1c', + 'b10026'], + 8: ['ffffcc', 'ffeda0', 'fed976', 'feb24c', 'fd8d3c', 'fc4e2a', + 'e31a1c', 'b10026'], + 9: ['ffffcc', 'ffeda0', 'fed976', 'feb24c', 'fd8d3c', 'fc4e2a', + 'e31a1c', 'bd0026', '800026'] + }, + YlOrBr: { + type: 'sequential', + cbf: 42, + 3: ['fff7bc', 'fec44f', 'd95f0e'], + 4: ['ffffd4', 'fed98e', 'fe9929', 'cc4c02'], + 5: ['ffffd4', 'fed98e', 'fe9929', 'd95f0e', '993404'], + 6: ['ffffd4', 'fee391', 'fec44f', 'fe9929', 'd95f0e', '993404'], + 7: ['ffffd4', 'fee391', 'fec44f', 'fe9929', 'ec7014', 'cc4c02', + '8c2d04'], + 8: ['ffffe5', 'fff7bc', 'fee391', 'fec44f', 'fe9929', 'ec7014', + 'cc4c02', '8c2d04'], + 9: ['ffffe5', 'fff7bc', 'fee391', 'fec44f', 'fe9929', 'ec7014', + 'cc4c02', '993404', '662506'] + }, + Purples: { + type: 'sequential', + cbf: 42, + 3: ['efedf5', 'bcbddc', '756bb1'], + 4: ['f2f0f7', 'cbc9e2', '9e9ac8', '6a51a3'], + 5: ['f2f0f7', 'cbc9e2', '9e9ac8', '756bb1', '54278f'], + 6: ['f2f0f7', 'dadaeb', 'bcbddc', '9e9ac8', '756bb1', '54278f'], + 7: ['f2f0f7', 'dadaeb', 'bcbddc', '9e9ac8', '807dba', '6a51a3', + '4a1486'], + 8: ['fcfbfd', 'efedf5', 'dadaeb', 'bcbddc', '9e9ac8', '807dba', + '6a51a3', '4a1486'], + 9: ['fcfbfd', 'efedf5', 'dadaeb', 'bcbddc', '9e9ac8', '807dba', + '6a51a3', '54278f', '3f007d'] + }, + Blues: { + type: 'sequential', + cbf: 42, + 3: ['deebf7', '9ecae1', '3182bd'], + 4: ['eff3ff', 'bdd7e7', '6baed6', '2171b5'], + 5: ['eff3ff', 'bdd7e7', '6baed6', '3182bd', '08519c'], + 6: ['eff3ff', 'c6dbef', '9ecae1', '6baed6', '3182bd', '08519c'], + 7: ['eff3ff', 'c6dbef', '9ecae1', '6baed6', '4292c6', '2171b5', + '084594'], + 8: ['f7fbff', 'deebf7', 'c6dbef', '9ecae1', '6baed6', '4292c6', + '2171b5', '084594'], + 9: ['f7fbff', 'deebf7', 'c6dbef', '9ecae1', '6baed6', '4292c6', + '2171b5', '08519c', '08306b'] + }, + Greens: { + type: 'sequential', + cbf: 42, + 3: ['e5f5e0', 'a1d99b', '31a354'], + 4: ['edf8e9', 'bae4b3', '74c476', '238b45'], + 5: ['edf8e9', 'bae4b3', '74c476', '31a354', '006d2c'], + 6: ['edf8e9', 'c7e9c0', 'a1d99b', '74c476', '31a354', '006d2c'], + 7: ['edf8e9', 'c7e9c0', 'a1d99b', '74c476', '41ab5d', '238b45', + '005a32'], + 8: ['f7fcf5', 'e5f5e0', 'c7e9c0', 'a1d99b', '74c476', '41ab5d', + '238b45', '005a32'], + 9: ['f7fcf5', 'e5f5e0', 'c7e9c0', 'a1d99b', '74c476', '41ab5d', + '238b45', '006d2c', '00441b'] + }, + Oranges: { + type: 'sequential', + cbf: 42, + 3: ['fee6ce', 'fdae6b', 'e6550d'], + 4: ['feedde', 'fdbe85', 'fd8d3c', 'd94701'], + 5: ['feedde', 'fdbe85', 'fd8d3c', 'e6550d', 'a63603'], + 6: ['feedde', 'fdd0a2', 'fdae6b', 'fd8d3c', 'e6550d', 'a63603'], + 7: ['feedde', 'fdd0a2', 'fdae6b', 'fd8d3c', 'f16913', 'd94801', + '8c2d04'], + 8: ['fff5eb', 'fee6ce', 'fdd0a2', 'fdae6b', 'fd8d3c', 'f16913', + 'd94801', '8c2d04'], + 9: ['fff5eb', 'fee6ce', 'fdd0a2', 'fdae6b', 'fd8d3c', 'f16913', + 'd94801', 'a63603', '7f2704'] + }, + Reds: { + type: 'sequential', + cbf: 42, + 3: ['fee0d2', 'fc9272', 'de2d26'], + 4: ['fee5d9', 'fcae91', 'fb6a4a', 'cb181d'], + 5: ['fee5d9', 'fcae91', 'fb6a4a', 'de2d26', 'a50f15'], + 6: ['fee5d9', 'fcbba1', 'fc9272', 'fb6a4a', 'de2d26', 'a50f15'], + 7: ['fee5d9', 'fcbba1', 'fc9272', 'fb6a4a', 'ef3b2c', 'cb181d', + '99000d'], + 8: ['fff5f0', 'fee0d2', 'fcbba1', 'fc9272', 'fb6a4a', 'ef3b2c', + 'cb181d', '99000d'], + 9: ['fff5f0', 'fee0d2', 'fcbba1', 'fc9272', 'fb6a4a', 'ef3b2c', + 'cb181d', 'a50f15', '67000d'] + }, + Greys: { + type: 'sequential', + cbf: 42, + 3: ['f0f0f0', 'bdbdbd', '636363'], + 4: ['f7f7f7', 'cccccc', '969696', '525252'], + 5: ['f7f7f7', 'cccccc', '969696', '636363', '252525'], + 6: ['f7f7f7', 'd9d9d9', 'bdbdbd', '969696', '636363', '252525'], + 7: ['f7f7f7', 'd9d9d9', 'bdbdbd', '969696', '737373', '525252', + '252525'], + 8: ['ffffff', 'f0f0f0', 'd9d9d9', 'bdbdbd', '969696', '737373', + '525252', '252525'], + 9: ['ffffff', 'f0f0f0', 'd9d9d9', 'bdbdbd', '969696', '737373', + '525252', '252525', '000000'] + }, + PuOr: { + type: 'diverging', + cbf: 42, + 3: ['f1a340', 'f7f7f7', '998ec3'], + 4: ['e66101', 'fdb863', 'b2abd2', '5e3c99'], + 5: ['e66101', 'fdb863', 'f7f7f7', 'b2abd2', '5e3c99'], + 6: ['b35806', 'f1a340', 'fee0b6', 'd8daeb', '998ec3', '542788'], + 7: ['b35806', 'f1a340', 'fee0b6', 'f7f7f7', 'd8daeb', '998ec3', + '542788'], + 8: ['b35806', 'e08214', 'fdb863', 'fee0b6', 'd8daeb', 'b2abd2', + '8073ac', '542788'], + 9: ['b35806', 'e08214', 'fdb863', 'fee0b6', 'f7f7f7', 'd8daeb', + 'b2abd2', '8073ac', '542788'], + 10: ['7f3b08', 'b35806', 'e08214', 'fdb863', 'fee0b6', 'd8daeb', + 'b2abd2', '8073ac', '542788', '2d004b'], + 11: ['7f3b08', 'b35806', 'e08214', 'fdb863', 'fee0b6', 'f7f7f7', + 'd8daeb', 'b2abd2', '8073ac', '542788', '2d004b'] + }, + BrBG: { + type: 'diverging', + cbf: 42, + 3: ['d8b365', 'f5f5f5', '5ab4ac'], + 4: ['a6611a', 'dfc27d', '80cdc1', '018571'], + 5: ['a6611a', 'dfc27d', 'f5f5f5', '80cdc1', '018571'], + 6: ['8c510a', 'd8b365', 'f6e8c3', 'c7eae5', '5ab4ac', '01665e'], + 7: ['8c510a', 'd8b365', 'f6e8c3', 'f5f5f5', 'c7eae5', '5ab4ac', + '01665e'], + 8: ['8c510a', 'bf812d', 'dfc27d', 'f6e8c3', 'c7eae5', '80cdc1', + '35978f', '01665e'], + 9: ['8c510a', 'bf812d', 'dfc27d', 'f6e8c3', 'f5f5f5', 'c7eae5', + '80cdc1', '35978f', '01665e'], + 10: ['543005', '8c510a', 'bf812d', 'dfc27d', 'f6e8c3', 'c7eae5', + '80cdc1', '35978f', '01665e', '003c30'], + 11: ['543005', '8c510a', 'bf812d', 'dfc27d', 'f6e8c3', 'f5f5f5', + 'c7eae5', '80cdc1', '35978f', '01665e', '003c30'] + }, + PRGn: { + type: 'diverging', + cbf: 42, + 3: ['af8dc3', 'f7f7f7', '7fbf7b'], + 4: ['7b3294', 'c2a5cf', 'a6dba0', '008837'], + 5: ['7b3294', 'c2a5cf', 'f7f7f7', 'a6dba0', '008837'], + 6: ['762a83', 'af8dc3', 'e7d4e8', 'd9f0d3', '7fbf7b', '1b7837'], + 7: ['762a83', 'af8dc3', 'e7d4e8', 'f7f7f7', 'd9f0d3', '7fbf7b', + '1b7837'], + 8: ['762a83', '9970ab', 'c2a5cf', 'e7d4e8', 'd9f0d3', 'a6dba0', + '5aae61', '1b7837'], + 9: ['762a83', '9970ab', 'c2a5cf', 'e7d4e8', 'f7f7f7', 'd9f0d3', + 'a6dba0', '5aae61', '1b7837'], + 10: ['40004b', '762a83', '9970ab', 'c2a5cf', 'e7d4e8', 'd9f0d3', + 'a6dba0', '5aae61', '1b7837', '00441b'], + 11: ['40004b', '762a83', '9970ab', 'c2a5cf', 'e7d4e8', 'f7f7f7', + 'd9f0d3', 'a6dba0', '5aae61', '1b7837', '00441b'] + }, + PiYG: { + type: 'diverging', + cbf: 42, + 3: ['e9a3c9', 'f7f7f7', 'a1d76a'], + 4: ['d01c8b', 'f1b6da', 'b8e186', '4dac26'], + 5: ['d01c8b', 'f1b6da', 'f7f7f7', 'b8e186', '4dac26'], + 6: ['c51b7d', 'e9a3c9', 'fde0ef', 'e6f5d0', 'a1d76a', '4d9221'], + 7: ['c51b7d', 'e9a3c9', 'fde0ef', 'f7f7f7', 'e6f5d0', 'a1d76a', + '4d9221'], + 8: ['c51b7d', 'de77ae', 'f1b6da', 'fde0ef', 'e6f5d0', 'b8e186', + '7fbc41', '4d9221'], + 9: ['c51b7d', 'de77ae', 'f1b6da', 'fde0ef', 'f7f7f7', 'e6f5d0', + 'b8e186', '7fbc41', '4d9221'], + 10: ['8e0152', 'c51b7d', 'de77ae', 'f1b6da', 'fde0ef', 'e6f5d0', + 'b8e186', '7fbc41', '4d9221', '276419'], + 11: ['8e0152', 'c51b7d', 'de77ae', 'f1b6da', 'fde0ef', 'f7f7f7', + 'e6f5d0', 'b8e186', '7fbc41', '4d9221', '276419'] + }, + RdBu: { + type: 'diverging', + cbf: 42, + 3: ['ef8a62', 'f7f7f7', '67a9cf'], + 4: ['ca0020', 'f4a582', '92c5de', '0571b0'], + 5: ['ca0020', 'f4a582', 'f7f7f7', '92c5de', '0571b0'], + 6: ['b2182b', 'ef8a62', 'fddbc7', 'd1e5f0', '67a9cf', '2166ac'], + 7: ['b2182b', 'ef8a62', 'fddbc7', 'f7f7f7', 'd1e5f0', '67a9cf', + '2166ac'], + 8: ['b2182b', 'd6604d', 'f4a582', 'fddbc7', 'd1e5f0', '92c5de', + '4393c3', '2166ac'], + 9: ['b2182b', 'd6604d', 'f4a582', 'fddbc7', 'f7f7f7', 'd1e5f0', + '92c5de', '4393c3', '2166ac'], + 10: ['67001f', 'b2182b', 'd6604d', 'f4a582', 'fddbc7', 'd1e5f0', + '92c5de', '4393c3', '2166ac', '053061'], + 11: ['67001f', 'b2182b', 'd6604d', 'f4a582', 'fddbc7', 'f7f7f7', + 'd1e5f0', '92c5de', '4393c3', '2166ac', '053061'] + }, + RdGy: { + type: 'diverging', + cbf: 42, + 3: ['ef8a62', 'ffffff', '999999'], + 4: ['ca0020', 'f4a582', 'bababa', '404040'], + 5: ['ca0020', 'f4a582', 'ffffff', 'bababa', '404040'], + 6: ['b2182b', 'ef8a62', 'fddbc7', 'e0e0e0', '999999', '4d4d4d'], + 7: ['b2182b', 'ef8a62', 'fddbc7', 'ffffff', 'e0e0e0', '999999', + '4d4d4d'], + 8: ['b2182b', 'd6604d', 'f4a582', 'fddbc7', 'e0e0e0', 'bababa', + '878787', '4d4d4d'], + 9: ['b2182b', 'd6604d', 'f4a582', 'fddbc7', 'ffffff', 'e0e0e0', + 'bababa', '878787', '4d4d4d'], + 10: ['67001f', 'b2182b', 'd6604d', 'f4a582', 'fddbc7', 'e0e0e0', + 'bababa', '878787', '4d4d4d', '1a1a1a'], + 11: ['67001f', 'b2182b', 'd6604d', 'f4a582', 'fddbc7', 'ffffff', + 'e0e0e0', 'bababa', '878787', '4d4d4d', '1a1a1a'] + }, + RdYlBu: { + type: 'diverging', + cbf: 42, + 3: ['fc8d59', 'ffffbf', '91bfdb'], + 4: ['d7191c', 'fdae61', 'abd9e9', '2c7bb6'], + 5: ['d7191c', 'fdae61', 'ffffbf', 'abd9e9', '2c7bb6'], + 6: ['d73027', 'fc8d59', 'fee090', 'e0f3f8', '91bfdb', '4575b4'], + 7: ['d73027', 'fc8d59', 'fee090', 'ffffbf', 'e0f3f8', '91bfdb', + '4575b4'], + 8: ['d73027', 'f46d43', 'fdae61', 'fee090', 'e0f3f8', 'abd9e9', + '74add1', '4575b4'], + 9: ['d73027', 'f46d43', 'fdae61', 'fee090', 'ffffbf', 'e0f3f8', + 'abd9e9', '74add1', '4575b4'], + 10: ['a50026', 'd73027', 'f46d43', 'fdae61', 'fee090', 'e0f3f8', + 'abd9e9', '74add1', '4575b4', '313695'], + 11: ['a50026', 'd73027', 'f46d43', 'fdae61', 'fee090', 'ffffbf', + 'e0f3f8', 'abd9e9', '74add1', '4575b4', '313695'] + }, + Spectral: { + type: 'diverging', + cbf: 0, + 3: ['fc8d59', 'ffffbf', '99d594'], + 4: ['d7191c', 'fdae61', 'abdda4', '2b83ba'], + 5: ['d7191c', 'fdae61', 'ffffbf', 'abdda4', '2b83ba'], + 6: ['d53e4f', 'fc8d59', 'fee08b', 'e6f598', '99d594', '3288bd'], + 7: ['d53e4f', 'fc8d59', 'fee08b', 'ffffbf', 'e6f598', '99d594', + '3288bd'], + 8: ['d53e4f', 'f46d43', 'fdae61', 'fee08b', 'e6f598', 'abdda4', + '66c2a5', '3288bd'], + 9: ['d53e4f', 'f46d43', 'fdae61', 'fee08b', 'ffffbf', 'e6f598', + 'abdda4', '66c2a5', '3288bd'], + 10: ['9e0142', 'd53e4f', 'f46d43', 'fdae61', 'fee08b', 'e6f598', + 'abdda4', '66c2a5', '3288bd', '5e4fa2'], + 11: ['9e0142', 'd53e4f', 'f46d43', 'fdae61', 'fee08b', 'ffffbf', + 'e6f598', 'abdda4', '66c2a5', '3288bd', '5e4fa2'] + }, + RdYlGn: { + type: 'diverging', + cbf: 0, + 3: ['fc8d59', 'ffffbf', '91cf60'], + 4: ['d7191c', 'fdae61', 'a6d96a', '1a9641'], + 5: ['d7191c', 'fdae61', 'ffffbf', 'a6d96a', '1a9641'], + 6: ['d73027', 'fc8d59', 'fee08b', 'd9ef8b', '91cf60', '1a9850'], + 7: ['d73027', 'fc8d59', 'fee08b', 'ffffbf', 'd9ef8b', '91cf60', + '1a9850'], + 8: ['d73027', 'f46d43', 'fdae61', 'fee08b', 'd9ef8b', 'a6d96a', + '66bd63', '1a9850'], + 9: ['d73027', 'f46d43', 'fdae61', 'fee08b', 'ffffbf', 'd9ef8b', + 'a6d96a', '66bd63', '1a9850'], + 10: ['a50026', 'd73027', 'f46d43', 'fdae61', 'fee08b', 'd9ef8b', + 'a6d96a', '66bd63', '1a9850', '006837'], + 11: ['a50026', 'd73027', 'f46d43', 'fdae61', 'fee08b', 'ffffbf', + 'd9ef8b', 'a6d96a', '66bd63', '1a9850', '006837'] + }, + Accent: { + type: 'qualitative', + cbf: 0, + 3: ['7fc97f', 'beaed4', 'fdc086'], + 4: ['7fc97f', 'beaed4', 'fdc086', 'ffff99'], + 5: ['7fc97f', 'beaed4', 'fdc086', 'ffff99', '386cb0'], + 6: ['7fc97f', 'beaed4', 'fdc086', 'ffff99', '386cb0', 'f0027f'], + 7: ['7fc97f', 'beaed4', 'fdc086', 'ffff99', '386cb0', 'f0027f', + 'bf5b17'], + 8: ['7fc97f', 'beaed4', 'fdc086', 'ffff99', '386cb0', 'f0027f', + 'bf5b17', '666666'] + }, + Dark2: { + type: 'qualitative', + cbf: 3, + 3: ['1b9e77', 'd95f02', '7570b3'], + 4: ['1b9e77', 'd95f02', '7570b3', 'e7298a'], + 5: ['1b9e77', 'd95f02', '7570b3', 'e7298a', '66a61e'], + 6: ['1b9e77', 'd95f02', '7570b3', 'e7298a', '66a61e', 'e6ab02'], + 7: ['1b9e77', 'd95f02', '7570b3', 'e7298a', '66a61e', 'e6ab02', + 'a6761d'], + 8: ['1b9e77', 'd95f02', '7570b3', 'e7298a', '66a61e', 'e6ab02', + 'a6761d', '666666'] + }, + Paired: { + type: 'qualitative', + cbf: 4, + 3: ['a6cee3', '1f78b4', 'b2df8a'], + 4: ['a6cee3', '1f78b4', 'b2df8a', '33a02c'], + 5: ['a6cee3', '1f78b4', 'b2df8a', '33a02c', 'fb9a99'], + 6: ['a6cee3', '1f78b4', 'b2df8a', '33a02c', 'fb9a99', 'e31a1c'], + 7: ['a6cee3', '1f78b4', 'b2df8a', '33a02c', 'fb9a99', 'e31a1c', + 'fdbf6f'], + 8: ['a6cee3', '1f78b4', 'b2df8a', '33a02c', 'fb9a99', 'e31a1c', + 'fdbf6f', 'ff7f00'], + 9: ['a6cee3', '1f78b4', 'b2df8a', '33a02c', 'fb9a99', 'e31a1c', + 'fdbf6f', 'ff7f00', 'cab2d6'], + 10: ['a6cee3', '1f78b4', 'b2df8a', '33a02c', 'fb9a99', 'e31a1c', + 'fdbf6f', 'ff7f00', 'cab2d6', '6a3d9a'], + 11: ['a6cee3', '1f78b4', 'b2df8a', '33a02c', 'fb9a99', 'e31a1c', + 'fdbf6f', 'ff7f00', 'cab2d6', '6a3d9a', 'ffff99'], + 12: ['a6cee3', '1f78b4', 'b2df8a', '33a02c', 'fb9a99', 'e31a1c', + 'fdbf6f', 'ff7f00', 'cab2d6', '6a3d9a', 'ffff99', 'b15928'] + }, + Pastel1: { + type: 'qualitative', + cbf: 0, + 3: ['fbb4ae', 'b3cde3', 'ccebc5'], + 4: ['fbb4ae', 'b3cde3', 'ccebc5', 'decbe4'], + 5: ['fbb4ae', 'b3cde3', 'ccebc5', 'decbe4', 'fed9a6'], + 6: ['fbb4ae', 'b3cde3', 'ccebc5', 'decbe4', 'fed9a6', 'ffffcc'], + 7: ['fbb4ae', 'b3cde3', 'ccebc5', 'decbe4', 'fed9a6', 'ffffcc', + 'e5d8bd'], + 8: ['fbb4ae', 'b3cde3', 'ccebc5', 'decbe4', 'fed9a6', 'ffffcc', + 'e5d8bd', 'fddaec'], + 9: ['fbb4ae', 'b3cde3', 'ccebc5', 'decbe4', 'fed9a6', 'ffffcc', + 'e5d8bd', 'fddaec', 'f2f2f2'] + }, + Pastel2: { + type: 'qualitative', + cbf: 0, + 3: ['b3e2cd', 'fdcdac', 'cbd5e8'], + 4: ['b3e2cd', 'fdcdac', 'cbd5e8', 'f4cae4'], + 5: ['b3e2cd', 'fdcdac', 'cbd5e8', 'f4cae4', 'e6f5c9'], + 6: ['b3e2cd', 'fdcdac', 'cbd5e8', 'f4cae4', 'e6f5c9', 'fff2ae'], + 7: ['b3e2cd', 'fdcdac', 'cbd5e8', 'f4cae4', 'e6f5c9', 'fff2ae', + 'f1e2cc'], + 8: ['b3e2cd', 'fdcdac', 'cbd5e8', 'f4cae4', 'e6f5c9', 'fff2ae', + 'f1e2cc', 'cccccc'] + }, + Set1: { + type: 'qualitative', + cbf: 0, + 3: ['e41a1c', '377eb8', '4daf4a'], + 4: ['e41a1c', '377eb8', '4daf4a', '984ea3'], + 5: ['e41a1c', '377eb8', '4daf4a', '984ea3', 'ff7f00'], + 6: ['e41a1c', '377eb8', '4daf4a', '984ea3', 'ff7f00', 'ffff33'], + 7: ['e41a1c', '377eb8', '4daf4a', '984ea3', 'ff7f00', 'ffff33', + 'a65628'], + 8: ['e41a1c', '377eb8', '4daf4a', '984ea3', 'ff7f00', 'ffff33', + 'a65628', 'f781bf'], + 9: ['e41a1c', '377eb8', '4daf4a', '984ea3', 'ff7f00', 'ffff33', + 'a65628', 'f781bf', '999999'] + }, + Set2: { + type: 'qualitative', + cbf: 3, + 3: ['66c2a5', 'fc8d62', '8da0cb'], + 4: ['66c2a5', 'fc8d62', '8da0cb', 'e78ac3'], + 5: ['66c2a5', 'fc8d62', '8da0cb', 'e78ac3', 'a6d854'], + 6: ['66c2a5', 'fc8d62', '8da0cb', 'e78ac3', 'a6d854', 'ffd92f'], + 7: ['66c2a5', 'fc8d62', '8da0cb', 'e78ac3', 'a6d854', 'ffd92f', + 'e5c494'], + 8: ['66c2a5', 'fc8d62', '8da0cb', 'e78ac3', 'a6d854', 'ffd92f', + 'e5c494', 'b3b3b3'] + }, + Set3: { + type: 'qualitative', + cbf: 0, + 3: ['8dd3c7', 'ffffb3', 'bebada'], + 4: ['8dd3c7', 'ffffb3', 'bebada', 'fb8072'], + 5: ['8dd3c7', 'ffffb3', 'bebada', 'fb8072', '80b1d3'], + 6: ['8dd3c7', 'ffffb3', 'bebada', 'fb8072', '80b1d3', 'fdb462'], + 7: ['8dd3c7', 'ffffb3', 'bebada', 'fb8072', '80b1d3', 'fdb462', + 'b3de69'], + 8: ['8dd3c7', 'ffffb3', 'bebada', 'fb8072', '80b1d3', 'fdb462', + 'b3de69', 'fccde5'], + 9: ['8dd3c7', 'ffffb3', 'bebada', 'fb8072', '80b1d3', 'fdb462', + 'b3de69', 'fccde5', 'd9d9d9'], + 10: ['8dd3c7', 'ffffb3', 'bebada', 'fb8072', '80b1d3', 'fdb462', + 'b3de69', 'fccde5', 'd9d9d9', 'bc80bd'], + 11: ['8dd3c7', 'ffffb3', 'bebada', 'fb8072', '80b1d3', 'fdb462', + 'b3de69', 'fccde5', 'd9d9d9', 'bc80bd', 'ccebc5'], + 12: ['8dd3c7', 'ffffb3', 'bebada', 'fb8072', '80b1d3', 'fdb462', + 'b3de69', 'fccde5', 'd9d9d9', 'bc80bd', 'ccebc5', 'ffed6f'] + } + }; + + for (var name in schemes) { + var scheme = schemes[name]; + scheme = palette.Scheme.fromPalettes( + 'cb-' + name, [scheme.type, 'cb-' + scheme.type], scheme, 12, scheme.cbf); + palette.register(scheme); + } +})(); + +if(typeof module === "object" && module.exports) { + module.exports = palette +} diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/js/sweetalert2.all.min.js b/ext/phpbbstudio/aps/styles/prosilver/template/js/sweetalert2.all.min.js new file mode 100644 index 0000000..c31d3aa --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/js/sweetalert2.all.min.js @@ -0,0 +1,2 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.Sweetalert2=t()}(this,function(){"use strict";function q(e){return(q="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function a(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){for(var n=0;n\n
    \n
      \n
      \n \n
      \n
      \n ?\n
      \n
      \n !\n
      \n
      \n i\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').replace(/(^|\n)\s*/g,""),X=function(e){var t=w();if(t&&(t.parentNode.removeChild(t),W([document.documentElement,document.body],[_["no-backdrop"],_["toast-shown"],_["has-column"]])),!j()){var n=document.createElement("div");n.className=_.container,n.innerHTML=V;var o="string"==typeof e.target?document.querySelector(e.target):e.target;o.appendChild(n);var i,r=k(),a=B(),s=U(a,_.input),c=U(a,_.file),u=a.querySelector(".".concat(_.range," input")),l=a.querySelector(".".concat(_.range," output")),d=U(a,_.select),p=a.querySelector(".".concat(_.checkbox," input")),f=U(a,_.textarea);r.setAttribute("role",e.toast?"alert":"dialog"),r.setAttribute("aria-live",e.toast?"polite":"assertive"),e.toast||r.setAttribute("aria-modal","true"),"rtl"===window.getComputedStyle(o).direction&&z(w(),_.rtl);var m=function(e){De.isVisible()&&i!==e.target.value&&De.resetValidationMessage(),i=e.target.value};return s.oninput=m,c.onchange=m,d.onchange=m,p.onchange=m,f.oninput=m,u.oninput=function(e){m(e),l.value=u.value},u.onchange=function(e){m(e),u.nextSibling.value=u.value},r}I("SweetAlert2 requires document to initialize")},G=function(e,t){if(!e)return F(t);if(e instanceof HTMLElement)t.appendChild(e);else if("object"===q(e))if(t.innerHTML="",0 in e)for(var n=0;n in e;n++)t.appendChild(e[n].cloneNode(!0));else t.appendChild(e.cloneNode(!0));else e&&(t.innerHTML=e);K(t)},ee=function(){if(j())return!1;var e=document.createElement("div"),t={WebkitAnimation:"webkitAnimationEnd",OAnimation:"oAnimationEnd oanimationend",animation:"animationend"};for(var n in t)if(t.hasOwnProperty(n)&&void 0!==e.style[n])return t[n];return!1}(),te=function(e){var t=Q(),n=L(),o=O();if(e.showConfirmButton||e.showCancelButton?K(t):F(t),e.showCancelButton?o.style.display="inline-block":F(o),e.showConfirmButton?n.style.removeProperty("display"):F(n),n.innerHTML=e.confirmButtonText,o.innerHTML=e.cancelButtonText,n.setAttribute("aria-label",e.confirmButtonAriaLabel),o.setAttribute("aria-label",e.cancelButtonAriaLabel),n.className=_.confirm,z(n,e.confirmButtonClass),o.className=_.cancel,z(o,e.cancelButtonClass),e.buttonsStyling){z([n,o],_.styled),e.confirmButtonColor&&(n.style.backgroundColor=e.confirmButtonColor),e.cancelButtonColor&&(o.style.backgroundColor=e.cancelButtonColor);var i=window.getComputedStyle(n).getPropertyValue("background-color");n.style.borderLeftColor=i,n.style.borderRightColor=i}else W([n,o],_.styled),n.style.backgroundColor=n.style.borderLeftColor=n.style.borderRightColor="",o.style.backgroundColor=o.style.borderLeftColor=o.style.borderRightColor=""},ne=function(e){var t=B().querySelector("#"+_.content);e.html?G(e.html,t):e.text?(t.textContent=e.text,K(t)):F(t)},oe=function(e){for(var t=x(),n=0;n=i.progressSteps.length&&R("Invalid currentProgressStep parameter, it should be less than progressSteps.length (currentProgressStep like JS arrays starts from 0)"),i.progressSteps.forEach(function(e,t){var n=document.createElement("li");if(z(n,_.progresscircle),n.innerHTML=e,t===a&&z(n,_.activeprogressstep),r.appendChild(n),t!==i.progressSteps.length-1){var o=document.createElement("li");z(o,_.progressline),i.progressStepsDistance&&(o.style.width=i.progressStepsDistance),r.appendChild(o)}})):F(r)},ae=function(e){var t=A();e.titleText?t.innerText=e.titleText:e.title&&("string"==typeof e.title&&(e.title=e.title.split("\n").join("
      ")),G(e.title,t))},se=function(){null===b.previousBodyPadding&&document.body.scrollHeight>window.innerHeight&&(b.previousBodyPadding=parseInt(window.getComputedStyle(document.body).getPropertyValue("padding-right")),document.body.style.paddingRight=b.previousBodyPadding+function(){if("ontouchstart"in window||navigator.msMaxTouchPoints)return 0;var e=document.createElement("div");e.style.width="50px",e.style.height="50px",e.style.overflow="scroll",document.body.appendChild(e);var t=e.offsetWidth-e.clientWidth;return document.body.removeChild(e),t}()+"px")},ce=function(){return!!window.MSInputMethodContext&&!!document.documentMode},ue=function(){var e=w(),t=k();e.style.removeProperty("align-items"),t.offsetTop<0&&(e.style.alignItems="flex-start")},le={},de=function(e,t){var n=w(),o=k();if(o){null!==e&&"function"==typeof e&&e(o),W(o,_.show),z(o,_.hide);var i=function(){M()?pe(t):(new Promise(function(e){var t=window.scrollX,n=window.scrollY;le.restoreFocusTimeout=setTimeout(function(){le.previousActiveElement&&le.previousActiveElement.focus?(le.previousActiveElement.focus(),le.previousActiveElement=null):document.body&&document.body.focus(),e()},100),void 0!==t&&void 0!==n&&window.scrollTo(t,n)}).then(function(){return pe(t)}),le.keydownTarget.removeEventListener("keydown",le.keydownHandler,{capture:le.keydownListenerCapture}),le.keydownHandlerAdded=!1),n.parentNode&&n.parentNode.removeChild(n),W([document.documentElement,document.body],[_.shown,_["height-auto"],_["no-backdrop"],_["toast-shown"],_["toast-column"]]),T()&&(null!==b.previousBodyPadding&&(document.body.style.paddingRight=b.previousBodyPadding,b.previousBodyPadding=null),function(){if(v(document.body,_.iosfix)){var e=parseInt(document.body.style.top,10);W(document.body,_.iosfix),document.body.style.top="",document.body.scrollTop=-1*e}}(),"undefined"!=typeof window&&ce()&&window.removeEventListener("resize",ue),f(document.body.children).forEach(function(e){e.hasAttribute("data-previous-aria-hidden")?(e.setAttribute("aria-hidden",e.getAttribute("data-previous-aria-hidden")),e.removeAttribute("data-previous-aria-hidden")):e.removeAttribute("aria-hidden")}))};ee&&!v(o,_.noanimation)?o.addEventListener(ee,function e(){o.removeEventListener(ee,e),v(o,_.hide)&&i()}):i()}},pe=function(e){null!==e&&"function"==typeof e&&setTimeout(function(){e()})};function fe(e){var t=function e(){for(var t=arguments.length,n=new Array(t),o=0;o.swal2-modal{box-shadow:0 0 10px rgba(0,0,0,.4)}body.swal2-no-backdrop .swal2-shown.swal2-top{top:0;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}body.swal2-no-backdrop .swal2-shown.swal2-top-left,body.swal2-no-backdrop .swal2-shown.swal2-top-start{top:0;left:0}body.swal2-no-backdrop .swal2-shown.swal2-top-end,body.swal2-no-backdrop .swal2-shown.swal2-top-right{top:0;right:0}body.swal2-no-backdrop .swal2-shown.swal2-center{top:50%;left:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%)}body.swal2-no-backdrop .swal2-shown.swal2-center-left,body.swal2-no-backdrop .swal2-shown.swal2-center-start{top:50%;left:0;-webkit-transform:translateY(-50%);transform:translateY(-50%)}body.swal2-no-backdrop .swal2-shown.swal2-center-end,body.swal2-no-backdrop .swal2-shown.swal2-center-right{top:50%;right:0;-webkit-transform:translateY(-50%);transform:translateY(-50%)}body.swal2-no-backdrop .swal2-shown.swal2-bottom{bottom:0;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}body.swal2-no-backdrop .swal2-shown.swal2-bottom-left,body.swal2-no-backdrop .swal2-shown.swal2-bottom-start{bottom:0;left:0}body.swal2-no-backdrop .swal2-shown.swal2-bottom-end,body.swal2-no-backdrop .swal2-shown.swal2-bottom-right{right:0;bottom:0}.swal2-container{display:flex;position:fixed;top:0;right:0;bottom:0;left:0;flex-direction:row;align-items:center;justify-content:center;padding:10px;background-color:transparent;z-index:1060;overflow-x:hidden;-webkit-overflow-scrolling:touch}.swal2-container.swal2-top{align-items:flex-start}.swal2-container.swal2-top-left,.swal2-container.swal2-top-start{align-items:flex-start;justify-content:flex-start}.swal2-container.swal2-top-end,.swal2-container.swal2-top-right{align-items:flex-start;justify-content:flex-end}.swal2-container.swal2-center{align-items:center}.swal2-container.swal2-center-left,.swal2-container.swal2-center-start{align-items:center;justify-content:flex-start}.swal2-container.swal2-center-end,.swal2-container.swal2-center-right{align-items:center;justify-content:flex-end}.swal2-container.swal2-bottom{align-items:flex-end}.swal2-container.swal2-bottom-left,.swal2-container.swal2-bottom-start{align-items:flex-end;justify-content:flex-start}.swal2-container.swal2-bottom-end,.swal2-container.swal2-bottom-right{align-items:flex-end;justify-content:flex-end}.swal2-container.swal2-grow-fullscreen>.swal2-modal{display:flex!important;flex:1;align-self:stretch;justify-content:center}.swal2-container.swal2-grow-row>.swal2-modal{display:flex!important;flex:1;align-content:center;justify-content:center}.swal2-container.swal2-grow-column{flex:1;flex-direction:column}.swal2-container.swal2-grow-column.swal2-bottom,.swal2-container.swal2-grow-column.swal2-center,.swal2-container.swal2-grow-column.swal2-top{align-items:center}.swal2-container.swal2-grow-column.swal2-bottom-left,.swal2-container.swal2-grow-column.swal2-bottom-start,.swal2-container.swal2-grow-column.swal2-center-left,.swal2-container.swal2-grow-column.swal2-center-start,.swal2-container.swal2-grow-column.swal2-top-left,.swal2-container.swal2-grow-column.swal2-top-start{align-items:flex-start}.swal2-container.swal2-grow-column.swal2-bottom-end,.swal2-container.swal2-grow-column.swal2-bottom-right,.swal2-container.swal2-grow-column.swal2-center-end,.swal2-container.swal2-grow-column.swal2-center-right,.swal2-container.swal2-grow-column.swal2-top-end,.swal2-container.swal2-grow-column.swal2-top-right{align-items:flex-end}.swal2-container.swal2-grow-column>.swal2-modal{display:flex!important;flex:1;align-content:center;justify-content:center}.swal2-container:not(.swal2-top):not(.swal2-top-start):not(.swal2-top-end):not(.swal2-top-left):not(.swal2-top-right):not(.swal2-center-start):not(.swal2-center-end):not(.swal2-center-left):not(.swal2-center-right):not(.swal2-bottom):not(.swal2-bottom-start):not(.swal2-bottom-end):not(.swal2-bottom-left):not(.swal2-bottom-right):not(.swal2-grow-fullscreen)>.swal2-modal{margin:auto}@media all and (-ms-high-contrast:none),(-ms-high-contrast:active){.swal2-container .swal2-modal{margin:0!important}}.swal2-container.swal2-fade{transition:background-color .1s}.swal2-container.swal2-shown{background-color:rgba(0,0,0,.4)}.swal2-popup{display:none;position:relative;flex-direction:column;justify-content:center;width:32em;max-width:100%;padding:1.25em;border-radius:.3125em;background:#fff;font-family:inherit;font-size:1rem;box-sizing:border-box}.swal2-popup:focus{outline:0}.swal2-popup.swal2-loading{overflow-y:hidden}.swal2-popup .swal2-header{display:flex;flex-direction:column;align-items:center}.swal2-popup .swal2-title{display:block;position:relative;max-width:100%;margin:0 0 .4em;padding:0;color:#595959;font-size:1.875em;font-weight:600;text-align:center;text-transform:none;word-wrap:break-word}.swal2-popup .swal2-actions{flex-wrap:wrap;align-items:center;justify-content:center;margin:1.25em auto 0;z-index:1}.swal2-popup .swal2-actions:not(.swal2-loading) .swal2-styled[disabled]{opacity:.4}.swal2-popup .swal2-actions:not(.swal2-loading) .swal2-styled:hover{background-image:linear-gradient(rgba(0,0,0,.1),rgba(0,0,0,.1))}.swal2-popup .swal2-actions:not(.swal2-loading) .swal2-styled:active{background-image:linear-gradient(rgba(0,0,0,.2),rgba(0,0,0,.2))}.swal2-popup .swal2-actions.swal2-loading .swal2-styled.swal2-confirm{width:2.5em;height:2.5em;margin:.46875em;padding:0;border:.25em solid transparent;border-radius:100%;border-color:transparent;background-color:transparent!important;color:transparent;cursor:default;box-sizing:border-box;-webkit-animation:swal2-rotate-loading 1.5s linear 0s infinite normal;animation:swal2-rotate-loading 1.5s linear 0s infinite normal;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.swal2-popup .swal2-actions.swal2-loading .swal2-styled.swal2-cancel{margin-right:30px;margin-left:30px}.swal2-popup .swal2-actions.swal2-loading :not(.swal2-styled).swal2-confirm::after{display:inline-block;width:15px;height:15px;margin-left:5px;border:3px solid #999;border-radius:50%;border-right-color:transparent;box-shadow:1px 1px 1px #fff;content:'';-webkit-animation:swal2-rotate-loading 1.5s linear 0s infinite normal;animation:swal2-rotate-loading 1.5s linear 0s infinite normal}.swal2-popup .swal2-styled{margin:.3125em;padding:.625em 2em;font-weight:500;box-shadow:none}.swal2-popup .swal2-styled:not([disabled]){cursor:pointer}.swal2-popup .swal2-styled.swal2-confirm{border:0;border-radius:.25em;background:initial;background-color:#3085d6;color:#fff;font-size:1.0625em}.swal2-popup .swal2-styled.swal2-cancel{border:0;border-radius:.25em;background:initial;background-color:#aaa;color:#fff;font-size:1.0625em}.swal2-popup .swal2-styled:focus{outline:0;box-shadow:0 0 0 2px #fff,0 0 0 4px rgba(50,100,150,.4)}.swal2-popup .swal2-styled::-moz-focus-inner{border:0}.swal2-popup .swal2-footer{justify-content:center;margin:1.25em 0 0;padding:1em 0 0;border-top:1px solid #eee;color:#545454;font-size:1em}.swal2-popup .swal2-image{max-width:100%;margin:1.25em auto}.swal2-popup .swal2-close{position:absolute;top:0;right:0;justify-content:center;width:1.2em;height:1.2em;padding:0;transition:color .1s ease-out;border:none;border-radius:0;outline:initial;background:0 0;color:#ccc;font-family:serif;font-size:2.5em;line-height:1.2;cursor:pointer;overflow:hidden}.swal2-popup .swal2-close:hover{-webkit-transform:none;transform:none;color:#f27474}.swal2-popup>.swal2-checkbox,.swal2-popup>.swal2-file,.swal2-popup>.swal2-input,.swal2-popup>.swal2-radio,.swal2-popup>.swal2-select,.swal2-popup>.swal2-textarea{display:none}.swal2-popup .swal2-content{justify-content:center;margin:0;padding:0;color:#545454;font-size:1.125em;font-weight:300;line-height:normal;z-index:1;word-wrap:break-word}.swal2-popup #swal2-content{text-align:center}.swal2-popup .swal2-checkbox,.swal2-popup .swal2-file,.swal2-popup .swal2-input,.swal2-popup .swal2-radio,.swal2-popup .swal2-select,.swal2-popup .swal2-textarea{margin:1em auto}.swal2-popup .swal2-file,.swal2-popup .swal2-input,.swal2-popup .swal2-textarea{width:100%;transition:border-color .3s,box-shadow .3s;border:1px solid #d9d9d9;border-radius:.1875em;font-size:1.125em;box-shadow:inset 0 1px 1px rgba(0,0,0,.06);box-sizing:border-box}.swal2-popup .swal2-file.swal2-inputerror,.swal2-popup .swal2-input.swal2-inputerror,.swal2-popup .swal2-textarea.swal2-inputerror{border-color:#f27474!important;box-shadow:0 0 2px #f27474!important}.swal2-popup .swal2-file:focus,.swal2-popup .swal2-input:focus,.swal2-popup .swal2-textarea:focus{border:1px solid #b4dbed;outline:0;box-shadow:0 0 3px #c4e6f5}.swal2-popup .swal2-file::-webkit-input-placeholder,.swal2-popup .swal2-input::-webkit-input-placeholder,.swal2-popup .swal2-textarea::-webkit-input-placeholder{color:#ccc}.swal2-popup .swal2-file:-ms-input-placeholder,.swal2-popup .swal2-input:-ms-input-placeholder,.swal2-popup .swal2-textarea:-ms-input-placeholder{color:#ccc}.swal2-popup .swal2-file::-ms-input-placeholder,.swal2-popup .swal2-input::-ms-input-placeholder,.swal2-popup .swal2-textarea::-ms-input-placeholder{color:#ccc}.swal2-popup .swal2-file::placeholder,.swal2-popup .swal2-input::placeholder,.swal2-popup .swal2-textarea::placeholder{color:#ccc}.swal2-popup .swal2-range input{width:80%}.swal2-popup .swal2-range output{width:20%;font-weight:600;text-align:center}.swal2-popup .swal2-range input,.swal2-popup .swal2-range output{height:2.625em;margin:1em auto;padding:0;font-size:1.125em;line-height:2.625em}.swal2-popup .swal2-input{height:2.625em;padding:0 .75em}.swal2-popup .swal2-input[type=number]{max-width:10em}.swal2-popup .swal2-file{font-size:1.125em}.swal2-popup .swal2-textarea{height:6.75em;padding:.75em}.swal2-popup .swal2-select{min-width:50%;max-width:100%;padding:.375em .625em;color:#545454;font-size:1.125em}.swal2-popup .swal2-checkbox,.swal2-popup .swal2-radio{align-items:center;justify-content:center}.swal2-popup .swal2-checkbox label,.swal2-popup .swal2-radio label{margin:0 .6em;font-size:1.125em}.swal2-popup .swal2-checkbox input,.swal2-popup .swal2-radio input{margin:0 .4em}.swal2-popup .swal2-validation-message{display:none;align-items:center;justify-content:center;padding:.625em;background:#f0f0f0;color:#666;font-size:1em;font-weight:300;overflow:hidden}.swal2-popup .swal2-validation-message::before{display:inline-block;width:1.5em;min-width:1.5em;height:1.5em;margin:0 .625em;border-radius:50%;background-color:#f27474;color:#fff;font-weight:600;line-height:1.5em;text-align:center;content:'!';zoom:normal}@supports (-ms-accelerator:true){.swal2-range input{width:100%!important}.swal2-range output{display:none}}@media all and (-ms-high-contrast:none),(-ms-high-contrast:active){.swal2-range input{width:100%!important}.swal2-range output{display:none}}@-moz-document url-prefix(){.swal2-close:focus{outline:2px solid rgba(50,100,150,.4)}}.swal2-icon{position:relative;justify-content:center;width:5em;height:5em;margin:1.25em auto 1.875em;border:.25em solid transparent;border-radius:50%;line-height:5em;cursor:default;box-sizing:content-box;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;zoom:normal}.swal2-icon-text{font-size:3.75em}.swal2-icon.swal2-error{border-color:#f27474}.swal2-icon.swal2-error .swal2-x-mark{position:relative;flex-grow:1}.swal2-icon.swal2-error [class^=swal2-x-mark-line]{display:block;position:absolute;top:2.3125em;width:2.9375em;height:.3125em;border-radius:.125em;background-color:#f27474}.swal2-icon.swal2-error [class^=swal2-x-mark-line][class$=left]{left:1.0625em;-webkit-transform:rotate(45deg);transform:rotate(45deg)}.swal2-icon.swal2-error [class^=swal2-x-mark-line][class$=right]{right:1em;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}.swal2-icon.swal2-warning{border-color:#facea8;color:#f8bb86}.swal2-icon.swal2-info{border-color:#9de0f6;color:#3fc3ee}.swal2-icon.swal2-question{border-color:#c9dae1;color:#87adbd}.swal2-icon.swal2-success{border-color:#a5dc86}.swal2-icon.swal2-success [class^=swal2-success-circular-line]{position:absolute;width:3.75em;height:7.5em;-webkit-transform:rotate(45deg);transform:rotate(45deg);border-radius:50%}.swal2-icon.swal2-success [class^=swal2-success-circular-line][class$=left]{top:-.4375em;left:-2.0635em;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);-webkit-transform-origin:3.75em 3.75em;transform-origin:3.75em 3.75em;border-radius:7.5em 0 0 7.5em}.swal2-icon.swal2-success [class^=swal2-success-circular-line][class$=right]{top:-.6875em;left:1.875em;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);-webkit-transform-origin:0 3.75em;transform-origin:0 3.75em;border-radius:0 7.5em 7.5em 0}.swal2-icon.swal2-success .swal2-success-ring{position:absolute;top:-.25em;left:-.25em;width:100%;height:100%;border:.25em solid rgba(165,220,134,.3);border-radius:50%;z-index:2;box-sizing:content-box}.swal2-icon.swal2-success .swal2-success-fix{position:absolute;top:.5em;left:1.625em;width:.4375em;height:5.625em;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);z-index:1}.swal2-icon.swal2-success [class^=swal2-success-line]{display:block;position:absolute;height:.3125em;border-radius:.125em;background-color:#a5dc86;z-index:2}.swal2-icon.swal2-success [class^=swal2-success-line][class$=tip]{top:2.875em;left:.875em;width:1.5625em;-webkit-transform:rotate(45deg);transform:rotate(45deg)}.swal2-icon.swal2-success [class^=swal2-success-line][class$=long]{top:2.375em;right:.5em;width:2.9375em;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}.swal2-progresssteps{align-items:center;margin:0 0 1.25em;padding:0;font-weight:600}.swal2-progresssteps li{display:inline-block;position:relative}.swal2-progresssteps .swal2-progresscircle{width:2em;height:2em;border-radius:2em;background:#3085d6;color:#fff;line-height:2em;text-align:center;z-index:20}.swal2-progresssteps .swal2-progresscircle:first-child{margin-left:0}.swal2-progresssteps .swal2-progresscircle:last-child{margin-right:0}.swal2-progresssteps .swal2-progresscircle.swal2-activeprogressstep{background:#3085d6}.swal2-progresssteps .swal2-progresscircle.swal2-activeprogressstep~.swal2-progresscircle{background:#add8e6}.swal2-progresssteps .swal2-progresscircle.swal2-activeprogressstep~.swal2-progressline{background:#add8e6}.swal2-progresssteps .swal2-progressline{width:2.5em;height:.4em;margin:0 -1px;background:#3085d6;z-index:10}[class^=swal2]{-webkit-tap-highlight-color:transparent}.swal2-show{-webkit-animation:swal2-show .3s;animation:swal2-show .3s}.swal2-show.swal2-noanimation{-webkit-animation:none;animation:none}.swal2-hide{-webkit-animation:swal2-hide .15s forwards;animation:swal2-hide .15s forwards}.swal2-hide.swal2-noanimation{-webkit-animation:none;animation:none}.swal2-rtl .swal2-close{right:auto;left:0}.swal2-animate-success-icon .swal2-success-line-tip{-webkit-animation:swal2-animate-success-line-tip .75s;animation:swal2-animate-success-line-tip .75s}.swal2-animate-success-icon .swal2-success-line-long{-webkit-animation:swal2-animate-success-line-long .75s;animation:swal2-animate-success-line-long .75s}.swal2-animate-success-icon .swal2-success-circular-line-right{-webkit-animation:swal2-rotate-success-circular-line 4.25s ease-in;animation:swal2-rotate-success-circular-line 4.25s ease-in}.swal2-animate-error-icon{-webkit-animation:swal2-animate-error-icon .5s;animation:swal2-animate-error-icon .5s}.swal2-animate-error-icon .swal2-x-mark{-webkit-animation:swal2-animate-error-x-mark .5s;animation:swal2-animate-error-x-mark .5s}@-webkit-keyframes swal2-rotate-loading{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes swal2-rotate-loading{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@media print{body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown){overflow-y:scroll!important}body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown)>[aria-hidden=true]{display:none}body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown) .swal2-container{position:initial!important}}"); diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/mcp/mcp_aps_change.html b/ext/phpbbstudio/aps/styles/prosilver/template/mcp/mcp_aps_change.html new file mode 100644 index 0000000..a5bfbbf --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/mcp/mcp_aps_change.html @@ -0,0 +1,81 @@ +{% include 'mcp_header.html' %} + +

      {{ lang('MCP_APS_POINTS_CHANGE', aps_name()) }}

      + +
      + {% if S_APS_SEARCH %} + {% include '@phpbbstudio_aps/mcp/mcp_aps_find_username.html' %} + {% else %} + {% INCLUDEJS '@phpbbstudio_aps/js/aps_mcp.js' %} + + {% if S_APS_LOGS %} +
      +
      + {% include '@phpbbstudio_aps/mcp/mcp_aps_logs_list.html' with {'logs_array': logs} %} +
      +
      + {% endif %} + +
      +
      +
      + {% if APS_USERNAME %} +
      +
      {{ lang('USERNAME') ~ lang('COLON') }}
      +
      {{ APS_USERNAME }}
      +
      +
      +
      {{ aps_name() ~ lang('COLON') }}
      +
      {{ APS_POINTS }}
      +
      + {% else %} +
      +
      {{ lang('GROUP') ~ lang('COLON') }}
      +
      {{ APS_GROUP }}
      +
      + {% endif %} +
      +
      + +
      +
      + +
      + +
      +
      +
      +
      +
      + +
      + +   + + {{ S_FORM_TOKEN }} +
      + {% endif %} +
      + +{% include 'mcp_footer.html' %} + diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/mcp/mcp_aps_find_username.html b/ext/phpbbstudio/aps/styles/prosilver/template/mcp/mcp_aps_find_username.html new file mode 100644 index 0000000..058b838 --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/mcp/mcp_aps_find_username.html @@ -0,0 +1,42 @@ +{% INCLUDECSS '@phpbbstudio_aps/aps_display.css' %} + +
      +
      + {% if S_ERROR %} +
      +

      {{ ERROR_MSG }}

      +
      + {% endif %} + +
      +
      +
      +
      +
      [ {{ lang('FIND_USERNAME') }} ]
      +
      +
      + +
      + +   + +
      + +
      + +
      +
      +
      +
      +
      +
      + +
      + +   + +
      + + {{ S_FORM_TOKEN }} +
      +
      diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/mcp/mcp_aps_front.html b/ext/phpbbstudio/aps/styles/prosilver/template/mcp/mcp_aps_front.html new file mode 100644 index 0000000..ddd4914 --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/mcp/mcp_aps_front.html @@ -0,0 +1,56 @@ +{% include 'mcp_header.html' %} + +

      {{ lang('MAIN') }}

      + +{% EVENT phpbbstudio_aps_mcp_front_before %} + +{% if S_APS_LOGS %} +
      +
      +

      {{ lang('LATEST_LOGS') }}

      + + {% include '@phpbbstudio_aps/mcp/mcp_aps_logs_list.html' with {'logs_array': logs} %} +
      +
      + + {% EVENT phpbbstudio_aps_mcp_front_between_logs %} + +
      +
      +

      {{ lang('MCP_APS_LATEST_ADJUSTED', 5) }}

      + + {% include '@phpbbstudio_aps/mcp/mcp_aps_logs_list.html' with {'logs_array': moderated} %} +
      +
      +{% endif %} + +{% EVENT phpbbstudio_aps_mcp_front_between %} + +
      +
      +
      +

      {{ lang('MCP_APS_USERS_TOP', 5) }}

      +
        + {% for user in aps_users_top %} +
      • {{ loop.index }}. {{ aps_display(user.POINTS) }} {{ user.NAME }}
      • + {% else %} +
      • {{ lang('NO_ONLINE_USERS') }}
      • + {% endfor %} +
      +
      +
      +

      {{ lang('MCP_APS_USERS_BOTTOM', 5) }}

      +
        + {% for user in aps_users_bottom %} +
      • {{ loop.index }}. {{ aps_display(user.POINTS) }} {{ user.NAME }}
      • + {% else %} +
      • {{ lang('NO_ONLINE_USERS') }}
      • + {% endfor %} +
      +
      +
      +
      + +{% EVENT phpbbstudio_aps_mcp_front_after %} + +{% include 'mcp_footer.html' %} diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/mcp/mcp_aps_logs.html b/ext/phpbbstudio/aps/styles/prosilver/template/mcp/mcp_aps_logs.html new file mode 100644 index 0000000..b720d4a --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/mcp/mcp_aps_logs.html @@ -0,0 +1,40 @@ +{% include 'mcp_header.html' %} + +

      {{ lang('MCP_APS_LOGS') }}

      + +
      +
      +
      + + + {% if pagination %} + + {% endif %} + + {% include '@phpbbstudio_aps/mcp/mcp_aps_logs_list.html' with {'logs_array': logs} %} + + {% if pagination %} + + {% endif %} + +
      + {{ lang('DISPLAY_LOG') ~ lang('COLON') }} {{ S_LIMIT_DAYS }}   + {{ lang('SORT_BY') ~ lang('COLON') }} {{ S_SORT_KEY }} {{ S_SORT_DIR }} + + {{ S_FORM_TOKEN }} +
      +
      +
      +
      + +{% include 'mcp_footer.html' %} diff --git a/ext/phpbbstudio/aps/styles/prosilver/template/mcp/mcp_aps_logs_list.html b/ext/phpbbstudio/aps/styles/prosilver/template/mcp/mcp_aps_logs_list.html new file mode 100644 index 0000000..6e0ac4c --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/template/mcp/mcp_aps_logs_list.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + {% for log in logs_array %} + + + + + + + + + + {% else %} + + + + {% endfor %} + +
      {{ lang('REASON') }}
      {{ log.USER }} + {{ lang(log.ACTION, aps_name()) }} + {% if not log.S_SELF and log.REPORTEE %}
      » {{ lang('FROM') ~ ' ' ~ log.REPORTEE }}{% endif %} +
      {{ user.format_date(log.TIME) }}{{ aps_display(log.POINTS_OLD, false) }}{{ aps_display(log.POINTS_SUM, false) }}{{ aps_display(log.POINTS_NEW, false) }} + {% if log.FORUM_NAME %}» {{ log.FORUM_NAME }}
      {% endif %} + {% if log.TOPIC_TITLE %}» {{ log.TOPIC_TITLE }}
      {% endif %} + {% if log.POST_SUBJECT %}» {{ log.POST_SUBJECT }}
      {% endif %} + {% if not log.FORUM_NAME and not log.TOPIC_TITLE and not log.POST_SUBJECT %}{{ lang('NA') }}{% endif %} +
      {{ lang('NO_ENTRIES') }}
      diff --git a/ext/phpbbstudio/aps/styles/prosilver/theme/aps_display.css b/ext/phpbbstudio/aps/styles/prosilver/theme/aps_display.css new file mode 100644 index 0000000..8e24c05 --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/theme/aps_display.css @@ -0,0 +1,738 @@ +/* Colours and border radius */ +:root { + --white: #ffffff; + --white-darken-1: #f9f9f9; + --white-darken-2: #fefefe; + --white-darken-3: #ebedf2; + --white-darken-4: #ececec; + --white-darken-5: #e0e0e0; + --white-darken-6: #c9c9c9; + + --blue: #12a3eb; + --blue-darken-1: #0076b1; + + --border-radius-style: 7px; + --border-radius-aps-panels: 4px; +} + +/* Helpers */ +.nojs .aps-js { display: none; } + +.aps-block { display: block; } +.aps-inline { display: inline-block; } +.aps-relative { position: relative; } + +.aps-center { text-align: center; } +.aps-bold { font-weight: bold; } +.aps-italic { font-style: italic; } + +.aps-cursor-normal { cursor: auto; } +.aps-cursor-banned { cursor: not-allowed; } +.aps-cursor-pointer { cursor: pointer; } +.aps-cursor-help { cursor: help; } + +.aps-mar-top { margin-top: 16px; } +.aps-mar-bot { margin-bottom: 16px; } +.aps-mar-left { margin-left: 16px; } +.aps-mar-right { margin-right: 16px; } + +.aps-no-mar { margin: 0 !important; } +.aps-no-mar-bot { margin-bottom: 0 !important; } +.aps-no-mar-top { margin-top: 0 !important; } +.aps-no-mar-left { margin-left: 0 !important; } +.aps-no-mar-right { margin-right: 0 !important; } + +.aps-no-mar-edge { + margin-top: 0; + margin-bottom: 0; +} + +.aps-no-mar-side { + margin-right: 0; + margin-left: 0; +} + +.aps-no-padding { padding: 0 !important; } +.aps-padding { padding: 8px 16px; } + +.aps-padding-edge { + padding-top: 8px; + padding-bottom: 8px; +} + +.aps-padding-side { + padding-right: 16px; + padding-left: 16px; +} + +.aps-ellipsis, +.aps-ellipsis * { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} + +.aps-positive, +.aps-negative { font-weight: bold; } +.aps-positive { color: #28a745; } +.aps-negative { color: #d31141; } + +.aps-podium-0 { color: #313131; } +.aps-podium-1 { color: #d6af36; } +.aps-podium-2 { color: #a7a7ad; } +.aps-podium-3 { color: #824a02; } + +.aps-main, +.aps-main *, +.aps-main *:after, +.aps-main *:before { + box-sizing: border-box; +} + +.aps-main { + font-size: 13px; + font-weight: 300; + line-height: 1.5; + background: var(--white-darken-1); + border-radius: var(--border-radius-style); + color: #212529; + position: relative; + z-index: 0; + margin: 10px 0; +} + +.aps-footer { + font-size: 0.9em; + text-align: right; + background: var(--white); + border-bottom-right-radius: var(--border-radius-style); + border-bottom-left-radius: var(--border-radius-style); + -webkit-box-shadow: 0 1px 15px 1px rgba(69, 65, 78, 0.1); + box-shadow: 0 1px 15px 1px rgba(69, 65, 78, 0.1); + color: #999999; + padding: 8px 16px; +} + +.aps-footer i { + font-size: 0.9em; + color: var(--blue); +} + +.aps-footer a { + color: #999999; +} + +.aps-footer a:hover { + color: #313131; + text-decoration-color: var(--blue); +} + +.aps-nav { + border-bottom: 1px solid var(--blue-darken-1); + border-radius: 0; + margin: 0; + padding: 0; +} + +.aps-menu { + background: var(--white); + border-top-left-radius: var(--border-radius-style); + border-top-right-radius: var(--border-radius-style); + -webkit-box-shadow: 0 1px 15px 1px rgba(69, 65, 78, 0.1); + box-shadow: 0 1px 15px 1px rgba(69, 65, 78, 0.1); + position: relative; + z-index: 1; +} + +.aps-menu-before { + line-height: 48px; + text-align: center; + width: 32px; + height: 48px; + margin-left: 8px; +} + +.aps-menu-icon { + line-height: 56px; + text-align: center; + background: linear-gradient(var(--white-darken-2), var(--white) 75%); + position: absolute; + z-index: 2; + top: 0; + left: 56px; + display: inline-block; + width: 64px; + height: 56px; +} + +.aps-menu-icon > div { + width: 48px; + margin: 0 8px; +} + +.aps-menu-icon > div:after, +.aps-menu-icon > div:before, +.aps-menu-icon:after, +.aps-menu-icon:before { + background: inherit; + position: absolute; + z-index: -2; + top: 0; + right: -12px; + width: 100%; + height: 56px; + content: ""; + -webkit-transform: skewX(-10deg); + -moz-transform: skewX(-10deg); + -ms-transform: skewX(-10deg); + transform: skewX(-10deg); +} + +.aps-menu-icon:after { + border-right: 1px solid var(--white-darken-1); + -webkit-box-shadow: 1px 0 0 rgba(69, 65, 78, 0.1); + box-shadow: 1px 0 0 rgba(69, 65, 78, 0.1); +} + +.aps-menu-icon:before { + border-left: 1px solid var(--white-darken-1); + -webkit-box-shadow: -1px 0 0 rgba(69, 65, 78, 0.1); + box-shadow: -1px 0 0 rgba(69, 65, 78, 0.1); + right: unset; + left: -12px; + -webkit-transform: skewX(10deg); + -moz-transform: skewX(10deg); + -ms-transform: skewX(10deg); + transform: skewX(10deg); +} + +.aps-menu-icon > div:after { + border-right: 1px solid var(--white-darken-4); + -webkit-box-shadow: 2px 0 0 rgba(69, 65, 78, 0.05); + box-shadow: 2px 0 0 rgba(69, 65, 78, 0.05); + z-index: -1; + right: -4px; + -webkit-transform: skewX(-7deg); + -moz-transform: skewX(-7deg); + -ms-transform: skewX(-7deg); + transform: skewX(-7deg); +} + +.aps-menu-icon > div:before { + border-left: 1px solid var(--white-darken-4); + -webkit-box-shadow: -2px 0 0 rgba(69, 65, 78, 0.05); + box-shadow: -2px 0 0 rgba(69, 65, 78, 0.05); + z-index: -1; + right: unset; + left: -4px; + -webkit-transform: skewX(7deg); + -moz-transform: skewX(7deg); + -ms-transform: skewX(7deg); + transform: skewX(7deg); +} + +.aps-menu-icon:after, +.aps-menu-icon:before { + height: 52px; +} + +.aps-menu-icon i { + font-size: 32px; + vertical-align: -6px; + -webkit-background-clip: text; + -moz-background-clip: text; + background-clip: text; + border: none; + text-shadow: 2px 2px 3px rgba(255, 255, 255, 0.5); + color: transparent; +} + +.aps-lists-container { + display: flex; + flex-flow: row nowrap; +} + +.aps-lists-container > .aps-list { + flex: 1 1 auto; +} + +.aps-lists-container > .aps-list-right { + flex-basis: 0; +} + +.aps-list { + display: flex; + justify-content: flex-start; + align-items: stretch; + list-style: none; + align-content: flex-start; + flex-flow: row wrap; +} + +.aps-list-right { + justify-content: flex-end; +} + +.aps-list > li { + flex: 0 1 auto; +} + +.aps-list-item { + line-height: 48px; + text-align: center; + white-space: nowrap; + text-decoration: none; + display: block; + min-width: 64px; + height: 48px; + padding: 0 16px; +} + +.aps-menu > .aps-list:not(.aps-list-right) > :first-child { + margin-left: 89px; +} + +.aps-menu .aps-list-item { + font-weight: bold; + text-decoration: none; + color: var(--blue-darken-1); + position: relative; + padding: 0 16px 0 24px; +} + +.aps-menu .aps-list-item:hover { + text-decoration: none; + color: var(--blue); +} + +.aps-menu .aps-list-item:not(.aps-list-active) i { + transition: all 0.2s ease-in-out; +} + +.aps-menu .aps-list-item:not(.aps-list-active):hover i { + font-size: 1.2em; +} + +.aps-menu .aps-list-item:after { + border-right: 1px solid var(--white-darken-4); + border-left: 1px solid var(--white-darken-4); + -webkit-box-shadow: 0 1px 15px 1px rgba(69, 65, 78, 0.08); + box-shadow: 0 1px 15px 1px rgba(69, 65, 78, 0.08); + position: absolute; + z-index: -1; + top: 0; + right: -4px; + width: 100%; + height: 100%; + content: ""; + -webkit-transform: skewX(-10deg); + -moz-transform: skewX(-10deg); + -ms-transform: skewX(-10deg); + transform: skewX(-10deg); +} + +.aps-menu .aps-list-active { + margin-right: 2px; +} + +.aps-menu .aps-list-active:after { + border-color: var(--blue-darken-1); + -webkit-box-shadow: 5px 1px 8px 1px rgba(69, 65, 78, 0.08), -5px 1px 8px 1px rgba(69, 65, 78, 0.08); + box-shadow: 5px 1px 8px 1px rgba(69, 65, 78, 0.08), -5px 1px 8px 1px rgba(69, 65, 78, 0.08); +} + +.aps-menu .aps-list-item > span { + display: none; +} + +@media only screen and (min-width: 601px) { + .aps-menu .aps-list-active > span { + display: inline-block; + } +} + +@media only screen and (max-width: 600px) { + .aps-menu { + flex-flow: row wrap; + } + + .aps-menu .aps-list:not(.aps-list-right) { + flex-basis: 100%; + order: 1; + } + + .aps-menu > .aps-list:not(.aps-list-right) > :first-child { + margin-left: 0; + } +} + +.aps-menu .aps-list-right > li:last-child .aps-list-item { + background: var(--white); + border-top-right-radius: var(--border-radius-style); +} + +.aps-menu .aps-list-right > li:last-child .aps-list-item:after { + border-right: none; + border-left-color: var(--blue-darken-1); + right: unset; + left: -5px; +} + +.aps-nav .aps-list-item { + text-decoration: none; + color: #ececec; +} + +.aps-nav .aps-list-item:hover { + text-decoration: none; + color: #ffffff; +} + +.aps-nav .aps-list-active { + font-weight: bold; + text-shadow: 1px 1px 1px var(--blue); + color: #ffffff; +} + +.aps-body { + margin: 6px; + padding: 15px; +} + +.aps-panel-placeholder, +.aps-panel { + background-color: var(--white); + border: 1px solid var(--white-darken-4); + border-radius: var(--border-radius-aps-panels); + -webkit-box-shadow: 0 1px 15px 1px rgba(69, 65, 78, 0.08); + box-shadow: 0 1px 15px 1px rgba(69, 65, 78, 0.08); + position: relative; +} + +.aps-panel-header, +.aps-panel-content, +.aps-panel-footer { + position: relative; + padding: 8px 10px; +} + +.aps-panel-header, +.aps-panel-footer { + background: var(--white-darken-2); +} + +.aps-panel-header { + border-bottom: 1px solid var(--white-darken-3); + border-top-left-radius: inherit; + border-top-right-radius: inherit; +} + +.aps-panel-header > h3 { + font-weight: bold; + letter-spacing: 0.05rem; + text-transform: none; + border: none; + color: #333333; + display: inline-block; + margin: 0; + padding: 0; +} + +.aps-panel-footer { + border-top: 1px solid var(--white-darken-3); + border-bottom-right-radius: inherit; + border-bottom-left-radius: inherit; +} + +.aps-panel-footer, +.aps-panel-footer label { + color: #999999; +} + +.aps-panel-content { + color: #7b7e8a; + overflow-y: scroll; + -webkit-overflow-scrolling: touch; + max-height: 500px; + -ms-overflow-style: -ms-autohiding-scrollbar; + scrollbar-color: rgba(0, 0, 0, 0.4) transparent; + scrollbar-width: 4px; +} + +.aps-panel-action, +a.aps-panel-action { + color: #999999; + float: right; + margin-right: 5px; + cursor: pointer; +} + +.aps-panel-delete:hover { + color: #d31141; +} + +.aps-panel-move:hover { + color: var(--blue); + cursor: grab; +} + +.aps-panel-add { + padding: 0 12px; +} + +.aps-panel-add-pulse, +.aps-panel-add .aps-panel-action:hover { + color: #28a745; +} + +.aps-panel-add .aps-button-red { + display: inline-block; + margin: 5px; + cursor: not-allowed; +} + +.aps-panel-add li { + margin: 5px; +} + +.aps-body > .aps-row > .aps-panel-empty, +.aps-panel-add .aps-panel-empty { + display: none; +} + +.aps-panel-add .aps-panel-empty:only-child { + display: list-item; +} + +.aps-panel-add > .dropdown { + margin-top: 16px; +} + +.aps-panel-add > .dropdown > .pointer { + right: 22px; +} + +.aps-body > .aps-row > .aps-panel-empty:only-child { + display: block; +} + +.aps-panel-placeholder { + background: var(--white-darken-5); + border: 1px dashed var(--white-darken-6); +} + +.aps-panel-content::-webkit-scrollbar { width: 6px; } +.aps-panel-content::-webkit-scrollbar-track { background: transparent; } + +.aps-panel-content::-webkit-scrollbar-thumb { + background: rgba(0, 0, 0, 0.4); + border-radius: 4px; +} + +.aps-panel-add-pulse { + box-shadow: 0 0 0 #28a745; /* same as rgba(40, 167, 69) below */ + animation: pulse 1.5s infinite; +} + +.aps-panel-add-pulse:hover { + animation: none; +} + +.aps-panel-add-pulse i { + padding-left: 3px; +} + +@keyframes pulse { + 0% { + -moz-box-shadow: 0 0 0 0 rgba(40, 167, 69, 0.4); + box-shadow: 0 0 0 0 rgba(40, 167, 69, 0.4); + } + + 70% { + -moz-box-shadow: 0 0 0 10px rgba(40, 167, 69, 0); + box-shadow: 0 0 0 10px rgba(40, 167, 69, 0); + } + + 100% { + -moz-box-shadow: 0 0 0 0 rgba(40, 167, 69, 0); + box-shadow: 0 0 0 0 rgba(40, 167, 69, 0); + } +} + +/** + * Materialize collections + */ +.aps-collection { + border: 1px solid var(--white-darken-5); + border-radius: var(--border-radius-aps-panels); + position: relative; + overflow: hidden; + margin: 0.5rem 0 1rem; +} + +.aps-collection .aps-collection-item { + line-height: 1.5rem; + background-color: var(--white); + border-bottom: 1px solid var(--white-darken-5); + margin: 0; + padding: 10px 20px; +} + +.aps-collection .aps-collection-item.aps-avatar { + position: relative; + min-height: 64px; + padding-left: 72px; +} + +.aps-collection .aps-collection-item.aps-avatar .avatar { + vertical-align: middle; + position: absolute; + left: 15px; + display: inline-block; + overflow: hidden; + width: 42px; + height: 42px; +} + +.aps-collection .aps-collection-item.aps-avatar i.circle { + font-size: 18px; + line-height: 42px; + text-align: center; + background-color: #999999; + color: #ffffff; +} + +.aps-collection .aps-collection-item.aps-avatar .title { + font-size: 16px; +} + +.aps-collection .aps-collection-item.aps-avatar p { + margin: 0; +} + +.aps-collection .aps-collection-item.aps-avatar .aps-secondary-content { + position: absolute; + top: 16px; + right: 16px; +} + +.aps-collection .aps-collection-item:last-child { + border-bottom: none; +} + +.aps-collection .aps-collection-item.active { + background-color: #26a69a; + color: #eafaf9; +} + +.aps-collection .aps-collection-item.active .secondary-content { + color: #ffffff; +} + +.aps-collection a.aps-collection-item { + display: block; + -webkit-transition: 0.25s; + transition: 0.25s; +} + +.aps-collection a.aps-collection-item:not(.active):hover { + background-color: #dddddd; +} + +.aps-collection.aps-with-header .aps-collection-header { + background-color: #ffffff; + border-bottom: 1px solid #e0e0e0; + padding: 10px 20px; +} + +.aps-collection.aps-with-header .aps-collection-item { + padding-left: 30px; +} + +.aps-collection.aps-with-header .aps-collection-item.aps-avatar { + padding-left: 72px; +} + +.aps-row { + display: flex; + justify-content: space-between; + align-items: flex-start; + align-content: flex-start; + flex-flow: row wrap; +} + +.aps-col { + flex: 0 auto; + min-height: 1px; + margin-bottom: 1.25rem; + padding: 0 0.75rem; +} + +.aps-col.s1 { flex-basis: calc(100% / 12); } +.aps-col.s2 { flex-basis: calc(100% / 12 * 2); } +.aps-col.s3 { flex-basis: 25%; } +.aps-col.s4 { flex-basis: calc(100% / 12 * 4); } +.aps-col.s5 { flex-basis: calc(100% / 12 * 5); } +.aps-col.s6 { flex-basis: 50%; } +.aps-col.s7 { flex-basis: calc(100% / 12 * 7); } +.aps-col.s8 { flex-basis: calc(100% / 12 * 8); } +.aps-col.s9 { flex-basis: 75%; } +.aps-col.s10 { flex-basis: calc(100% / 12 * 10); } +.aps-col.s11 { flex-basis: calc(100% / 12 * 11); } +.aps-col.s12 { flex-basis: 100%; } + +.aps-hide-s { display: none; } + +@media only screen and (min-width: 601px) { + .aps-col.m1 { flex-basis: calc(100% / 12); } + .aps-col.m2 { flex-basis: calc(100% / 12 * 2); } + .aps-col.m3 { flex-basis: 25%; } + .aps-col.m4 { flex-basis: calc(100% / 12 * 4); } + .aps-col.m5 { flex-basis: calc(100% / 12 * 5); } + .aps-col.m6 { flex-basis: 50%; } + .aps-col.m7 { flex-basis: calc(100% / 12 * 7); } + .aps-col.m8 { flex-basis: calc(100% / 12 * 8); } + .aps-col.m9 { flex-basis: 75%; } + .aps-col.m10 { flex-basis: calc(100% / 12 * 10); } + .aps-col.m11 { flex-basis: calc(100% / 12 * 11); } + .aps-col.m12 { flex-basis: 100%; } + + .aps-hide-s { display: block; } + .aps-hide-m { display: none; } +} + +@media only screen and (min-width: 993px) { + .aps-col.l1 { flex-basis: calc(100% / 12); } + .aps-col.l2 { flex-basis: calc(100% / 12 * 2); } + .aps-col.l3 { flex-basis: 25%; } + .aps-col.l4 { flex-basis: calc(100% / 12 * 4); } + .aps-col.l5 { flex-basis: calc(100% / 12 * 5); } + .aps-col.l6 { flex-basis: 50%; } + .aps-col.l7 { flex-basis: calc(100% / 12 * 7); } + .aps-col.l8 { flex-basis: calc(100% / 12 * 8); } + .aps-col.l9 { flex-basis: 75%; } + .aps-col.l10 { flex-basis: calc(100% / 12 * 10); } + .aps-col.l11 { flex-basis: calc(100% / 12 * 11); } + .aps-col.l12 { flex-basis: 100%; } + + .aps-hide-m { display: block; } + .aps-hide-l { display: none; } +} + +@media only screen and (min-width: 1201px) { + .aps-col.xl1 { flex-basis: calc(100% / 12); } + .aps-col.xl2 { flex-basis: calc(100% / 12 * 2); } + .aps-col.xl3 { flex-basis: 25%; } + .aps-col.xl4 { flex-basis: calc(100% / 12 * 4); } + .aps-col.xl5 { flex-basis: calc(100% / 12 * 5); } + .aps-col.xl6 { flex-basis: 50%; } + .aps-col.xl7 { flex-basis: calc(100% / 12 * 7); } + .aps-col.xl8 { flex-basis: calc(100% / 12 * 8); } + .aps-col.xl9 { flex-basis: 75%; } + .aps-col.xl10 { flex-basis: calc(100% / 12 * 10); } + .aps-col.xl11 { flex-basis: calc(100% / 12 * 11); } + .aps-col.xl12 { flex-basis: 100%; } + + .aps-hide-l { display: block; } + .aps-hide-xl { display: none; } +} diff --git a/ext/phpbbstudio/aps/styles/prosilver/theme/aps_form.css b/ext/phpbbstudio/aps/styles/prosilver/theme/aps_form.css new file mode 100644 index 0000000..2c086ec --- /dev/null +++ b/ext/phpbbstudio/aps/styles/prosilver/theme/aps_form.css @@ -0,0 +1,184 @@ +.aps-form *, +.aps-form *:before, +.aps-form *:after { + box-sizing: border-box; +} + +.aps-radio { display: none; } + +.aps-radio:checked + .aps-button-blue { + background: #12a3eb; + border-color: #12a3eb; + color: #ffffff; +} + +/* Fix for double borders due to border-box */ +.aps-form dt { + border-right: none; +} + +.aps-form dd, +.aps-form dd label { + font-size: 14px; + line-height: 1.42857143; +} + +.aps-form dd label { + display: inline-block; + height: 34px; + padding: 6px; +} + +.aps-form dd label input[type="radio"] { + height: initial; + margin-right: 3px; +} + +.aps-button-red, +.aps-button-blue, +.aps-button-green, +.aps-form input:not(.iconpicker-search), +.aps-form select, +.aps-form textarea { + font-size: 14px; + line-height: 1.42857143; + color: #555555; + height: 34px; + padding: 6px 12px; +} + +.aps-button-red, +.aps-button-blue, +.aps-button-green, +.aps-form input[type="text"], +.aps-form input[type="number"], +.aps-form input[type="search"], +.aps-form input[type="submit"], +.aps-form input[type="reset"], +.aps-form select, +.aps-form textarea { + background-color: #ffffff; + background-image: none; + border: 1px solid #cccccc; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-sizing: border-box; + -webkit-transition: border-color ease-in-out 0.15s, -webkit-box-shadow ease-in-out 0.15s; + -o-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; +} + +.aps-form input:not([type="checkbox"]):not([type="radio"]):focus, +.aps-form select:focus, +.aps-form textarea:focus { + border-color: #66afe9; + outline: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6); +} + +.aps-form select:focus { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} + +.aps-form select[multiple] { + height: auto; + min-height: 170px; +} + +.has-js select[multiple] { + max-height: 340px; +} + +.aps-form textarea { + height: auto; + resize: vertical; +} + +/* Buttons */ +.aps-button-green, +a.aps-button-green, +.aps-form input[type="submit"] { + border-color: #28a745; + color: #28a745; +} + +.aps-button-green:hover, +a.aps-button-green:hover, +.aps-button-green.aps-button-active, +.aps-form input[type="submit"]:hover { + background-color: #28a745; + color: #ffffff; +} + +.aps-button-red, +a.aps-button-red, +.aps-form input[type="reset"], +.aps-form input[name="cancel"] { + border-color: #d31141; + color: #d31141; +} + +.aps-button-red:hover, +a.aps-button-red:hover, +.aps-button-red.aps-button-active, +.aps-form input[type="reset"]:hover, +.aps-form input[name="cancel"]:hover { + background-color: #d31141; + color: #ffffff; +} + +.aps-button-blue { + border-color: #12a3eb; + color: #12a3eb; +} + +.aps-button-blue:hover, +.aps-button-blue.aps-button-active { + background-color: #12a3eb; + color: #ffffff; +} + +[class*="aps-button-"] .caret { + padding-left: 6px; +} + +[class*="aps-button-"]:hover .caret { + border-color: #ffffff; +} + +.aps-button-red, +.aps-button-blue, +.aps-button-green { + text-decoration: none !important; + cursor: pointer; +} + +/* Form dropdown(s) */ +.aps-form .dropdown-container { + vertical-align: -2px; + display: inline-block; + float: none; +} + +/* Multiple select scrollbar */ +.aps-form select[multiple] { + scrollbar-color: #666666 #cccccc; + scrollbar-width: 10px; +} + +.aps-form select[multiple]::-webkit-scrollbar { + width: 10px; +} + +.aps-form select[multiple]::-webkit-scrollbar-thumb { + background: #666666; + border-radius: 0 4px 4px 0; +} + +.aps-form select[multiple]::-webkit-scrollbar-track { + background: #cccccc; + border-radius: 0 4px 4px 0; +} diff --git a/ext/phpbbstudio/ass/acp/main_info.php b/ext/phpbbstudio/ass/acp/main_info.php new file mode 100644 index 0000000..a1d01e0 --- /dev/null +++ b/ext/phpbbstudio/ass/acp/main_info.php @@ -0,0 +1,57 @@ + '\phpbbstudio\ass\acp\main_module', + 'title' => 'ACP_ASS_SYSTEM', + 'modes' => [ + 'overview' => [ + 'title' => 'ACP_ASS_OVERVIEW', + 'auth' => 'ext_phpbbstudio/ass && acl_a_ass_overview', + 'cat' => ['ACP_ASS_SYSTEM'], + ], + 'settings' => [ + 'title' => 'ACP_ASS_SETTINGS', + 'auth' => 'ext_phpbbstudio/ass && acl_a_ass_settings', + 'cat' => ['ACP_ASS_SYSTEM'], + ], + 'items' => [ + 'title' => 'ACP_ASS_ITEMS', + 'auth' => 'ext_phpbbstudio/ass && acl_a_ass_items', + 'cat' => ['ACP_ASS_SYSTEM'], + ], + 'files' => [ + 'title' => 'ACP_ASS_FILES', + 'auth' => 'ext_phpbbstudio/ass && acl_a_ass_files', + 'cat' => ['ACP_ASS_SYSTEM'], + ], + 'logs' => [ + 'title' => 'ACP_ASS_LOGS', + 'auth' => 'ext_phpbbstudio/ass && acl_a_ass_logs', + 'cat' => ['ACP_ASS_SYSTEM'], + ], + 'inventory' => [ + 'title' => 'ACP_ASS_INVENTORY', + 'auth' => 'ext_phpbbstudio/ass && acl_a_ass_inventory', + 'cat' => ['ACP_ASS_SYSTEM'], + ], + ], + ]; + } +} diff --git a/ext/phpbbstudio/ass/acp/main_module.php b/ext/phpbbstudio/ass/acp/main_module.php new file mode 100644 index 0000000..ab395ab --- /dev/null +++ b/ext/phpbbstudio/ass/acp/main_module.php @@ -0,0 +1,51 @@ +get('language'); + + $request = $phpbb_container->get('request'); + + if ($request->variable('action', '', true) === 'select_file') + { + $mode = 'files'; + } + + /** @var \phpbbstudio\ass\controller\acp_settings_controller $controller */ + $controller = $phpbb_container->get("phpbbstudio.ass.controller.acp.{$mode}"); + + // Set the page title and template + $this->tpl_name = 'ass_' . $mode; + $this->page_title = $language->lang('ACP_ASS_SYSTEM') . ' • ' . $language->lang('ACP_ASS_' . utf8_strtoupper($mode)); + + // Make the custom form action available in the controller and handle the mode + $controller->set_page_url($this->u_action)->{$mode}(); + } +} diff --git a/ext/phpbbstudio/ass/adm/style/ass_errors.html b/ext/phpbbstudio/ass/adm/style/ass_errors.html new file mode 100644 index 0000000..9a71e80 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/ass_errors.html @@ -0,0 +1,14 @@ +{% if ERRORS %} +
      +

      {{ lang('ERROR') }}

      +

      + {% if ERRORS is iterable %} + {% for error in ERRORS %} + {{ lang(error) }}{% if not loop.last %}
      {% endif %} + {% endfor %} + {% else %} + {{ lang(ERRORS) }} + {% endif %} +

      +
      +{% endif %} diff --git a/ext/phpbbstudio/ass/adm/style/ass_files.html b/ext/phpbbstudio/ass/adm/style/ass_files.html new file mode 100644 index 0000000..bc5546a --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/ass_files.html @@ -0,0 +1,199 @@ +{% if S_FILE_SELECT %} + {{ include('simple_header.html') }} + + + + +{% else %} + {{ include('overall_header.html') }} + +

      {{ PAGE_TITLE }}

      +

      {{ lang('ACP_ASS_FILES_' ~ (S_FILE_MODE ? S_FILE_MODE|upper ~ '_') ~ 'EXPLAIN') }}

      +{% endif %} + +{% INCLUDECSS '@phpbbstudio_aps/css/aps_form.css' %} +{% INCLUDECSS '@phpbbstudio_ass/css/ass_common.css' %} + +{% if S_FILE_INDEX %} + +{% else %} +
      + {{ lang('ACP_ASS_FILES') }} + +
      + {% if not S_FILE_SELECT %} +
      + +
      + {% endif %} + + + {%- for crumb in DIRECTORIES -%} + + + {% if loop.last %} + {{ crumb }} + {% else %} + {{ crumb }} + {%- endif -%} + + {% endfor %} +
      + +
      + {% spaceless %} + {% if DIRECTORIES|length %} + + + + {% else %} + + + + {% endif %} + {% if not S_FILE_SELECT %} +
      + + + + {{ S_FORM_TOKEN }} +
      +
      + + + + {{ S_FORM_TOKEN }} +
      + {% endif %} + {% endspaceless %} +
      + +
      + + + + {% if not S_FILE_SELECT %}{% endif %} + + + + + + + {% for folder in ass_folders %} + {% if loop.first %} + + + + {% endif %} + + {% if not S_FILE_SELECT %} + + {% endif %} + + + + + {% endfor %} + + {% for file in ass_files %} + {% if loop.first %} + + + + {% endif %} + + {% if S_FILE_SELECT %} + {% if loop.first %} + + + + {% endif %} + {% else %} + + + + + + + {% endif %} + {% endfor %} + +
      {{ lang('ACTIONS') }}{{ lang('ASS_FILENAME') }}{{ lang('ASS_FILETIME') }}{{ lang('ASS_FILESIZE') }}
      {{ lang('ASS_FOLDERS') }}
      + + + + + + {{ folder.NAME }} + {{ folder.TIME }}
      {{ lang('ASS_FILES') }}
      +
      + {% endif %} + +
      + +
      + + {% if loop.last %} +
      +
      + + + + + + {{ file.NAME }} + {{ file.TIME }}{{ file.SIZE }}
      + + {% if S_FILE_SELECT %} +

      + +

      + {% endif %} +
      +
      +{% endif %} + +{% if S_FILE_SELECT %} + {{ include('simple_footer.html') }} +{% else %} + {{ include('overall_footer.html') }} +{% endif %} diff --git a/ext/phpbbstudio/ass/adm/style/ass_inventory.html b/ext/phpbbstudio/ass/adm/style/ass_inventory.html new file mode 100644 index 0000000..f9747bb --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/ass_inventory.html @@ -0,0 +1,206 @@ +{% include 'overall_header.html' %} + +{% INCLUDECSS '@phpbbstudio_aps/css/aps_form.css' %} +{% INCLUDECSS '@phpbbstudio_aps/css/aps_common.css' %} +{% INCLUDECSS '@phpbbstudio_ass/css/ass_common.css' %} + +

      {{ PAGE_TITLE }}

      +

      {{ lang('ACP_ASS_INVENTORY_EXPLAIN') }}

      + +{% if not S_TYPE %} + +
      + +
      + +{% else %} + +
      + + + {{ lang('BACK') }} + +
      + +
      + {{ include('@phpbbstudio_ass/ass_errors.html') }} + + {% if S_TYPE == 'global' %} + {% INCLUDECSS '@phpbbstudio_ass/css/select2.min.css' %} + {% INCLUDEJS '@phpbbstudio_ass/js/select2.min.js' %} + {% INCLUDEJS '@phpbbstudio_ass/js/ass_common.js' %} + +
      +
      +
      +
      + + +
      +
      +
      +
      +
      + + +
      +
      +
      +
      +
      + +
      +
      +
      +
      +
      + +
      +
      +
      + {% else %} + + {% if U_FIND_USER %} +
      +
      +
      +
      + + +
      +
      +
      + {% else %} + {% INCLUDECSS '@phpbbstudio_ass/css/select2.min.css' %} + {% INCLUDEJS '@phpbbstudio_ass/js/select2.min.js' %} + {% INCLUDEJS '@phpbbstudio_ass/js/ass_common.js' %} + +
      + + + + + + + + + + + + + + + {% for category in categories if category.S_INVENTORY %} + {% for item in category.items if item.S_INVENTORY %} + + + + + + + + + + + {% endfor %} + {% else %} + + + + {% endfor %} + +
      {{ lang('ENABLED') }}{{ lang('ACP_ASS_CONFLICT') }}{{ lang('ASS_ITEM') }}{{ lang('ASS_PURCHASE_TIME') }}{{ lang('ASS_USED_LAST') }}{{ lang('ASS_USAGES') }}{{ lang('ASS_GIFT') }}{{ lang('ACTIONS') }}
      + + + + + {{ category.TITLE }} + + {{ item.TITLE }} + {{ item.PURCHASE_TIME }}{{ item.USE_TIME }}{{ item.USE_COUNT }}{{ item.GIFTER }} + + + {{ lang('DELETE') }} + +
      +
      + +
      + {{ lang('ADD') }} +
      +
      +
      + +
      +
      + + +
      + {% endif %} + + {% endif %} + +
      +

      + {{ S_FORM_TOKEN }} + +   + +

      +
      +
      + +{% endif %} + +{% include 'overall_footer.html' %} diff --git a/ext/phpbbstudio/ass/adm/style/ass_item_form.html b/ext/phpbbstudio/ass/adm/style/ass_item_form.html new file mode 100644 index 0000000..b54963c --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/ass_item_form.html @@ -0,0 +1,351 @@ +{% INCLUDECSS '@phpbbstudio_ass/css/select2.min.css' %} +{% INCLUDEJS '@phpbbstudio_ass/js/select2.min.js' %} +{% INCLUDEJS '@phpbbstudio_ass/js/ass_help.js' %} + + + +{% macro desc(string, italic) %} +
      {{ lang('ACP_ASS_ITEM_' ~ string ~ '_DESC') }} +{% endmacro %} + +{% from _self import desc as desc %} + +
      + {{ lang('ACP_ASS_SETTINGS_TYPE') }} + +
      +
      +
      + +
      +
      +
      + {% if T_ITEM_TEMPLATE %} + {{ include(T_ITEM_TEMPLATE, ignore_missing = true) }} + {% endif %} +
      +
      + +
      + {{ lang('ACP_ASS_SETTINGS_DEFAULT') }} + +
      +
      +
      {{ aps_icon() }}
      +
      +
      +
      {{ desc('STOCK_UNLIMITED') }}
      +
      + + +
      +
      +
      +
      {{ desc('STOCK') }}
      +
      +
      +
      +
      {{ desc('STOCK_THRESHOLD') }}
      +
      +
      +
      + +
      + {{ lang('ACP_ASS_SETTINGS_GIFT') }} + +
      +
      {{ desc('GIFT') }}
      +
      + + +
      +
      +
      +
      {{ desc('GIFT_ONLY') }}
      +
      + + +
      +
      +
      +
      {{ desc('GIFT_TYPE') }}
      +
      + + +
      +
      +
      +
      {{ desc('GIFT_PERCENTAGE') }}
      +
      +
      +
      +
      {{ desc('GIFT_PRICE') }}
      +
      {{ aps_icon() }}
      +
      +
      + +
      + {{ lang('ACP_ASS_SETTINGS_SPECIAL') }} + +
      +
      {{ desc('SALE_PRICE') }}
      +
      {{ aps_icon() }}
      +
      + {% set timezone = + '
      ' ~ + '
      ' ~ lang('ACP_ASS_ITEM_TIMEZONE_BOARD') ~ lang('COLON') ~ '
      ' ~ "now"|date(DATE_FORMAT, TIMEZONE) ~ '
      ' ~ + '
      ' ~ lang('ACP_ASS_ITEM_TIMEZONE_YOUR') ~ lang('COLON') ~ '
      ' ~ "now"|date(DATE_FORMAT) ~ '
      ' ~ + '
      ' + %} +
      +
      + {{ desc('SALE') ~ desc('TIMEZONE', true) }} + +
      +
      + {% spaceless %} + + + +   + + {# #} + + {% endspaceless %} +
      +
      +
      +
      + {{ desc('FEATURED') ~ desc('TIMEZONE', true) }} + +
      +
      + {% spaceless %} + +   + + {# #} + + {% endspaceless %} +
      +
      +
      +
      + {{ desc('AVAILABLE') ~ desc('TIMEZONE', true) }} + +
      +
      + {% spaceless %} + +   + + {# #} + + {% endspaceless %} +
      +
      +
      + +
      + {{ lang('ACP_ASS_SETTINGS_INVENTORY') }} + +
      +
      {{ desc('COUNT') ~ desc('COUNT_ZERO', true) }}
      +
      +
      +
      +
      {{ desc('STACK') }}
      +
      +
      + + {# These strings should NOT be translated, as strtotime() only accept English. #} + {% set str_to_time = ['1 year', '1 week', '5 days 12 hours', '3 weeks 6 days 23 hours 59 minutes 59 seconds'] %} + {% set str_to_time = str_to_time|join('”
      ') %} + {% set str_to_time = '
      ' ~ str_to_time ~ '”' %} + +
      +
      + {{ desc('REFUND') ~ desc('STR_TO_TIME', true) }} + +
      +
      +
      +
      +
      + {{ desc('EXPIRE') ~ desc('STR_TO_TIME', true) }} + +
      +
      +
      +
      +
      + {{ desc('DELETE') ~ desc('STR_TO_TIME', true) }} + +
      +
      +
      +
      + +
      + {{ lang('ACP_ASS_SETTINGS_DISPLAY') }} + +
      +
      {{ desc('BACKGROUND') }}
      +
      + +
      +
      +
      +
      + {% for image in ITEM_IMAGES %} +
      + {% spaceless %} + + {# #} + + {% endspaceless %} +
      + {% endfor %} +
      + {% spaceless %} + + {# #} + + {% endspaceless %} +
      +
      + +
      +
      +
      +
      +
      + + +
      +
      +
      +
      +
      + +
      +
      +
      + +{% if S_ASS_EDIT %} +
      +
      +
      +
      {{ user.format_date(ITEM_CREATE_TIME) }}
      +
      {{ lang('ASS_ITEM_EDIT_TIME') ~ lang('COLON') }}
      {{ ITEM_EDIT_TIME ? user.format_date(ITEM_EDIT_TIME) : lang('NEVER') }}
      +
       
      +
      +
      +
      +
      +
      +
      {{ lang('ASS_FEATURED') ~ lang('COLON') }}
      +
      {{ lang('ACP_ASS_AVAILABLE') ~ lang('COLON') }}
      +
      +
      +
      +
      +
      +
      +
      {{ not ITEM_STOCK_UNLIMITED ? ITEM_STOCK_INITIAL : lang('ASS_UNLIMITED') }}
      +
      {{ lang('ASS_STOCK_CURRENT') ~ lang('COLON') }}
      {{ not ITEM_STOCK_UNLIMITED ? ITEM_STOCK : lang('ASS_UNLIMITED') }}
      +
      {{ lang('ASS_PURCHASES') ~ lang('COLON') }}
      {{ ITEM_PURCHASES }}
      +
      +
      +
      +
      +
      {{ aps_display(ITEM_SALE_PRICE) }}
      +
      {{ lang('ASS_SALE_DISCOUNT') ~ lang('COLON') }}
      {{ aps_display(ITEM_SALE_DIFF) }}
      +
      {{ lang('ASS_SALE_PERCENTAGE') ~ lang('COLON') }}
      -{{ ITEM_SALE_PCT }}%
      +
      +
      +
      +{% endif %} diff --git a/ext/phpbbstudio/ass/adm/style/ass_items.html b/ext/phpbbstudio/ass/adm/style/ass_items.html new file mode 100644 index 0000000..37e077b --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/ass_items.html @@ -0,0 +1,208 @@ +{% include 'overall_header.html' %} + +{% INCLUDECSS '@phpbbstudio_aps/css/aps_form.css' %} +{% INCLUDECSS '@phpbbstudio_aps/css/aps_common.css' %} +{% INCLUDECSS '@phpbbstudio_ass/css/ass_common.css' %} +{% INCLUDEJS '@phpbbstudio_aps/js/jquery-ui-sortable.min.js' %} +{% INCLUDEJS '@phpbbstudio_ass/js/ass_common.js' %} + +{% set mode = S_ITEMS ? 'ITEMS' : 'CATEGORIES' %} +{% set type = S_ITEMS ? 'ITEM' : 'CATEGORY' %} + + + + {{ lang('BACK') }} + + +

      {{ lang('ACP_ASS_SYSTEM') }} • {{ lang('ACP_ASS_' ~ mode) }}

      +

      {{ lang('ACP_ASS_' ~ mode ~ '_EXPLAIN') }}

      + +{% if S_ASS_ADD or S_ASS_EDIT %} +
      + {% INCLUDECSS '@phpbbstudio_aps/css/aps_iconpicker.css' %} + {% INCLUDECSS '@phpbbstudio_aps/css/fontawesome-iconpicker.min.css' %} + {% INCLUDECSS '@phpbbstudio_ass/css/daterangepicker.css' %} + {% INCLUDEJS '@phpbbstudio_aps/js/fontawesome-iconpicker.min.js' %} + {% INCLUDEJS '@phpbbstudio_ass/js/moment.min.js' %} + {% INCLUDEJS '@phpbbstudio_ass/js/daterangepicker.js' %} + + + + {{ include('@phpbbstudio_ass/ass_errors.html', {ERRORS: ASS_ERRORS}) }} + + {% if ITEM_CONFLICT %} +
      +
      +

      {{ lang('ACP_ASS_CONFLICT') }}

      +

      {{ lang('ACP_ASS_CONFLICT_DESC', U_ITEM_ERROR_LOG, lang('ACP_ASS_SETTINGS_TYPE')) }}

      +
      + +
      + {% endif %} + +
      + {{ lang('GENERAL_SETTINGS') }} + + {% if S_ITEMS %} + + {% endif %} + +
      +
      +
      + + +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      + {% include 'acp_posting_buttons.html' %} + +
      + + +
      +
      +
      + + {% if S_ITEMS %} + {{ include('@phpbbstudio_ass/ass_item_form.html') }} + {% endif %} + +
      + {{ lang('ACP_SUBMIT_CHANGES') }} + +

      +   + + {{ S_FORM_TOKEN }} +

      +
      +
      +{% else %} + +
      +
      + {% set rowset = S_ITEMS ? ass_items : ass_categories %} + + + + + + + {% if S_ITEMS %} + + {% endif %} + + + + + + + + {% for row in rowset %} + + + + {% if S_ITEMS %} + + {% endif %} + + + + + + {% else %} + + + + {% endfor %} + +
      {{ lang('ENABLED') }}{{ lang('ACP_ASS_CONFLICT' ~ (not S_ITEMS ? 'S')) }}{{ lang('ACP_ASS_AVAILABLE') }}{{ lang('ACP_ASS_' ~ type ~ '_ICON') }}{{ lang('ACP_ASS_' ~ type ~ '_TITLE') }}{{ lang('ACP_ASS_' ~ type ~ '_SLUG') }}{{ lang('ACTIONS') }}
      + + + + + + + {% if row.ICON %} + + {% else %} + - + {% endif %} + + + {{ row.TITLE }} + + + {{ row.SLUG }} + + {% spaceless %} + {% if row.S_AUTH %} + + + {{ lang('EDIT') }} + +   + {% if S_ITEMS %} + + + {{ lang('ACP_ASS_COPY') }} + +   + {% endif %} + + + {{ lang('DELETE') }} + +   + {% endif %} + + + {{ lang('MOVE') }} + + {% endspaceless %} +
      {{ lang('ASS_' ~ mode ~ '_NONE') }}
      + +
      + +
      + {{ lang('ADD') }} +
      +
      +
      + +{% endif %} + +{% include 'overall_footer.html' %} diff --git a/ext/phpbbstudio/ass/adm/style/ass_logs.html b/ext/phpbbstudio/ass/adm/style/ass_logs.html new file mode 100644 index 0000000..62ddc82 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/ass_logs.html @@ -0,0 +1,113 @@ +{% include 'overall_header.html' %} + +{% INCLUDECSS '@phpbbstudio_aps/css/aps_form.css' %} +{% INCLUDECSS '@phpbbstudio_aps/css/aps_common.css' %} +{% INCLUDECSS '@phpbbstudio_ass/css/ass_common.css' %} + +

      {{ PAGE_TITLE }}

      +

      {{ lang('ACP_ASS_LOGS_EXPLAIN') }}

      + +
      + + + + + + + + + + + + + + + {% for log in ass_logs %} + + + + + + + + + {% else %} + + + + {% endfor %} + +
      {{ lang('USERNAME') }}{{ lang('TIME') }}{{ lang('ACTION') }}{{ lang('ASS_ITEM_TITLE') }}{{ lang('ASS_ITEM_PRICE') }}{{ lang('MARK') }}
      {{ log.USER }}{% if not log.S_SELF and log.REPORTEE %}
      » {{ lang('FROM') ~ ' ' ~ log.REPORTEE }}{% endif %}
      {{ log.LOG_TIME }} + {% if log.S_PURCHASE %} + {% if log.RECIPIENT %} + {{ lang('ASS_LOG_ITEM_GIFTED', log.RECIPIENT) }} + {% else %} + {{ lang('ASS_LOG_ITEM_PURCHASED') }} + {% endif %} + {% else %} + {{ lang('ASS_LOG_ITEM_USED') ~ lang('COLON') }} {{ log.LOG_ACTION }} + {% endif %} + + {{ log.CATEGORY_TITLE }} + + {{ log.ITEM_TITLE }} + {{ aps_display(log.POINTS_SUM, false) }}
      +
      +

      {{ lang('NO_ENTRIES') }}

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

      {{ lang('MARK_ALL') }}{{ lang('UNMARK_ALL') }}

      +
      +
      + +{% include 'overall_footer.html' %} diff --git a/ext/phpbbstudio/ass/adm/style/ass_overview.html b/ext/phpbbstudio/ass/adm/style/ass_overview.html new file mode 100644 index 0000000..b2646d9 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/ass_overview.html @@ -0,0 +1,440 @@ +{% include 'overall_header.html' %} + +{% INCLUDECSS '@phpbbstudio_aps/css/aps_form.css' %} +{% INCLUDECSS '@phpbbstudio_aps/css/aps_common.css' %} +{% INCLUDECSS '@phpbbstudio_ass/css/ass_common.css' %} + +

      {{ PAGE_TITLE }}

      +

      {{ lang('ACP_ASS_OVERVIEW_EXPLAIN') }}

      + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      {{ lang('STATISTIC') }}{{ lang('VALUE') }}{{ lang('STATISTIC') }}{{ lang('VALUE') }}
      {{ lang('ACP_ASS_NUMBER_ITEMS') ~ lang('COLON') }}{{ COUNTS['items'] }}{{ lang('ACP_ASS_NUMBER_CONFLICTS') ~ lang('COLON') }}{{ COUNTS['errors'] }}
      {{ lang('ACP_ASS_NUMBER_SALE') ~ lang('COLON') }}{{ COUNTS['sale'] }}{{ lang('ACP_ASS_NUMBER_SPENT', aps_name()) ~ lang('COLON') }}{{ aps_display(COUNTS['spent'], false) }}
      {{ lang('ACP_ASS_NUMBER_FEATURED') ~ lang('COLON') }}{{ COUNTS['featured'] }}{{ lang('ACP_ASS_SHOP_ENABLED') ~ lang('COLON') }}
      {{ lang('ACP_ASS_NUMBER_CATEGORIES') ~ lang('COLON') }}{{ COUNTS['categories'] }}{{ lang('ACP_ASS_SHOP_ACTIVE') ~ lang('COLON') }}
      {{ lang('ACP_ASS_NUMBER_PURCHASES') ~ lang('COLON') }}{{ COUNTS['purchases'] }}{{ lang('ACP_ASS_GIFTING_ENABLED') ~ lang('COLON') }}
      + +
      + {{ lang('ACP_ASS_NOTES') }} + +
      + {% if S_NOTES %} + + {% else %} + +
      + {% if NOTES %} + {{ NOTES }} + {% else %} + {{ lang('ACP_ASS_NOTES_NO') ~ lang('ELLIPSIS') }} + {% endif %} +
      + {% endif %} + + {% if S_NOTES %} +
      + + + {{ lang('CANCEL') }} +
      + {% endif %} +
      +
      + +
      + {{ lang('ACP_ASS_PANEL_FEATURED') }} + +
        + {% for item in featured %} +
      • +
        + {% if item.BACKGROUND_SRC %} + {{ item.TITLE }} + {% else %} + + {% endif %} +
        +
        +
        + {{ item.TITLE }}
        + + + {{ user.format_date(item.FEATURED_UNTIL_UNIX) }} + +
        +
        +
      • + {% else %} +
      • +
        + {{ lang('ACP_ASS_OVERVIEW_FEATURED_NO') }} +
        +
      • + {% endfor %} +
      +
      + +
      + {{ lang('ACP_ASS_OVERVIEW_FEATURED_UPCOMING') }} + +
        + {% for item in featured_coming %} +
      • +
        + {% if item.BACKGROUND_SRC %} + {{ item.TITLE }} + {% else %} + + {% endif %} +
        +
        +
        + {{ item.TITLE }}
        + + + {{ user.format_date(item.FEATURED_START_UNIX) }} + +
        +
        +
      • + {% else %} +
      • +
        + {{ lang('ACP_ASS_OVERVIEW_FEATURED_UPCOMING_NO') }} +
        +
      • + {% endfor %} +
      +
      + +
      + {{ lang('ACP_ASS_PANEL_SALE') }} + +
        + {% for item in sale %} +
      • +
        + {% if item.BACKGROUND_SRC %} + {{ item.TITLE }} + {% else %} + + {% endif %} +
        +
        +
        + {{ item.TITLE }}
        + + + {{ user.format_date(item.SALE_UNTIL_UNIX) }} + +
        +
        +
      • + {% else %} +
      • +
        + {{ lang('ACP_ASS_OVERVIEW_SALE_NO') }} +
        +
      • + {% endfor %} +
      +
      + +
      + {{ lang('ACP_ASS_OVERVIEW_SALE_UPCOMING') }} + +
        + {% for item in sale_coming %} +
      • +
        + {% if item.BACKGROUND_SRC %} + {{ item.TITLE }} + {% else %} + + {% endif %} +
        +
        +
        + {{ item.TITLE }}
        + + + {{ user.format_date(item.SALE_START_UNIX) }} + +
        +
        +
      • + {% else %} +
      • +
        + {{ lang('ACP_ASS_OVERVIEW_SALE_UPCOMING_NO') }} +
        +
      • + {% endfor %} +
      +
      + +
      + {{ lang('ACP_ASS_OVERVIEW_LOW_STOCK') }} + +
        + {% for item in low_stock %} +
      • +
        + {% if item.BACKGROUND_SRC %} + {{ item.TITLE }} + {% else %} + + {% endif %} +
        +
        {{ item.TITLE }}
        +
        {{ item.STOCK }}
        +
      • + {% else %} +
      • +
        + {{ lang('ACP_ASS_OVERVIEW_LOW_STOCK_NO') }} +
        +
      • + {% endfor %} +
      +
      + +
      + {{ lang('ACP_ASS_OVERVIEW_BIGGEST_GIFTERS') }} + +
        + {% for user in gifters %} +
      • +
        + {% if user.AVATAR %} + {{ user.AVATAR }} + {% else %} + + {% endif %} +
        +
        {{ user.NAME }}
        +
        {{ user.COUNT }}
        +
      • + {% else %} +
      • +
        + {{ lang('ACP_ASS_OVERVIEW_BIGGEST_GIFTERS_NO') }} +
        +
      • + {% endfor %} +
      +
      + +
      + {{ lang('ACP_ASS_OVERVIEW_BIGGEST_BUYERS') }} + +
        + {% for user in buyers %} +
      • +
        + {% if user.AVATAR %} + {{ user.AVATAR }} + {% else %} + + {% endif %} +
        +
        {{ user.NAME }}
        +
        {{ user.COUNT }}
        +
      • + {% else %} +
      • +
        + {{ lang('ACP_ASS_OVERVIEW_BIGGEST_BUYERS_NO') }} +
        +
      • + {% endfor %} +
      +
      + +
      + {{ lang('ACP_ASS_OVERVIEW_BIGGEST_SPENDERS') }} + +
        + {% for user in spenders %} +
      • +
        + {% if user.AVATAR %} + {{ user.AVATAR }} + {% else %} + + {% endif %} +
        +
        {{ user.NAME }}
        +
        {{ aps_display(user.COUNT, false) }}
        +
      • + {% else %} +
      • +
        + {{ lang('ACP_ASS_OVERVIEW_BIGGEST_SPENDERS_NO') }} +
        +
      • + {% endfor %} +
      +
      + +
      + {{ lang('ACP_ASS_OVERVIEW_SELLERS_LOW') }} + +
        + {% for item in low_sellers %} +
      • +
        + {% if item.BACKGROUND_SRC %} + {{ item.TITLE }} + {% else %} + + {% endif %} +
        +
        {{ item.TITLE }}
        +
        {{ item.PURCHASES }}
        +
      • + {% else %} +
      • +
        + {{ lang('ACP_ASS_OVERVIEW_SELLERS_LOW_NO') }} +
        +
      • + {% endfor %} +
      +
      + +
      + {{ lang('ACP_ASS_OVERVIEW_SELLERS_TOP') }} + +
        + {% for item in top_sellers %} +
      • +
        + {% if item.BACKGROUND_SRC %} + {{ item.TITLE }} + {% else %} + + {% endif %} +
        +
        {{ item.TITLE }}
        +
        {{ item.PURCHASES }}
        +
      • + {% else %} +
      • +
        + {{ lang('ACP_ASS_OVERVIEW_SELLERS_TOP_NO') }} +
        +
      • + {% endfor %} +
      +
      + +
      + {{ lang('ACP_ASS_OVERVIEW_RECENT_ITEMS') }} + +
        + {% for item in recent %} +
      • +
        + {% if item.BACKGROUND_SRC %} + {{ item.TITLE }} + {% else %} + + {% endif %} +
        +
        +
        + {{ item.TITLE }}
        + + + {{ user.format_date(item.CREATE_TIME) }} + +
        +
        +
      • + {% else %} +
      • +
        + {{ lang('ACP_ASS_OVERVIEW_RECENT_ITEMS_NO') }} +
        +
      • + {% endfor %} +
      +
      + +
      + {{ lang('ACP_ASS_OVERVIEW_RECENT_PURCHASES') }} + +
        + {% for item in purchases %} +
      • +
        + {% if item.BACKGROUND_SRC %} + {{ item.TITLE }} + {% else %} + + {% endif %} +
        +
        +
        + {{ item.TITLE }}
        + + + {{ user.format_date(item.PURCHASE_TIME) }} + +
        +
        +
      • + {% else %} +
      • +
        + {{ lang('ACP_ASS_OVERVIEW_RECENT_PURCHASES_NO') }} +
        +
      • + {% endfor %} +
      +
      +
      + +{% include 'overall_footer.html' %} diff --git a/ext/phpbbstudio/ass/adm/style/ass_settings.html b/ext/phpbbstudio/ass/adm/style/ass_settings.html new file mode 100644 index 0000000..7b5e82e --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/ass_settings.html @@ -0,0 +1,350 @@ +{% include 'overall_header.html' %} + +{% INCLUDECSS '@phpbbstudio_aps/css/aps_form.css' %} +{% INCLUDECSS '@phpbbstudio_aps/css/aps_common.css' %} +{% INCLUDECSS '@phpbbstudio_ass/css/ass_common.css' %} +{% INCLUDECSS '@phpbbstudio_aps/css/aps_iconpicker.css' %} +{% INCLUDECSS '@phpbbstudio_aps/css/fontawesome-iconpicker.min.css' %} +{% INCLUDEJS '@phpbbstudio_aps/js/fontawesome-iconpicker.min.js' %} +{% INCLUDEJS '@phpbbstudio_ass/js/ass_common.js' %} + +{% if S_ASS_LOCATIONS %} + {% include '@phpbbstudio_aps/aps_locations.html' %} +{% else %} + +

      {{ PAGE_TITLE }}

      +

      {{ lang('ACP_ASS_SETTINGS_EXPLAIN') }}

      + +
      + {{ include('@phpbbstudio_ass/ass_errors.html') }} + +
      + {{ lang('GENERAL_SETTINGS') }} + +
      +
      + +
      {{ lang('ACP_ASS_SHOP_ENABLED_DESC') }} +
      +
      + + +
      +
      +
      +
      + +
      {{ lang('ACP_ASS_SHOP_ACTIVE_DESC') }} +
      +
      + + +
      +
      +
      +
      + +
      {{ lang('ACP_ASS_SHOP_INACTIVE_DESC_DESC') }} +
      +
      + + {% include 'acp_posting_buttons.html' %} +
      +
      +
      +
      + {{ lang('ACP_ASS_LOCATIONS') }} +
      {{ lang('ACP_ASS_LOCATIONS_DESC') }} +
      +
      + +
      +
      +
      +
      +
      + +
      +
      +
      +
      +
      + +
      +
      +
      +
      +
      + +
      +
      +
      +
      +
      + +
      +
      +
      +
      +
      + + +
      +
      +
      +
      +
      + + +
      +
      +
      +
      + +
      {{ lang('ACP_ASS_PURGE_CACHE_DESC') }} +
      +
      + + +
      +
      +
      +
      +
      + + {{ ITEMS_PER_PAGE }} +
      +
      +
      +
      +
      + + {{ LOGS_PER_PAGE }} +
      +
      +
      + +
      + {{ lang('ACP_ASS_SETTINGS_SHOP') }} + + {% for row in SHOP_BLOCKS|batch(2) %} + {% for type, data in row %} +
      +
      {{ lang('ACP_ASS_PANEL_' ~ type|upper) }}
      +
      + + +
      +
      +
      + +
      +
      +
      +
      +
      + +
      +
      +
      +
      +
      + +
      +
      +
      +
      +
      + +
      +
      +
      +
      +
      +
      +
      +
      +
      + {% if type == 'random' %} + + {% endif %} + + {% if type != 'random' %} + + {% endif %} +
      +
      +
      +
      +
      + + {{ LIMIT[type] }} +
      +
      +
      + {% endfor %} + {% endfor %} +
      + +
      + {{ lang('ACP_ASS_SETTINGS_CAROUSEL') }} + +
      +
      + +
      {{ lang('ACP_ASS_CAROUSEL_ARROWS_DESC') }} +
      +
      + + +
      +
      +
      +
      + +
      {{ lang('ACP_ASS_CAROUSEL_DOTS_DESC') }} +
      +
      + + +
      +
      +
      +
      + +
      {{ lang('ACP_ASS_CAROUSEL_FADE_DESC') }} +
      +
      + + +
      +
      +
      +
      + +
      {{ lang('ACP_ASS_CAROUSEL_PLAY_DESC') }} +
      +
      + + +
      +
      +
      +
      +
      {{ lang('ACP_ASS_CAROUSEL_PLAY_SPEED_DESC') }} +
      +
      + + {{ CAROUSEL_PLAY_SPEED }} +
      +
      +
      +
      + +
      {{ lang('ACP_ASS_CAROUSEL_SPEED_DESC') }} +
      +
      + + {{ CAROUSEL_SPEED }} +
      +
      +
      + +
      + {{ lang('ACP_SUBMIT_CHANGES') }} + +

      +   + + {{ S_FORM_TOKEN }} +

      +
      +
      + +{% endif %} + +{% include 'overall_footer.html' %} diff --git a/ext/phpbbstudio/ass/adm/style/css/ass_common.css b/ext/phpbbstudio/ass/adm/style/css/ass_common.css new file mode 100644 index 0000000..9f32a17 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/css/ass_common.css @@ -0,0 +1,590 @@ +/** + * + * phpBB Studio - Advanced Shop System. An extension for the phpBB Forum Software package. + * + * @copyright (c) 2019, phpBB Studio, https://www.phpbbstudio.com + * @license GNU General Public License, version 2 (GPL-2.0) + * + */ +.name i { font-size: 14px; } + +.ass-pointer { + cursor: pointer; +} + +.ass-width-200 { + width: 200px; +} + +.ass-width-90p { + width: 90%; +} + +.ass-vertical-resize { + resize: vertical; +} + +.ass-inline { + display: inline-block; +} + +.ass-label-fix { + vertical-align: 2px; + padding-right: 0; +} + +.ass-actions { + padding: 8px; +} + +.ass-no-mar { + margin: 0; +} + +.ass-mar { + margin: 8px; +} + +.ass-mar-left { + margin-left: 8px; +} + +.ass-mar-right { + margin-right: 8px; +} + +.ass-mar-right-half { + margin-right: 4px; +} + +.ass-mar-side { + margin-right: 8px; + margin-left: 8px; +} + +.ass-no-radius-top { + border-top-left-radius: 0 !important; + border-top-right-radius: 0 !important; +} + +.ass-no-radius-right { + border-top-right-radius: 0 !important; + border-bottom-right-radius: 0 !important; +} + +.ass-no-radius-bottom { + border-bottom-right-radius: 0 !important; + border-bottom-left-radius: 0 !important; +} + +.ass-no-radius-left { + border-top-left-radius: 0 !important; + border-bottom-left-radius: 0 !important; +} + +.ass-color-palette table { + border-color: #12a3eb; + border-radius: 4px; + min-width: 90%; + max-width: 90%; + margin: 0 0 4px; + padding: 4px; +} + +.ass-color-palette a { + width: 100% !important; +} + +.ass-radio { display: none; } + +.ass-radio:checked + .aps-button-blue, +input[type="button"]:hover { + background: #12a3eb; + border-color: #12a3eb; + color: #ffffff; +} + +input[type="button"] { + background-color: #ffffff; + background-image: none; + border: 1px solid #12a3eb; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -webkit-transition: border-color ease-in-out 0.15s, -webkit-box-shadow ease-in-out 0.15s; + -o-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; +} + +.ass-input-icon i { + font-size: 14px; + line-height: 1.42857143; + text-align: center; + vertical-align: -1px; + background-color: #f3f3f3; + border: 1px solid #cccccc; + border-right: none; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + color: #555555; + display: inline-block; + width: auto; + min-width: 40px; + height: 34px; + padding: 6px 12px; +} + +.ass-input-icon i + input[type="text"], +.ass-input-icon i + input[type="number"] { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +.ass-file-select-container { + display: flex; + flex-flow: row wrap; +} + +.ass-file-select { + flex: 1 0 210px; +} + +.ass-file-select input { + display: none; +} + +.ass-file-select input + * { + border: 2px solid transparent; + display: inline-block; + width: 200px; + height: auto; +} + +.ass-file-select input:checked + * { + border-color: #12a3eb; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.5); +} + +.ass-button-pulse:not(:hover) { + animation: bgPulse 3s linear 3; +} + +@keyframes bgPulse { + 0% { background-color: #ffffff; } + 50% { background-color: #8dcc8d; } + 100% { background-color: #ffffff; } +} + +/* Panel icons */ +.icon-blue { color: #196db5; } +.icon-green { color: #1b9a1b; } +.icon-red { color: #bc2a4d; } +.icon-orange { color: #ff6600; } +.icon-bluegray { color: #536482; } +.icon-gray { color: #777777; } +.icon-lightgray { color: #999999; } +.icon-black { color: #333333; } +.icon-white { color: #ffffff; } +.icon-lighten { color: rgba(255, 255, 255, 0.75); } +.icon-darken { color: rgba(0, 0, 0, 0.5); } +.icon-aqua { color: #18a39b; } +.icon-yellow { color: #f8b739; } +.icon-pink { color: #ed2861; } +.icon-violet { color: #c12680; } +.icon-purple { color: #5d3191; } +.icon-gold { color: #d6af36; } +.icon-silver { color: #a7a7ad; } +.icon-bronze { color: #824a02; } + +.shop-panel-icon { + text-align: center; + position: absolute; + top: 0; + right: 16px; + width: 36px; +} + +.shop-panel-icon:before { + font-size: 20px; + line-height: 30px; + position: relative; + z-index: 1; +} + +.shop-panel-icon-small:before { + font-size: 16px; + line-height: 22px; +} + +.shop-panel-icon-tiny:before { + font-size: 13px; + line-height: 18px; +} + +.shop-panel-icon:after { + font-family: FontAwesome, sans-serif; + font-size: 48px; + text-shadow: -2px 1px 4px rgba(0, 0, 0, 0.4); + color: #12a3eb; + position: absolute; + top: -4px; + left: 0; + content: "\f02e"; +} + +.shop-panel-icon-small:after, +.shop-panel-icon-tiny:after { + text-shadow: -1px 1px 3px rgba(0, 0, 0, 0.4); +} + +.shop-panel-icon-small { width: 30px; } +.shop-panel-icon-tiny { width: 25px; } + +.shop-panel-icon-small:after { font-size: 40px; } +.shop-panel-icon-tiny:after { font-size: 32px; } + +.shop-panel-icon-blue:after { color: #12a3eb; } +.shop-panel-icon-red:after { color: #d31141; } +.shop-panel-icon-green:after { color: #28a745; } +.shop-panel-icon-orange:after { color: #f06045; } +.shop-panel-icon-aqua:after { color: #18a39b; } +.shop-panel-icon-yellow:after { color: #f8b739; } +.shop-panel-icon-pink:after { color: #ed2861; } +.shop-panel-icon-violet:after { color: #c12680; } +.shop-panel-icon-purple:after { color: #5d3191; } +.shop-panel-icon-gold:after { color: #d6af36; } +.shop-panel-icon-silver:after { color: #a7a7ad; } +.shop-panel-icon-bronze:after { color: #824a02; } + +/* Date picker */ +.daterangepicker .calendar-table th { + text-transform: none; + background: #12a3eb; + padding: 0; +} + +.daterangepicker .drp-calendar.left .calendar-table th:empty { display: none; } +.daterangepicker .drp-calendar.right .calendar-table th:empty { background: none; } +.daterangepicker .calendar-table td { padding: 0; } +.daterangepicker .calendar-table .active { background: #0076b1; } + +/* Slider */ +output { + font-weight: bold; + text-align: center; + display: block; +} + +.shop-slider { + width: 100%; + margin: 0; + padding: 0; + -webkit-appearance: none; +} + +.aps-form [type="range"].shop-slider { + border: none; + margin-top: 4px; + padding: 0; +} + +.shop-slider:focus { + outline: none; +} + +.shop-slider::-webkit-slider-runnable-track { + background: #ffffff; + border: 1px solid #cccccc; + border-radius: 4px; + box-shadow: 0 0 0 #000000, 0 0 0 #0d0d0d; + width: 100%; + height: 34px; + cursor: pointer; +} + +.shop-slider::-webkit-slider-thumb { + background: #12a3eb; + border: 1px solid rgba(0, 118, 177, 0.57); + border-radius: 4px; + box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5), 0 0 1px rgba(13, 13, 13, 0.5); + width: 24px; + height: 24px; + margin-top: 4px; + cursor: pointer; + -webkit-appearance: none; +} + +.shop-slider:focus::-webkit-slider-runnable-track { + background: #ffffff; +} + +.shop-slider::-moz-range-track { + background: #ffffff; + border: 1px solid #cccccc; + border-radius: 4px; + width: 100%; + height: 34px; + cursor: pointer; +} + +.shop-slider::-moz-range-thumb { + background: #12a3eb; + border: 1px solid rgba(0, 118, 177, 0.57); + border-radius: 4px; + box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5), 0 0 1px rgba(13, 13, 13, 0.5); + width: 24px; + height: 24px; + cursor: pointer; +} + +.shop-slider::-ms-track { + background: transparent; + border-color: transparent; + color: transparent; + width: 100%; + height: 34px; + cursor: pointer; +} + +.shop-slider::-ms-fill-lower { + background: #f2f2f2; + border: 1px solid #cccccc; + border-radius: 8px; +} + +.shop-slider::-ms-fill-upper { + background: #ffffff; + border: 1px solid #cccccc; + border-radius: 8px; +} + +.shop-slider::-ms-thumb { + background: #12a3eb; + border: 1px solid rgba(0, 118, 177, 0.57); + border-radius: 4px; + box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5), 0 0 1px rgba(13, 13, 13, 0.5); + width: 24px; + height: 24px; + cursor: pointer; +} + +.shop-slider:focus::-ms-fill-lower { background: #ffffff; } +.shop-slider:focus::-ms-fill-upper { background: #ffffff; } + +/* Overview page */ +.ass-overview, +.ass-overview * { + box-sizing: border-box; +} + +.ass-overview { + display: flex; + justify-content: space-between; + align-items: stretch; + flex-flow: row wrap; +} + +.ass-overview > * { + flex: 0 0 24%; +} + +.ass-overview > .ass-overview-50 { flex-basis: calc(50% - (4% / 6)); } + +@media all and (max-width: 1200px) { + .ass-overview > * { flex-basis: calc(50% - (4% / 6)); } +} + +@media all and (max-width: 700px) { + .ass-overview > .ass-overview-50, + .ass-overview > * { flex-basis: 100%; } +} + +.ass-overview-list { + min-height: 305px; + list-style: none; +} + +.ass-overview-list > :not(:last-child) { + border-bottom: 1px solid #d7d7d7; + margin-bottom: 5px; + padding-bottom: 5px; +} + +.ass-overview-flex { + display: flex; +} + +.ass-overview-flex-auto { + flex: 1 1 auto; + padding: 0 8px; +} + +.ass-overview-flex-small { + text-align: center; + flex: 0 0 52px; +} + +.ass-overview-flex-small img { + width: 50px; + height: auto; + max-height: 52px; +} + +.ass-overview-flex-auto, +.ass-overview-flex-small { + display: flex; + align-items: center; + height: 52px; +} + +.ass-overview-flex-auto > *, +.ass-overview-flex-small > * { + flex: 1 0 100%; +} + +.ass-overview-flex-full-height { + height: 305px; +} + +/* Item help */ +.ass-help-body { margin-bottom: 400px; } + +.ass-help-toolbox, +.ass-help-toolbox * { + box-sizing: border-box; +} + +.ass-help-toolbox { + background: #fafafa; + border-top: 2px solid #12a3eb; + -webkit-box-shadow: 0 24px 38px 3px rgba(0, 0, 0, 0.14), 0 9px 46px 8px rgba(0, 0, 0, 0.12), 0 11px 15px -7px rgba(0, 0, 0, 0.2); + box-shadow: 0 24px 38px 3px rgba(0, 0, 0, 0.14), 0 9px 46px 8px rgba(0, 0, 0, 0.12), 0 11px 15px -7px rgba(0, 0, 0, 0.2); + position: fixed; + z-index: 50; + right: 0; + bottom: 0; + left: 0; + height: 400px; +} + +.ass-help-flexbox { + position: relative; + z-index: 51; + display: flex; + overflow: hidden; + -webkit-overflow-scrolling: touch; + width: 100%; + height: 100%; + scroll-behavior: smooth; + scroll-snap-type: x mandatory; +} + +.ass-help-flexbox > div { + flex-shrink: 0; + width: 100%; + height: 100%; + scroll-snap-align: start; +} + +.ass-help-flexbox > div > h3 { + font-size: 24px; + text-align: center; + border-bottom: 1px solid #12a3eb; + color: #12a3eb; + width: 75%; + margin: 0 auto; + padding: 16px; +} + +.ass-help-flexbox > div > div { + font-size: 16px; + background: #ffffff; + color: #313131; + height: 100%; + padding: 16px; +} + +.ass-help-flexbox > div > div > a { + font-weight: bold; + letter-spacing: 0.05em; + color: #12a3eb; +} + +.ass-help-flexbox > div > div > i { + font-family: monospace; + color: #000000; + quotes: "“" "”" "‘" "’"; +} + +.ass-help-flexbox > div > div > i:before, +.ass-help-flexbox > div > div > i:after { color: #12a3eb; } + +.ass-help-flexbox > div > div > i:before { content: open-quote; } +.ass-help-flexbox > div > div > i:after { content: close-quote; } + +.ass-help-active { + background: #ffffff; + border: 1px solid #12a3eb; + border-radius: 4px; + -webkit-box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12), 0 2px 4px -1px rgba(0, 0, 0, 0.3); + box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12), 0 2px 4px -1px rgba(0, 0, 0, 0.3); + position: relative; + z-index: 45; + margin-right: 32px; + padding: 16px 8px; + transition: all 0.75s ease-in-out; + pointer-events: none; +} + +.ass-help-start { + border-color: #cccccc; + border-bottom: none; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + outline: none; + position: absolute; + top: -26px; + right: 8px; +} + +@media all and (max-width: 1024px) { + .ass-help-start, + .ass-help-toolbox { + display: none; + } +} + +.ass-help-start:hover { border-color: #28a745; } + +.ass-help-next, +.ass-help-prev, +.ass-help-close { + position: fixed; + z-index: 52; + top: 16px; + right: 16px; +} + +.ass-help-next, +.ass-help-prev { + top: unset; + bottom: 350px; +} + +.ass-help-prev { + right: unset; + left: 16px; +} + +/* Inventory */ +.ass-button-inventory { + display: block; + height: auto; + padding: 16px; +} + +.ass-button-inventory > span { + font-size: 24px; + display: block; + margin-bottom: 16px; +} diff --git a/ext/phpbbstudio/ass/adm/style/css/daterangepicker.css b/ext/phpbbstudio/ass/adm/style/css/daterangepicker.css new file mode 100644 index 0000000..a963804 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/css/daterangepicker.css @@ -0,0 +1,410 @@ +.daterangepicker { + position: absolute; + color: inherit; + background-color: #fff; + border-radius: 4px; + border: 1px solid #ddd; + width: 278px; + max-width: none; + padding: 0; + margin-top: 7px; + top: 100px; + left: 20px; + z-index: 3001; + display: none; + font-family: arial; + font-size: 15px; + line-height: 1em; +} + +.daterangepicker:before, .daterangepicker:after { + position: absolute; + display: inline-block; + border-bottom-color: rgba(0, 0, 0, 0.2); + content: ''; +} + +.daterangepicker:before { + top: -7px; + border-right: 7px solid transparent; + border-left: 7px solid transparent; + border-bottom: 7px solid #ccc; +} + +.daterangepicker:after { + top: -6px; + border-right: 6px solid transparent; + border-bottom: 6px solid #fff; + border-left: 6px solid transparent; +} + +.daterangepicker.opensleft:before { + right: 9px; +} + +.daterangepicker.opensleft:after { + right: 10px; +} + +.daterangepicker.openscenter:before { + left: 0; + right: 0; + width: 0; + margin-left: auto; + margin-right: auto; +} + +.daterangepicker.openscenter:after { + left: 0; + right: 0; + width: 0; + margin-left: auto; + margin-right: auto; +} + +.daterangepicker.opensright:before { + left: 9px; +} + +.daterangepicker.opensright:after { + left: 10px; +} + +.daterangepicker.drop-up { + margin-top: -7px; +} + +.daterangepicker.drop-up:before { + top: initial; + bottom: -7px; + border-bottom: initial; + border-top: 7px solid #ccc; +} + +.daterangepicker.drop-up:after { + top: initial; + bottom: -6px; + border-bottom: initial; + border-top: 6px solid #fff; +} + +.daterangepicker.single .daterangepicker .ranges, .daterangepicker.single .drp-calendar { + float: none; +} + +.daterangepicker.single .drp-selected { + display: none; +} + +.daterangepicker.show-calendar .drp-calendar { + display: block; +} + +.daterangepicker.show-calendar .drp-buttons { + display: block; +} + +.daterangepicker.auto-apply .drp-buttons { + display: none; +} + +.daterangepicker .drp-calendar { + display: none; + max-width: 270px; +} + +.daterangepicker .drp-calendar.left { + padding: 8px 0 8px 8px; +} + +.daterangepicker .drp-calendar.right { + padding: 8px; +} + +.daterangepicker .drp-calendar.single .calendar-table { + border: none; +} + +.daterangepicker .calendar-table .next span, .daterangepicker .calendar-table .prev span { + color: #fff; + border: solid black; + border-width: 0 2px 2px 0; + border-radius: 0; + display: inline-block; + padding: 3px; +} + +.daterangepicker .calendar-table .next span { + transform: rotate(-45deg); + -webkit-transform: rotate(-45deg); +} + +.daterangepicker .calendar-table .prev span { + transform: rotate(135deg); + -webkit-transform: rotate(135deg); +} + +.daterangepicker .calendar-table th, .daterangepicker .calendar-table td { + white-space: nowrap; + text-align: center; + vertical-align: middle; + min-width: 32px; + width: 32px; + height: 24px; + line-height: 24px; + font-size: 12px; + border-radius: 4px; + border: 1px solid transparent; + white-space: nowrap; + cursor: pointer; +} + +.daterangepicker .calendar-table { + border: 1px solid #fff; + border-radius: 4px; + background-color: #fff; +} + +.daterangepicker .calendar-table table { + width: 100%; + margin: 0; + border-spacing: 0; + border-collapse: collapse; +} + +.daterangepicker td.available:hover, .daterangepicker th.available:hover { + background-color: #eee; + border-color: transparent; + color: inherit; +} + +.daterangepicker td.week, .daterangepicker th.week { + font-size: 80%; + color: #ccc; +} + +.daterangepicker td.off, .daterangepicker td.off.in-range, .daterangepicker td.off.start-date, .daterangepicker td.off.end-date { + background-color: #fff; + border-color: transparent; + color: #999; +} + +.daterangepicker td.in-range { + background-color: #ebf4f8; + border-color: transparent; + color: #000; + border-radius: 0; +} + +.daterangepicker td.start-date { + border-radius: 4px 0 0 4px; +} + +.daterangepicker td.end-date { + border-radius: 0 4px 4px 0; +} + +.daterangepicker td.start-date.end-date { + border-radius: 4px; +} + +.daterangepicker td.active, .daterangepicker td.active:hover { + background-color: #357ebd; + border-color: transparent; + color: #fff; +} + +.daterangepicker th.month { + width: auto; +} + +.daterangepicker td.disabled, .daterangepicker option.disabled { + color: #999; + cursor: not-allowed; + text-decoration: line-through; +} + +.daterangepicker select.monthselect, .daterangepicker select.yearselect { + font-size: 12px; + padding: 1px; + height: auto; + margin: 0; + cursor: default; +} + +.daterangepicker select.monthselect { + margin-right: 2%; + width: 56%; +} + +.daterangepicker select.yearselect { + width: 40%; +} + +.daterangepicker select.hourselect, .daterangepicker select.minuteselect, .daterangepicker select.secondselect, .daterangepicker select.ampmselect { + width: 50px; + margin: 0 auto; + background: #eee; + border: 1px solid #eee; + padding: 2px; + outline: 0; + font-size: 12px; +} + +.daterangepicker .calendar-time { + text-align: center; + margin: 4px auto 0 auto; + line-height: 30px; + position: relative; +} + +.daterangepicker .calendar-time select.disabled { + color: #ccc; + cursor: not-allowed; +} + +.daterangepicker .drp-buttons { + clear: both; + text-align: right; + padding: 8px; + border-top: 1px solid #ddd; + display: none; + line-height: 12px; + vertical-align: middle; +} + +.daterangepicker .drp-selected { + display: inline-block; + font-size: 12px; + padding-right: 8px; +} + +.daterangepicker .drp-buttons .btn { + margin-left: 8px; + font-size: 12px; + font-weight: bold; + padding: 4px 8px; +} + +.daterangepicker.show-ranges.single.rtl .drp-calendar.left { + border-right: 1px solid #ddd; +} + +.daterangepicker.show-ranges.single.ltr .drp-calendar.left { + border-left: 1px solid #ddd; +} + +.daterangepicker.show-ranges.rtl .drp-calendar.right { + border-right: 1px solid #ddd; +} + +.daterangepicker.show-ranges.ltr .drp-calendar.left { + border-left: 1px solid #ddd; +} + +.daterangepicker .ranges { + float: none; + text-align: left; + margin: 0; +} + +.daterangepicker.show-calendar .ranges { + margin-top: 8px; +} + +.daterangepicker .ranges ul { + list-style: none; + margin: 0 auto; + padding: 0; + width: 100%; +} + +.daterangepicker .ranges li { + font-size: 12px; + padding: 8px 12px; + cursor: pointer; +} + +.daterangepicker .ranges li:hover { + background-color: #eee; +} + +.daterangepicker .ranges li.active { + background-color: #08c; + color: #fff; +} + +/* Larger Screen Styling */ +@media (min-width: 564px) { + .daterangepicker { + width: auto; + } + + .daterangepicker .ranges ul { + width: 140px; + } + + .daterangepicker.single .ranges ul { + width: 100%; + } + + .daterangepicker.single .drp-calendar.left { + clear: none; + } + + .daterangepicker.single .ranges, .daterangepicker.single .drp-calendar { + float: left; + } + + .daterangepicker { + direction: ltr; + text-align: left; + } + + .daterangepicker .drp-calendar.left { + clear: left; + margin-right: 0; + } + + .daterangepicker .drp-calendar.left .calendar-table { + border-right: none; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + + .daterangepicker .drp-calendar.right { + margin-left: 0; + } + + .daterangepicker .drp-calendar.right .calendar-table { + border-left: none; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + + .daterangepicker .drp-calendar.left .calendar-table { + padding-right: 8px; + } + + .daterangepicker .ranges, .daterangepicker .drp-calendar { + float: left; + } +} + +@media (min-width: 730px) { + .daterangepicker .ranges { + width: auto; + } + + .daterangepicker .ranges { + float: left; + } + + .daterangepicker.rtl .ranges { + float: right; + } + + .daterangepicker .drp-calendar.left { + clear: none !important; + } +} diff --git a/ext/phpbbstudio/ass/adm/style/css/select2.css b/ext/phpbbstudio/ass/adm/style/css/select2.css new file mode 100644 index 0000000..750b320 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/css/select2.css @@ -0,0 +1,481 @@ +.select2-container { + box-sizing: border-box; + display: inline-block; + margin: 0; + position: relative; + vertical-align: middle; } + .select2-container .select2-selection--single { + box-sizing: border-box; + cursor: pointer; + display: block; + height: 28px; + user-select: none; + -webkit-user-select: none; } + .select2-container .select2-selection--single .select2-selection__rendered { + display: block; + padding-left: 8px; + padding-right: 20px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } + .select2-container .select2-selection--single .select2-selection__clear { + position: relative; } + .select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered { + padding-right: 8px; + padding-left: 20px; } + .select2-container .select2-selection--multiple { + box-sizing: border-box; + cursor: pointer; + display: block; + min-height: 32px; + user-select: none; + -webkit-user-select: none; } + .select2-container .select2-selection--multiple .select2-selection__rendered { + display: inline-block; + overflow: hidden; + padding-left: 8px; + text-overflow: ellipsis; + white-space: nowrap; } + .select2-container .select2-search--inline { + float: left; } + .select2-container .select2-search--inline .select2-search__field { + box-sizing: border-box; + border: none; + font-size: 100%; + margin-top: 5px; + padding: 0; } + .select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button { + -webkit-appearance: none; } + +.select2-dropdown { + background-color: white; + border: 1px solid #aaa; + border-radius: 4px; + box-sizing: border-box; + display: block; + position: absolute; + left: -100000px; + width: 100%; + z-index: 1051; } + +.select2-results { + display: block; } + +.select2-results__options { + list-style: none; + margin: 0; + padding: 0; } + +.select2-results__option { + padding: 6px; + user-select: none; + -webkit-user-select: none; } + .select2-results__option[aria-selected] { + cursor: pointer; } + +.select2-container--open .select2-dropdown { + left: 0; } + +.select2-container--open .select2-dropdown--above { + border-bottom: none; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; } + +.select2-container--open .select2-dropdown--below { + border-top: none; + border-top-left-radius: 0; + border-top-right-radius: 0; } + +.select2-search--dropdown { + display: block; + padding: 4px; } + .select2-search--dropdown .select2-search__field { + padding: 4px; + width: 100%; + box-sizing: border-box; } + .select2-search--dropdown .select2-search__field::-webkit-search-cancel-button { + -webkit-appearance: none; } + .select2-search--dropdown.select2-search--hide { + display: none; } + +.select2-close-mask { + border: 0; + margin: 0; + padding: 0; + display: block; + position: fixed; + left: 0; + top: 0; + min-height: 100%; + min-width: 100%; + height: auto; + width: auto; + opacity: 0; + z-index: 99; + background-color: #fff; + filter: alpha(opacity=0); } + +.select2-hidden-accessible { + border: 0 !important; + clip: rect(0 0 0 0) !important; + -webkit-clip-path: inset(50%) !important; + clip-path: inset(50%) !important; + height: 1px !important; + overflow: hidden !important; + padding: 0 !important; + position: absolute !important; + width: 1px !important; + white-space: nowrap !important; } + +.select2-container--default .select2-selection--single { + background-color: #fff; + border: 1px solid #aaa; + border-radius: 4px; } + .select2-container--default .select2-selection--single .select2-selection__rendered { + color: #444; + line-height: 28px; } + .select2-container--default .select2-selection--single .select2-selection__clear { + cursor: pointer; + float: right; + font-weight: bold; } + .select2-container--default .select2-selection--single .select2-selection__placeholder { + color: #999; } + .select2-container--default .select2-selection--single .select2-selection__arrow { + height: 26px; + position: absolute; + top: 1px; + right: 1px; + width: 20px; } + .select2-container--default .select2-selection--single .select2-selection__arrow b { + border-color: #888 transparent transparent transparent; + border-style: solid; + border-width: 5px 4px 0 4px; + height: 0; + left: 50%; + margin-left: -4px; + margin-top: -2px; + position: absolute; + top: 50%; + width: 0; } + +.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear { + float: left; } + +.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow { + left: 1px; + right: auto; } + +.select2-container--default.select2-container--disabled .select2-selection--single { + background-color: #eee; + cursor: default; } + .select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear { + display: none; } + +.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b { + border-color: transparent transparent #888 transparent; + border-width: 0 4px 5px 4px; } + +.select2-container--default .select2-selection--multiple { + background-color: white; + border: 1px solid #aaa; + border-radius: 4px; + cursor: text; } + .select2-container--default .select2-selection--multiple .select2-selection__rendered { + box-sizing: border-box; + list-style: none; + margin: 0; + padding: 0 5px; + width: 100%; } + .select2-container--default .select2-selection--multiple .select2-selection__rendered li { + list-style: none; } + .select2-container--default .select2-selection--multiple .select2-selection__clear { + cursor: pointer; + float: right; + font-weight: bold; + margin-top: 5px; + margin-right: 10px; + padding: 1px; } + .select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: #e4e4e4; + border: 1px solid #aaa; + border-radius: 4px; + cursor: default; + float: left; + margin-right: 5px; + margin-top: 5px; + padding: 0 5px; } + .select2-container--default .select2-selection--multiple .select2-selection__choice__remove { + color: #999; + cursor: pointer; + display: inline-block; + font-weight: bold; + margin-right: 2px; } + .select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover { + color: #333; } + +.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice, .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline { + float: right; } + +.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice { + margin-left: 5px; + margin-right: auto; } + +.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove { + margin-left: 2px; + margin-right: auto; } + +.select2-container--default.select2-container--focus .select2-selection--multiple { + border: solid black 1px; + outline: 0; } + +.select2-container--default.select2-container--disabled .select2-selection--multiple { + background-color: #eee; + cursor: default; } + +.select2-container--default.select2-container--disabled .select2-selection__choice__remove { + display: none; } + +.select2-container--default.select2-container--open.select2-container--above .select2-selection--single, .select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple { + border-top-left-radius: 0; + border-top-right-radius: 0; } + +.select2-container--default.select2-container--open.select2-container--below .select2-selection--single, .select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; } + +.select2-container--default .select2-search--dropdown .select2-search__field { + border: 1px solid #aaa; } + +.select2-container--default .select2-search--inline .select2-search__field { + background: transparent; + border: none; + outline: 0; + box-shadow: none; + -webkit-appearance: textfield; } + +.select2-container--default .select2-results > .select2-results__options { + max-height: 200px; + overflow-y: auto; } + +.select2-container--default .select2-results__option[role=group] { + padding: 0; } + +.select2-container--default .select2-results__option[aria-disabled=true] { + color: #999; } + +.select2-container--default .select2-results__option[aria-selected=true] { + background-color: #ddd; } + +.select2-container--default .select2-results__option .select2-results__option { + padding-left: 1em; } + .select2-container--default .select2-results__option .select2-results__option .select2-results__group { + padding-left: 0; } + .select2-container--default .select2-results__option .select2-results__option .select2-results__option { + margin-left: -1em; + padding-left: 2em; } + .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option { + margin-left: -2em; + padding-left: 3em; } + .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option { + margin-left: -3em; + padding-left: 4em; } + .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option { + margin-left: -4em; + padding-left: 5em; } + .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option { + margin-left: -5em; + padding-left: 6em; } + +.select2-container--default .select2-results__option--highlighted[aria-selected] { + background-color: #5897fb; + color: white; } + +.select2-container--default .select2-results__group { + cursor: default; + display: block; + padding: 6px; } + +.select2-container--classic .select2-selection--single { + background-color: #f7f7f7; + border: 1px solid #aaa; + border-radius: 4px; + outline: 0; + background-image: -webkit-linear-gradient(top, white 50%, #eeeeee 100%); + background-image: -o-linear-gradient(top, white 50%, #eeeeee 100%); + background-image: linear-gradient(to bottom, white 50%, #eeeeee 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); } + .select2-container--classic .select2-selection--single:focus { + border: 1px solid #5897fb; } + .select2-container--classic .select2-selection--single .select2-selection__rendered { + color: #444; + line-height: 28px; } + .select2-container--classic .select2-selection--single .select2-selection__clear { + cursor: pointer; + float: right; + font-weight: bold; + margin-right: 10px; } + .select2-container--classic .select2-selection--single .select2-selection__placeholder { + color: #999; } + .select2-container--classic .select2-selection--single .select2-selection__arrow { + background-color: #ddd; + border: none; + border-left: 1px solid #aaa; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + height: 26px; + position: absolute; + top: 1px; + right: 1px; + width: 20px; + background-image: -webkit-linear-gradient(top, #eeeeee 50%, #cccccc 100%); + background-image: -o-linear-gradient(top, #eeeeee 50%, #cccccc 100%); + background-image: linear-gradient(to bottom, #eeeeee 50%, #cccccc 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0); } + .select2-container--classic .select2-selection--single .select2-selection__arrow b { + border-color: #888 transparent transparent transparent; + border-style: solid; + border-width: 5px 4px 0 4px; + height: 0; + left: 50%; + margin-left: -4px; + margin-top: -2px; + position: absolute; + top: 50%; + width: 0; } + +.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear { + float: left; } + +.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow { + border: none; + border-right: 1px solid #aaa; + border-radius: 0; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + left: 1px; + right: auto; } + +.select2-container--classic.select2-container--open .select2-selection--single { + border: 1px solid #5897fb; } + .select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow { + background: transparent; + border: none; } + .select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b { + border-color: transparent transparent #888 transparent; + border-width: 0 4px 5px 4px; } + +.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single { + border-top: none; + border-top-left-radius: 0; + border-top-right-radius: 0; + background-image: -webkit-linear-gradient(top, white 0%, #eeeeee 50%); + background-image: -o-linear-gradient(top, white 0%, #eeeeee 50%); + background-image: linear-gradient(to bottom, white 0%, #eeeeee 50%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); } + +.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single { + border-bottom: none; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + background-image: -webkit-linear-gradient(top, #eeeeee 50%, white 100%); + background-image: -o-linear-gradient(top, #eeeeee 50%, white 100%); + background-image: linear-gradient(to bottom, #eeeeee 50%, white 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0); } + +.select2-container--classic .select2-selection--multiple { + background-color: white; + border: 1px solid #aaa; + border-radius: 4px; + cursor: text; + outline: 0; } + .select2-container--classic .select2-selection--multiple:focus { + border: 1px solid #5897fb; } + .select2-container--classic .select2-selection--multiple .select2-selection__rendered { + list-style: none; + margin: 0; + padding: 0 5px; } + .select2-container--classic .select2-selection--multiple .select2-selection__clear { + display: none; } + .select2-container--classic .select2-selection--multiple .select2-selection__choice { + background-color: #e4e4e4; + border: 1px solid #aaa; + border-radius: 4px; + cursor: default; + float: left; + margin-right: 5px; + margin-top: 5px; + padding: 0 5px; } + .select2-container--classic .select2-selection--multiple .select2-selection__choice__remove { + color: #888; + cursor: pointer; + display: inline-block; + font-weight: bold; + margin-right: 2px; } + .select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover { + color: #555; } + +.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice { + float: right; + margin-left: 5px; + margin-right: auto; } + +.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove { + margin-left: 2px; + margin-right: auto; } + +.select2-container--classic.select2-container--open .select2-selection--multiple { + border: 1px solid #5897fb; } + +.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple { + border-top: none; + border-top-left-radius: 0; + border-top-right-radius: 0; } + +.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple { + border-bottom: none; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; } + +.select2-container--classic .select2-search--dropdown .select2-search__field { + border: 1px solid #aaa; + outline: 0; } + +.select2-container--classic .select2-search--inline .select2-search__field { + outline: 0; + box-shadow: none; } + +.select2-container--classic .select2-dropdown { + background-color: white; + border: 1px solid transparent; } + +.select2-container--classic .select2-dropdown--above { + border-bottom: none; } + +.select2-container--classic .select2-dropdown--below { + border-top: none; } + +.select2-container--classic .select2-results > .select2-results__options { + max-height: 200px; + overflow-y: auto; } + +.select2-container--classic .select2-results__option[role=group] { + padding: 0; } + +.select2-container--classic .select2-results__option[aria-disabled=true] { + color: grey; } + +.select2-container--classic .select2-results__option--highlighted[aria-selected] { + background-color: #3875d7; + color: white; } + +.select2-container--classic .select2-results__group { + cursor: default; + display: block; + padding: 6px; } + +.select2-container--classic.select2-container--open .select2-dropdown { + border-color: #5897fb; } diff --git a/ext/phpbbstudio/ass/adm/style/css/select2.min.css b/ext/phpbbstudio/ass/adm/style/css/select2.min.css new file mode 100644 index 0000000..7c18ad5 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/css/select2.min.css @@ -0,0 +1 @@ +.select2-container{box-sizing:border-box;display:inline-block;margin:0;position:relative;vertical-align:middle}.select2-container .select2-selection--single{box-sizing:border-box;cursor:pointer;display:block;height:28px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--single .select2-selection__rendered{display:block;padding-left:8px;padding-right:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-selection--single .select2-selection__clear{position:relative}.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered{padding-right:8px;padding-left:20px}.select2-container .select2-selection--multiple{box-sizing:border-box;cursor:pointer;display:block;min-height:32px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--multiple .select2-selection__rendered{display:inline-block;overflow:hidden;padding-left:8px;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-search--inline{float:left}.select2-container .select2-search--inline .select2-search__field{box-sizing:border-box;border:none;font-size:100%;margin-top:5px;padding:0}.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-dropdown{background-color:white;border:1px solid #aaa;border-radius:4px;box-sizing:border-box;display:block;position:absolute;left:-100000px;width:100%;z-index:1051}.select2-results{display:block}.select2-results__options{list-style:none;margin:0;padding:0}.select2-results__option{padding:6px;user-select:none;-webkit-user-select:none}.select2-results__option[aria-selected]{cursor:pointer}.select2-container--open .select2-dropdown{left:0}.select2-container--open .select2-dropdown--above{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--open .select2-dropdown--below{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-search--dropdown{display:block;padding:4px}.select2-search--dropdown .select2-search__field{padding:4px;width:100%;box-sizing:border-box}.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-search--dropdown.select2-search--hide{display:none}.select2-close-mask{border:0;margin:0;padding:0;display:block;position:fixed;left:0;top:0;min-height:100%;min-width:100%;height:auto;width:auto;opacity:0;z-index:99;background-color:#fff;filter:alpha(opacity=0)}.select2-hidden-accessible{border:0 !important;clip:rect(0 0 0 0) !important;-webkit-clip-path:inset(50%) !important;clip-path:inset(50%) !important;height:1px !important;overflow:hidden !important;padding:0 !important;position:absolute !important;width:1px !important;white-space:nowrap !important}.select2-container--default .select2-selection--single{background-color:#fff;border:1px solid #aaa;border-radius:4px}.select2-container--default .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--default .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold}.select2-container--default .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--default .select2-selection--single .select2-selection__arrow{height:26px;position:absolute;top:1px;right:1px;width:20px}.select2-container--default .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow{left:1px;right:auto}.select2-container--default.select2-container--disabled .select2-selection--single{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear{display:none}.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--default .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text}.select2-container--default .select2-selection--multiple .select2-selection__rendered{box-sizing:border-box;list-style:none;margin:0;padding:0 5px;width:100%}.select2-container--default .select2-selection--multiple .select2-selection__rendered li{list-style:none}.select2-container--default .select2-selection--multiple .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-top:5px;margin-right:10px;padding:1px}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove{color:#999;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover{color:#333}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice,.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline{float:right}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--default.select2-container--focus .select2-selection--multiple{border:solid black 1px;outline:0}.select2-container--default.select2-container--disabled .select2-selection--multiple{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection__choice__remove{display:none}.select2-container--default.select2-container--open.select2-container--above .select2-selection--single,.select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple{border-top-left-radius:0;border-top-right-radius:0}.select2-container--default.select2-container--open.select2-container--below .select2-selection--single,.select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--default .select2-search--dropdown .select2-search__field{border:1px solid #aaa}.select2-container--default .select2-search--inline .select2-search__field{background:transparent;border:none;outline:0;box-shadow:none;-webkit-appearance:textfield}.select2-container--default .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--default .select2-results__option[role=group]{padding:0}.select2-container--default .select2-results__option[aria-disabled=true]{color:#999}.select2-container--default .select2-results__option[aria-selected=true]{background-color:#ddd}.select2-container--default .select2-results__option .select2-results__option{padding-left:1em}.select2-container--default .select2-results__option .select2-results__option .select2-results__group{padding-left:0}.select2-container--default .select2-results__option .select2-results__option .select2-results__option{margin-left:-1em;padding-left:2em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-2em;padding-left:3em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-3em;padding-left:4em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-4em;padding-left:5em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-5em;padding-left:6em}.select2-container--default .select2-results__option--highlighted[aria-selected]{background-color:#5897fb;color:white}.select2-container--default .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic .select2-selection--single{background-color:#f7f7f7;border:1px solid #aaa;border-radius:4px;outline:0;background-image:-webkit-linear-gradient(top, #fff 50%, #eee 100%);background-image:-o-linear-gradient(top, #fff 50%, #eee 100%);background-image:linear-gradient(to bottom, #fff 50%, #eee 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic .select2-selection--single:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--classic .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-right:10px}.select2-container--classic .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--classic .select2-selection--single .select2-selection__arrow{background-color:#ddd;border:none;border-left:1px solid #aaa;border-top-right-radius:4px;border-bottom-right-radius:4px;height:26px;position:absolute;top:1px;right:1px;width:20px;background-image:-webkit-linear-gradient(top, #eee 50%, #ccc 100%);background-image:-o-linear-gradient(top, #eee 50%, #ccc 100%);background-image:linear-gradient(to bottom, #eee 50%, #ccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0)}.select2-container--classic .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow{border:none;border-right:1px solid #aaa;border-radius:0;border-top-left-radius:4px;border-bottom-left-radius:4px;left:1px;right:auto}.select2-container--classic.select2-container--open .select2-selection--single{border:1px solid #5897fb}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow{background:transparent;border:none}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single{border-top:none;border-top-left-radius:0;border-top-right-radius:0;background-image:-webkit-linear-gradient(top, #fff 0%, #eee 50%);background-image:-o-linear-gradient(top, #fff 0%, #eee 50%);background-image:linear-gradient(to bottom, #fff 0%, #eee 50%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0;background-image:-webkit-linear-gradient(top, #eee 50%, #fff 100%);background-image:-o-linear-gradient(top, #eee 50%, #fff 100%);background-image:linear-gradient(to bottom, #eee 50%, #fff 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0)}.select2-container--classic .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text;outline:0}.select2-container--classic .select2-selection--multiple:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--multiple .select2-selection__rendered{list-style:none;margin:0;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__clear{display:none}.select2-container--classic .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove{color:#888;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover{color:#555}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice{float:right;margin-left:5px;margin-right:auto}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--classic.select2-container--open .select2-selection--multiple{border:1px solid #5897fb}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--classic .select2-search--dropdown .select2-search__field{border:1px solid #aaa;outline:0}.select2-container--classic .select2-search--inline .select2-search__field{outline:0;box-shadow:none}.select2-container--classic .select2-dropdown{background-color:#fff;border:1px solid transparent}.select2-container--classic .select2-dropdown--above{border-bottom:none}.select2-container--classic .select2-dropdown--below{border-top:none}.select2-container--classic .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--classic .select2-results__option[role=group]{padding:0}.select2-container--classic .select2-results__option[aria-disabled=true]{color:grey}.select2-container--classic .select2-results__option--highlighted[aria-selected]{background-color:#3875d7;color:#fff}.select2-container--classic .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic.select2-container--open .select2-dropdown{border-color:#5897fb} diff --git a/ext/phpbbstudio/ass/adm/style/items/file.html b/ext/phpbbstudio/ass/adm/style/items/file.html new file mode 100644 index 0000000..7b48fb0 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/items/file.html @@ -0,0 +1,6 @@ +
      +
      +
      + +
      +
      diff --git a/ext/phpbbstudio/ass/adm/style/items/points.html b/ext/phpbbstudio/ass/adm/style/items/points.html new file mode 100644 index 0000000..ffe2151 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/items/points.html @@ -0,0 +1,6 @@ +
      +
      +
      + {{ aps_icon() }} +
      +
      diff --git a/ext/phpbbstudio/ass/adm/style/js/ass_common.js b/ext/phpbbstudio/ass/adm/style/js/ass_common.js new file mode 100644 index 0000000..ca79598 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/ass_common.js @@ -0,0 +1,326 @@ +/** + * + * phpBB Studio - Advanced Shop System. An extension for the phpBB Forum Software package. + * + * @copyright (c) 2019, phpBB Studio, https://www.phpbbstudio.com + * @license GNU General Public License, version 2 (GPL-2.0) + * + */ +jQuery(function($) { + let studio = { + iconPicker: $('.aps-icon-picker'), + imageAddRow: $('#add_image_row'), + sortRows: $('[data-studio-sortable]'), + selects: $('[data-studio-select]'), + sliders: $('.shop-slider'), + panels: $('[data-studio-panel]'), + dates: { + format: 'DD/MM/YYYY HH:mm', + items: $('.shop-date'), + data: $('[data-shop-date]').data() + }, + types: { + template: $('#type_template'), + select: $('#type') + }, + title: $('#title'), + slug: $('#slug') + }; + + if (studio.iconPicker.length) { + studio.iconPicker.iconpicker({ + collision: true, + placement: 'bottomRight', + component: '.aps-icon-picker + i', + locale: { + cancelLabel: 'Clear', + format: studio.dates.format + } + }); + } + + if (studio.selects.length) { + studio.selects.select2({ + closeOnSelect: false, + }); + } + + /** + * Load the requested Item type template. + * + * @return {void} + */ + studio.types.select.on('change', function() { + $.ajax({ + url: studio.types.template.data('shop-url'), + type: 'POST', + data: { + type: $(this).val() + }, + success: function(r) { + if (r.success) { + studio.ajaxifyFiles(studio.types.template.html(r.body)); + } else if (r.error) { + phpbb.alert(r.MESSAGE_TITLE, r.MESSAGE_TEXT); + } + } + }) + }); + + /** + * Create date range pickers. + * + * @return {void} + */ + studio.dates.items.each(function() { + let $input = $(this).find('input'), + $start = $input.first(), + $until = $input.last(); + + /** + * Localised language strings. + * + * @param studio.dates.data.sun + * @param studio.dates.data.mon + * @param studio.dates.data.tue + * @param studio.dates.data.wed + * @param studio.dates.data.thu + * @param studio.dates.data.fri + * @param studio.dates.data.sat + * @param studio.dates.data.january, + * @param studio.dates.data.february, + * @param studio.dates.data.march, + * @param studio.dates.data.april, + * @param studio.dates.data.may, + * @param studio.dates.data.june, + * @param studio.dates.data.july, + * @param studio.dates.data.august, + * @param studio.dates.data.september, + * @param studio.dates.data.october, + * @param studio.dates.data.november, + * @param studio.dates.data.december + */ + $start.daterangepicker({ + startDate: $start.val() || false, + endDate: $until.val() || false, + timePicker: true, + timePicker24Hour: true, + autoUpdateInput: false, + showWeekNumbers: true, + applyButtonClasses: 'aps-button-green', + cancelButtonClasses: 'aps-button-red', + locale: { + applyLabel: studio.dates.data.apply, + cancelLabel: studio.dates.data.clear, + format: studio.dates.format, + firstDay: 1, + daysOfWeek: [ + studio.dates.data.sun, + studio.dates.data.mon, + studio.dates.data.tue, + studio.dates.data.wed, + studio.dates.data.thu, + studio.dates.data.fri, + studio.dates.data.sat + ], + monthNames: [ + studio.dates.data.january, + studio.dates.data.february, + studio.dates.data.march, + studio.dates.data.april, + studio.dates.data.may, + studio.dates.data.june, + studio.dates.data.july, + studio.dates.data.august, + studio.dates.data.september, + studio.dates.data.october, + studio.dates.data.november, + studio.dates.data.december + ], + } + }).on('apply.daterangepicker', function(e, picker) { + $start.val(picker.startDate.format(studio.dates.format)); + $until.val(picker.endDate.format(studio.dates.format)); + }).on('cancel.daterangepicker', function() { + $start.val(''); + $until.val(''); + }); + + $until.on('click', function() { $start.data('daterangepicker').show(); }); + }); + + /** + * Automatically create a slug from a title/ + * + * @return {void} + */ + if (studio.title.length && studio.slug.length) { + studio.title.on('blur', function() { + let title = $(this).val(); + + studio.slug.val(function(event, slug) { + return (slug) ? slug : title.toLowerCase().replace(/[^a-z0-9-_\s]/gi, '').trim().replace(/[\s]+/g, '-'); + }); + }); + } + + /** + * Make the category and item tables sortables. + * + * @return {void} + */ + if (studio.sortRows.length) { + studio.sortRows.sortable({ + axis: 'y', + containment: $(this).selector, + cursor: 'move', + delay: 150, + handle: '.aps-button-blue', + forcePlaceholderSize: true, + placeholder: 'panel', + tolerance: 'pointer', + update: function(e, ui) { + // On update (when rows changes position), save the order + $.ajax({ + url: $(this).parents('form').attr('action') + '&action=move', + type: 'POST', + data: { + id: ui.item.data('id'), + order: ui.item.index(), + }, + }); + } + }); + } + + /** + * Ajaxify the different panels in the Settings page. + * + * @return {void} + */ + studio.panels.each(function() { + let $banner = $(this).find('i.shop-panel-icon'), + $bannerSize = $(this).find('select[name$="banner_size"]'), + $bannerColour = $(this).find('select[name$="banner_colour"]'), + $iconColour = $(this).find('select[name$="icon_colour"]'), + $icon = $(this).find('.aps-icon-picker'); + + let updateBanner = function() { + let bg = $bannerColour.val(), + size = $bannerSize.val(), + color = $iconColour.val(), + icon = $icon.val(); + + size = size ? `shop-panel-icon-${size}` : ''; + bg = bg ? `shop-panel-icon-${bg}` : ''; + + if ($.inArray(icon, ['', 'fa-li', 'fa-2x', 'fa-3x', 'fa-4x', 'fa-5x']) !== -1) { + $banner.hide(); + } else { + $banner.attr('class', `icon ${icon} icon-${color} shop-panel-icon ${size} ${bg}`).show(); + } + }; + + $bannerSize.add($bannerColour).add($iconColour).on('change', updateBanner); + $icon.on('iconpickerSelected keyup', updateBanner); + }); + + /** + * Show the slider value in the output element after it. + * + * @return {void} + */ + studio.sliders.on('mouseup input', function(e) { + if (e.type === 'mouseup') { + $(this).attr('value', this.value); + $(this).blur(); + } else { + $(this).next('output').text(this.value); + } + }); + + /** + * Add an additional item image input row. + * + * @return {void} + */ + studio.imageAddRow.on('click', function() { + let $parent = $(this).parent(), + $prev = $parent.prev(), + $row = $prev.clone(), + $input = $row.find('input'); + + /** + * Increment a number by one. + * + * @param {?} value + * @return {number} + */ + function increment(value) { + return parseInt(value) + 1; + } + + // Increment the id="" and name="" attributes + $input.attr('id', $input.attr('id').replace(/\d+$/, increment)); + $input.attr('name', $input.attr('name').replace(/\d+(?=]$)/, increment)); + $input.val(''); + + // Ajaxify the file links + studio.ajaxifyLinks(0, $row); + + // And insert the row + $row.insertBefore($parent) + }); + + /** + * Register shop file links as pop up requests. + * + * @param {jQuery=} context + * @return {void} + */ + studio.ajaxifyFiles = function(context) { + $('[data-shop-file]', context).each(studio.ajaxifyLinks); + }; + + /** + * Open a new pop up window for the shop file links. + * + * @param {number} i + * @param {HTMLElement} element + * @return {void} + */ + studio.ajaxifyLinks = function(i, element) { + let $this = $(element), + $input = $this.find('input'); + + $this.find('input').on('click', function() { + let url = $this.data('shop-file') + encodeURIComponent($input.val()) + '&input=' + encodeURIComponent($input.attr('id')); + + window.open(url.replace(/&/g, '&'), 'file', 'height=570,resizable=yes,scrollbars=yes, width=760'); + }); + }; + + studio.ajaxifyFiles(); + + /** + * Add AJAX callback for resolving items. + * + * @return {void} + */ + phpbb.addAjaxCallback('shop_resolve', function() { + $(this).parents('fieldset').hide(); + + let $active = $('#active'); + + // If the item is not active, highlight the activate button. + if ($active.is(':checked') === false) { + let $span = $active.next('span'); + + $span.addClass('ass-button-pulse'); + + $active.on('change', function() { + $span.removeClass('ass-button-pulse'); + }); + } + }); +}); diff --git a/ext/phpbbstudio/ass/adm/style/js/ass_help.js b/ext/phpbbstudio/ass/adm/style/js/ass_help.js new file mode 100644 index 0000000..db3f809 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/ass_help.js @@ -0,0 +1,91 @@ +/** + * + * phpBB Studio - Advanced Shop System. An extension for the phpBB Forum Software package. + * + * @copyright (c) 2019, phpBB Studio, https://www.phpbbstudio.com + * @license GNU General Public License, version 2 (GPL-2.0) + * + */ +jQuery(function($) { + let studio = { + buttons: { + close: $('.ass-help-close'), + start: $('.ass-help-start'), + next: $('.ass-help-next'), + prev: $('.ass-help-prev') + }, + classes: { + active: 'ass-help-active', + body: 'ass-help-body' + }, + flexbox: $('.ass-help-flexbox'), + toolbox: $('.ass-help-toolbox'), + wrapper: $('#darkenwrapper'), + index: 0 + }; + + studio.init = function() { + this.wrapper.append(this.toolbox); + + this.items = this.flexbox.children(); + + this.buttons.start.on('click', function() { studio.toggle(true); }); + this.buttons.close.on('click', function() { studio.toggle(false); }); + this.buttons.next.on('click', function() { studio.navigate(1); }); + this.buttons.prev.on('click', function() { studio.navigate(-1); }); + }; + + studio.init(); + + studio.toggle = function(show) { + $('body').toggleClass(studio.classes.body); + + studio.toolbox.toggle(show); + studio.wrapper.toggle(show); + + show ? studio.select() : studio.deselect(); + }; + + studio.navigate = function(direction) { + let length = studio.items.length, + index = studio.index + direction; + + if (index >= 0 && index < length) { + studio.index = index; + + studio.select(); + + studio.scrollToSlide(); + } + + studio.buttons.prev.toggle(studio.index !== 0); + studio.buttons.next.toggle(studio.index !== (length - 1)); + }; + + studio.select = function() { + let $item = $(studio.items.get(studio.index)), + $option = $(`label[for="${$item.data('id')}"]`).parents('dl'); + + studio.deselect(); + + $option.addClass(studio.classes.active).css('max-height', $(window).outerHeight() - (400 + 64 + 64)); + + studio.scrollToTop($option); + }; + + studio.deselect = function() { + $(`.${studio.classes.active}`).removeClass(studio.classes.active).css('max-height', ''); + }; + + studio.scrollToSlide = function() { + studio.flexbox.animate({ + scrollLeft: studio.index * $(window).outerWidth() + }); + }; + + studio.scrollToTop = function($element) { + $('html, body').stop(true).animate({ + scrollTop: $element.offset().top - 64 + }, 1000); + }; +}); diff --git a/ext/phpbbstudio/ass/adm/style/js/daterangepicker.js b/ext/phpbbstudio/ass/adm/style/js/daterangepicker.js new file mode 100644 index 0000000..6aac966 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/daterangepicker.js @@ -0,0 +1,1565 @@ +/** +* @version: 3.0.5 +* @author: Dan Grossman http://www.dangrossman.info/ +* @copyright: Copyright (c) 2012-2019 Dan Grossman. All rights reserved. +* @license: Licensed under the MIT license. See http://www.opensource.org/licenses/mit-license.php +* @website: http://www.daterangepicker.com/ +*/ +// Following the UMD template https://github.com/umdjs/umd/blob/master/templates/returnExportsGlobal.js +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Make globaly available as well + define(['moment', 'jquery'], function (moment, jquery) { + if (!jquery.fn) jquery.fn = {}; // webpack server rendering + if (typeof moment !== 'function' && moment.default) moment = moment.default + return factory(moment, jquery); + }); + } else if (typeof module === 'object' && module.exports) { + // Node / Browserify + //isomorphic issue + var jQuery = (typeof window != 'undefined') ? window.jQuery : undefined; + if (!jQuery) { + jQuery = require('jquery'); + if (!jQuery.fn) jQuery.fn = {}; + } + var moment = (typeof window != 'undefined' && typeof window.moment != 'undefined') ? window.moment : require('moment'); + module.exports = factory(moment, jQuery); + } else { + // Browser globals + root.daterangepicker = factory(root.moment, root.jQuery); + } +}(this, function(moment, $) { + var DateRangePicker = function(element, options, cb) { + + //default settings for options + this.parentEl = 'body'; + this.element = $(element); + this.startDate = moment().startOf('day'); + this.endDate = moment().endOf('day'); + this.minDate = false; + this.maxDate = false; + this.maxSpan = false; + this.autoApply = false; + this.singleDatePicker = false; + this.showDropdowns = false; + this.minYear = moment().subtract(100, 'year').format('YYYY'); + this.maxYear = moment().add(100, 'year').format('YYYY'); + this.showWeekNumbers = false; + this.showISOWeekNumbers = false; + this.showCustomRangeLabel = true; + this.timePicker = false; + this.timePicker24Hour = false; + this.timePickerIncrement = 1; + this.timePickerSeconds = false; + this.linkedCalendars = true; + this.autoUpdateInput = true; + this.alwaysShowCalendars = false; + this.ranges = {}; + + this.opens = 'right'; + if (this.element.hasClass('pull-right')) + this.opens = 'left'; + + this.drops = 'down'; + if (this.element.hasClass('dropup')) + this.drops = 'up'; + + this.buttonClasses = 'btn btn-sm'; + this.applyButtonClasses = 'btn-primary'; + this.cancelButtonClasses = 'btn-default'; + + this.locale = { + direction: 'ltr', + format: moment.localeData().longDateFormat('L'), + separator: ' - ', + applyLabel: 'Apply', + cancelLabel: 'Cancel', + weekLabel: 'W', + customRangeLabel: 'Custom Range', + daysOfWeek: moment.weekdaysMin(), + monthNames: moment.monthsShort(), + firstDay: moment.localeData().firstDayOfWeek() + }; + + this.callback = function() { }; + + //some state information + this.isShowing = false; + this.leftCalendar = {}; + this.rightCalendar = {}; + + //custom options from user + if (typeof options !== 'object' || options === null) + options = {}; + + //allow setting options with data attributes + //data-api options will be overwritten with custom javascript options + options = $.extend(this.element.data(), options); + + //html template for the picker UI + if (typeof options.template !== 'string' && !(options.template instanceof $)) + options.template = + '
      ' + + '
      ' + + '
      ' + + '
      ' + + '
      ' + + '
      ' + + '
      ' + + '
      ' + + '
      ' + + '
      ' + + '
      ' + + '' + + '' + + ' ' + + '
      ' + + '
      '; + + this.parentEl = (options.parentEl && $(options.parentEl).length) ? $(options.parentEl) : $(this.parentEl); + this.container = $(options.template).appendTo(this.parentEl); + + // + // handle all the possible options overriding defaults + // + + if (typeof options.locale === 'object') { + + if (typeof options.locale.direction === 'string') + this.locale.direction = options.locale.direction; + + if (typeof options.locale.format === 'string') + this.locale.format = options.locale.format; + + if (typeof options.locale.separator === 'string') + this.locale.separator = options.locale.separator; + + if (typeof options.locale.daysOfWeek === 'object') + this.locale.daysOfWeek = options.locale.daysOfWeek.slice(); + + if (typeof options.locale.monthNames === 'object') + this.locale.monthNames = options.locale.monthNames.slice(); + + if (typeof options.locale.firstDay === 'number') + this.locale.firstDay = options.locale.firstDay; + + if (typeof options.locale.applyLabel === 'string') + this.locale.applyLabel = options.locale.applyLabel; + + if (typeof options.locale.cancelLabel === 'string') + this.locale.cancelLabel = options.locale.cancelLabel; + + if (typeof options.locale.weekLabel === 'string') + this.locale.weekLabel = options.locale.weekLabel; + + if (typeof options.locale.customRangeLabel === 'string'){ + //Support unicode chars in the custom range name. + var elem = document.createElement('textarea'); + elem.innerHTML = options.locale.customRangeLabel; + var rangeHtml = elem.value; + this.locale.customRangeLabel = rangeHtml; + } + } + this.container.addClass(this.locale.direction); + + if (typeof options.startDate === 'string') + this.startDate = moment(options.startDate, this.locale.format); + + if (typeof options.endDate === 'string') + this.endDate = moment(options.endDate, this.locale.format); + + if (typeof options.minDate === 'string') + this.minDate = moment(options.minDate, this.locale.format); + + if (typeof options.maxDate === 'string') + this.maxDate = moment(options.maxDate, this.locale.format); + + if (typeof options.startDate === 'object') + this.startDate = moment(options.startDate); + + if (typeof options.endDate === 'object') + this.endDate = moment(options.endDate); + + if (typeof options.minDate === 'object') + this.minDate = moment(options.minDate); + + if (typeof options.maxDate === 'object') + this.maxDate = moment(options.maxDate); + + // sanity check for bad options + if (this.minDate && this.startDate.isBefore(this.minDate)) + this.startDate = this.minDate.clone(); + + // sanity check for bad options + if (this.maxDate && this.endDate.isAfter(this.maxDate)) + this.endDate = this.maxDate.clone(); + + if (typeof options.applyButtonClasses === 'string') + this.applyButtonClasses = options.applyButtonClasses; + + if (typeof options.applyClass === 'string') //backwards compat + this.applyButtonClasses = options.applyClass; + + if (typeof options.cancelButtonClasses === 'string') + this.cancelButtonClasses = options.cancelButtonClasses; + + if (typeof options.cancelClass === 'string') //backwards compat + this.cancelButtonClasses = options.cancelClass; + + if (typeof options.maxSpan === 'object') + this.maxSpan = options.maxSpan; + + if (typeof options.dateLimit === 'object') //backwards compat + this.maxSpan = options.dateLimit; + + if (typeof options.opens === 'string') + this.opens = options.opens; + + if (typeof options.drops === 'string') + this.drops = options.drops; + + if (typeof options.showWeekNumbers === 'boolean') + this.showWeekNumbers = options.showWeekNumbers; + + if (typeof options.showISOWeekNumbers === 'boolean') + this.showISOWeekNumbers = options.showISOWeekNumbers; + + if (typeof options.buttonClasses === 'string') + this.buttonClasses = options.buttonClasses; + + if (typeof options.buttonClasses === 'object') + this.buttonClasses = options.buttonClasses.join(' '); + + if (typeof options.showDropdowns === 'boolean') + this.showDropdowns = options.showDropdowns; + + if (typeof options.minYear === 'number') + this.minYear = options.minYear; + + if (typeof options.maxYear === 'number') + this.maxYear = options.maxYear; + + if (typeof options.showCustomRangeLabel === 'boolean') + this.showCustomRangeLabel = options.showCustomRangeLabel; + + if (typeof options.singleDatePicker === 'boolean') { + this.singleDatePicker = options.singleDatePicker; + if (this.singleDatePicker) + this.endDate = this.startDate.clone(); + } + + if (typeof options.timePicker === 'boolean') + this.timePicker = options.timePicker; + + if (typeof options.timePickerSeconds === 'boolean') + this.timePickerSeconds = options.timePickerSeconds; + + if (typeof options.timePickerIncrement === 'number') + this.timePickerIncrement = options.timePickerIncrement; + + if (typeof options.timePicker24Hour === 'boolean') + this.timePicker24Hour = options.timePicker24Hour; + + if (typeof options.autoApply === 'boolean') + this.autoApply = options.autoApply; + + if (typeof options.autoUpdateInput === 'boolean') + this.autoUpdateInput = options.autoUpdateInput; + + if (typeof options.linkedCalendars === 'boolean') + this.linkedCalendars = options.linkedCalendars; + + if (typeof options.isInvalidDate === 'function') + this.isInvalidDate = options.isInvalidDate; + + if (typeof options.isCustomDate === 'function') + this.isCustomDate = options.isCustomDate; + + if (typeof options.alwaysShowCalendars === 'boolean') + this.alwaysShowCalendars = options.alwaysShowCalendars; + + // update day names order to firstDay + if (this.locale.firstDay != 0) { + var iterator = this.locale.firstDay; + while (iterator > 0) { + this.locale.daysOfWeek.push(this.locale.daysOfWeek.shift()); + iterator--; + } + } + + var start, end, range; + + //if no start/end dates set, check if an input element contains initial values + if (typeof options.startDate === 'undefined' && typeof options.endDate === 'undefined') { + if ($(this.element).is(':text')) { + var val = $(this.element).val(), + split = val.split(this.locale.separator); + + start = end = null; + + if (split.length == 2) { + start = moment(split[0], this.locale.format); + end = moment(split[1], this.locale.format); + } else if (this.singleDatePicker && val !== "") { + start = moment(val, this.locale.format); + end = moment(val, this.locale.format); + } + if (start !== null && end !== null) { + this.setStartDate(start); + this.setEndDate(end); + } + } + } + + if (typeof options.ranges === 'object') { + for (range in options.ranges) { + + if (typeof options.ranges[range][0] === 'string') + start = moment(options.ranges[range][0], this.locale.format); + else + start = moment(options.ranges[range][0]); + + if (typeof options.ranges[range][1] === 'string') + end = moment(options.ranges[range][1], this.locale.format); + else + end = moment(options.ranges[range][1]); + + // If the start or end date exceed those allowed by the minDate or maxSpan + // options, shorten the range to the allowable period. + if (this.minDate && start.isBefore(this.minDate)) + start = this.minDate.clone(); + + var maxDate = this.maxDate; + if (this.maxSpan && maxDate && start.clone().add(this.maxSpan).isAfter(maxDate)) + maxDate = start.clone().add(this.maxSpan); + if (maxDate && end.isAfter(maxDate)) + end = maxDate.clone(); + + // If the end of the range is before the minimum or the start of the range is + // after the maximum, don't display this range option at all. + if ((this.minDate && end.isBefore(this.minDate, this.timepicker ? 'minute' : 'day')) + || (maxDate && start.isAfter(maxDate, this.timepicker ? 'minute' : 'day'))) + continue; + + //Support unicode chars in the range names. + var elem = document.createElement('textarea'); + elem.innerHTML = range; + var rangeHtml = elem.value; + + this.ranges[rangeHtml] = [start, end]; + } + + var list = '
        '; + for (range in this.ranges) { + list += '
      • ' + range + '
      • '; + } + if (this.showCustomRangeLabel) { + list += '
      • ' + this.locale.customRangeLabel + '
      • '; + } + list += '
      '; + this.container.find('.ranges').prepend(list); + } + + if (typeof cb === 'function') { + this.callback = cb; + } + + if (!this.timePicker) { + this.startDate = this.startDate.startOf('day'); + this.endDate = this.endDate.endOf('day'); + this.container.find('.calendar-time').hide(); + } + + //can't be used together for now + if (this.timePicker && this.autoApply) + this.autoApply = false; + + if (this.autoApply) { + this.container.addClass('auto-apply'); + } + + if (typeof options.ranges === 'object') + this.container.addClass('show-ranges'); + + if (this.singleDatePicker) { + this.container.addClass('single'); + this.container.find('.drp-calendar.left').addClass('single'); + this.container.find('.drp-calendar.left').show(); + this.container.find('.drp-calendar.right').hide(); + if (!this.timePicker) { + this.container.addClass('auto-apply'); + } + } + + if ((typeof options.ranges === 'undefined' && !this.singleDatePicker) || this.alwaysShowCalendars) { + this.container.addClass('show-calendar'); + } + + this.container.addClass('opens' + this.opens); + + //apply CSS classes and labels to buttons + this.container.find('.applyBtn, .cancelBtn').addClass(this.buttonClasses); + if (this.applyButtonClasses.length) + this.container.find('.applyBtn').addClass(this.applyButtonClasses); + if (this.cancelButtonClasses.length) + this.container.find('.cancelBtn').addClass(this.cancelButtonClasses); + this.container.find('.applyBtn').html(this.locale.applyLabel); + this.container.find('.cancelBtn').html(this.locale.cancelLabel); + + // + // event listeners + // + + this.container.find('.drp-calendar') + .on('click.daterangepicker', '.prev', $.proxy(this.clickPrev, this)) + .on('click.daterangepicker', '.next', $.proxy(this.clickNext, this)) + .on('mousedown.daterangepicker', 'td.available', $.proxy(this.clickDate, this)) + .on('mouseenter.daterangepicker', 'td.available', $.proxy(this.hoverDate, this)) + .on('change.daterangepicker', 'select.yearselect', $.proxy(this.monthOrYearChanged, this)) + .on('change.daterangepicker', 'select.monthselect', $.proxy(this.monthOrYearChanged, this)) + .on('change.daterangepicker', 'select.hourselect,select.minuteselect,select.secondselect,select.ampmselect', $.proxy(this.timeChanged, this)) + + this.container.find('.ranges') + .on('click.daterangepicker', 'li', $.proxy(this.clickRange, this)) + + this.container.find('.drp-buttons') + .on('click.daterangepicker', 'button.applyBtn', $.proxy(this.clickApply, this)) + .on('click.daterangepicker', 'button.cancelBtn', $.proxy(this.clickCancel, this)) + + if (this.element.is('input') || this.element.is('button')) { + this.element.on({ + 'click.daterangepicker': $.proxy(this.show, this), + 'focus.daterangepicker': $.proxy(this.show, this), + 'keyup.daterangepicker': $.proxy(this.elementChanged, this), + 'keydown.daterangepicker': $.proxy(this.keydown, this) //IE 11 compatibility + }); + } else { + this.element.on('click.daterangepicker', $.proxy(this.toggle, this)); + this.element.on('keydown.daterangepicker', $.proxy(this.toggle, this)); + } + + // + // if attached to a text input, set the initial value + // + + this.updateElement(); + + }; + + DateRangePicker.prototype = { + + constructor: DateRangePicker, + + setStartDate: function(startDate) { + if (typeof startDate === 'string') + this.startDate = moment(startDate, this.locale.format); + + if (typeof startDate === 'object') + this.startDate = moment(startDate); + + if (!this.timePicker) + this.startDate = this.startDate.startOf('day'); + + if (this.timePicker && this.timePickerIncrement) + this.startDate.minute(Math.round(this.startDate.minute() / this.timePickerIncrement) * this.timePickerIncrement); + + if (this.minDate && this.startDate.isBefore(this.minDate)) { + this.startDate = this.minDate.clone(); + if (this.timePicker && this.timePickerIncrement) + this.startDate.minute(Math.round(this.startDate.minute() / this.timePickerIncrement) * this.timePickerIncrement); + } + + if (this.maxDate && this.startDate.isAfter(this.maxDate)) { + this.startDate = this.maxDate.clone(); + if (this.timePicker && this.timePickerIncrement) + this.startDate.minute(Math.floor(this.startDate.minute() / this.timePickerIncrement) * this.timePickerIncrement); + } + + if (!this.isShowing) + this.updateElement(); + + this.updateMonthsInView(); + }, + + setEndDate: function(endDate) { + if (typeof endDate === 'string') + this.endDate = moment(endDate, this.locale.format); + + if (typeof endDate === 'object') + this.endDate = moment(endDate); + + if (!this.timePicker) + this.endDate = this.endDate.endOf('day'); + + if (this.timePicker && this.timePickerIncrement) + this.endDate.minute(Math.round(this.endDate.minute() / this.timePickerIncrement) * this.timePickerIncrement); + + if (this.endDate.isBefore(this.startDate)) + this.endDate = this.startDate.clone(); + + if (this.maxDate && this.endDate.isAfter(this.maxDate)) + this.endDate = this.maxDate.clone(); + + if (this.maxSpan && this.startDate.clone().add(this.maxSpan).isBefore(this.endDate)) + this.endDate = this.startDate.clone().add(this.maxSpan); + + this.previousRightTime = this.endDate.clone(); + + this.container.find('.drp-selected').html(this.startDate.format(this.locale.format) + this.locale.separator + this.endDate.format(this.locale.format)); + + if (!this.isShowing) + this.updateElement(); + + this.updateMonthsInView(); + }, + + isInvalidDate: function() { + return false; + }, + + isCustomDate: function() { + return false; + }, + + updateView: function() { + if (this.timePicker) { + this.renderTimePicker('left'); + this.renderTimePicker('right'); + if (!this.endDate) { + this.container.find('.right .calendar-time select').attr('disabled', 'disabled').addClass('disabled'); + } else { + this.container.find('.right .calendar-time select').removeAttr('disabled').removeClass('disabled'); + } + } + if (this.endDate) + this.container.find('.drp-selected').html(this.startDate.format(this.locale.format) + this.locale.separator + this.endDate.format(this.locale.format)); + this.updateMonthsInView(); + this.updateCalendars(); + this.updateFormInputs(); + }, + + updateMonthsInView: function() { + if (this.endDate) { + + //if both dates are visible already, do nothing + if (!this.singleDatePicker && this.leftCalendar.month && this.rightCalendar.month && + (this.startDate.format('YYYY-MM') == this.leftCalendar.month.format('YYYY-MM') || this.startDate.format('YYYY-MM') == this.rightCalendar.month.format('YYYY-MM')) + && + (this.endDate.format('YYYY-MM') == this.leftCalendar.month.format('YYYY-MM') || this.endDate.format('YYYY-MM') == this.rightCalendar.month.format('YYYY-MM')) + ) { + return; + } + + this.leftCalendar.month = this.startDate.clone().date(2); + if (!this.linkedCalendars && (this.endDate.month() != this.startDate.month() || this.endDate.year() != this.startDate.year())) { + this.rightCalendar.month = this.endDate.clone().date(2); + } else { + this.rightCalendar.month = this.startDate.clone().date(2).add(1, 'month'); + } + + } else { + if (this.leftCalendar.month.format('YYYY-MM') != this.startDate.format('YYYY-MM') && this.rightCalendar.month.format('YYYY-MM') != this.startDate.format('YYYY-MM')) { + this.leftCalendar.month = this.startDate.clone().date(2); + this.rightCalendar.month = this.startDate.clone().date(2).add(1, 'month'); + } + } + if (this.maxDate && this.linkedCalendars && !this.singleDatePicker && this.rightCalendar.month > this.maxDate) { + this.rightCalendar.month = this.maxDate.clone().date(2); + this.leftCalendar.month = this.maxDate.clone().date(2).subtract(1, 'month'); + } + }, + + updateCalendars: function() { + + if (this.timePicker) { + var hour, minute, second; + if (this.endDate) { + hour = parseInt(this.container.find('.left .hourselect').val(), 10); + minute = parseInt(this.container.find('.left .minuteselect').val(), 10); + if (isNaN(minute)) { + minute = parseInt(this.container.find('.left .minuteselect option:last').val(), 10); + } + second = this.timePickerSeconds ? parseInt(this.container.find('.left .secondselect').val(), 10) : 0; + if (!this.timePicker24Hour) { + var ampm = this.container.find('.left .ampmselect').val(); + if (ampm === 'PM' && hour < 12) + hour += 12; + if (ampm === 'AM' && hour === 12) + hour = 0; + } + } else { + hour = parseInt(this.container.find('.right .hourselect').val(), 10); + minute = parseInt(this.container.find('.right .minuteselect').val(), 10); + if (isNaN(minute)) { + minute = parseInt(this.container.find('.right .minuteselect option:last').val(), 10); + } + second = this.timePickerSeconds ? parseInt(this.container.find('.right .secondselect').val(), 10) : 0; + if (!this.timePicker24Hour) { + var ampm = this.container.find('.right .ampmselect').val(); + if (ampm === 'PM' && hour < 12) + hour += 12; + if (ampm === 'AM' && hour === 12) + hour = 0; + } + } + this.leftCalendar.month.hour(hour).minute(minute).second(second); + this.rightCalendar.month.hour(hour).minute(minute).second(second); + } + + this.renderCalendar('left'); + this.renderCalendar('right'); + + //highlight any predefined range matching the current start and end dates + this.container.find('.ranges li').removeClass('active'); + if (this.endDate == null) return; + + this.calculateChosenLabel(); + }, + + renderCalendar: function(side) { + + // + // Build the matrix of dates that will populate the calendar + // + + var calendar = side == 'left' ? this.leftCalendar : this.rightCalendar; + var month = calendar.month.month(); + var year = calendar.month.year(); + var hour = calendar.month.hour(); + var minute = calendar.month.minute(); + var second = calendar.month.second(); + var daysInMonth = moment([year, month]).daysInMonth(); + var firstDay = moment([year, month, 1]); + var lastDay = moment([year, month, daysInMonth]); + var lastMonth = moment(firstDay).subtract(1, 'month').month(); + var lastYear = moment(firstDay).subtract(1, 'month').year(); + var daysInLastMonth = moment([lastYear, lastMonth]).daysInMonth(); + var dayOfWeek = firstDay.day(); + + //initialize a 6 rows x 7 columns array for the calendar + var calendar = []; + calendar.firstDay = firstDay; + calendar.lastDay = lastDay; + + for (var i = 0; i < 6; i++) { + calendar[i] = []; + } + + //populate the calendar with date objects + var startDay = daysInLastMonth - dayOfWeek + this.locale.firstDay + 1; + if (startDay > daysInLastMonth) + startDay -= 7; + + if (dayOfWeek == this.locale.firstDay) + startDay = daysInLastMonth - 6; + + var curDate = moment([lastYear, lastMonth, startDay, 12, minute, second]); + + var col, row; + for (var i = 0, col = 0, row = 0; i < 42; i++, col++, curDate = moment(curDate).add(24, 'hour')) { + if (i > 0 && col % 7 === 0) { + col = 0; + row++; + } + calendar[row][col] = curDate.clone().hour(hour).minute(minute).second(second); + curDate.hour(12); + + if (this.minDate && calendar[row][col].format('YYYY-MM-DD') == this.minDate.format('YYYY-MM-DD') && calendar[row][col].isBefore(this.minDate) && side == 'left') { + calendar[row][col] = this.minDate.clone(); + } + + if (this.maxDate && calendar[row][col].format('YYYY-MM-DD') == this.maxDate.format('YYYY-MM-DD') && calendar[row][col].isAfter(this.maxDate) && side == 'right') { + calendar[row][col] = this.maxDate.clone(); + } + + } + + //make the calendar object available to hoverDate/clickDate + if (side == 'left') { + this.leftCalendar.calendar = calendar; + } else { + this.rightCalendar.calendar = calendar; + } + + // + // Display the calendar + // + + var minDate = side == 'left' ? this.minDate : this.startDate; + var maxDate = this.maxDate; + var selected = side == 'left' ? this.startDate : this.endDate; + var arrow = this.locale.direction == 'ltr' ? {left: 'chevron-left', right: 'chevron-right'} : {left: 'chevron-right', right: 'chevron-left'}; + + var html = ''; + html += ''; + html += ''; + + // add empty cell for week number + if (this.showWeekNumbers || this.showISOWeekNumbers) + html += ''; + + if ((!minDate || minDate.isBefore(calendar.firstDay)) && (!this.linkedCalendars || side == 'left')) { + html += ''; + } else { + html += ''; + } + + var dateHtml = this.locale.monthNames[calendar[1][1].month()] + calendar[1][1].format(" YYYY"); + + if (this.showDropdowns) { + var currentMonth = calendar[1][1].month(); + var currentYear = calendar[1][1].year(); + var maxYear = (maxDate && maxDate.year()) || (this.maxYear); + var minYear = (minDate && minDate.year()) || (this.minYear); + var inMinYear = currentYear == minYear; + var inMaxYear = currentYear == maxYear; + + var monthHtml = '"; + + var yearHtml = ''; + + dateHtml = monthHtml + yearHtml; + } + + html += ''; + if ((!maxDate || maxDate.isAfter(calendar.lastDay)) && (!this.linkedCalendars || side == 'right' || this.singleDatePicker)) { + html += ''; + } else { + html += ''; + } + + html += ''; + html += ''; + + // add week number label + if (this.showWeekNumbers || this.showISOWeekNumbers) + html += ''; + + $.each(this.locale.daysOfWeek, function(index, dayOfWeek) { + html += ''; + }); + + html += ''; + html += ''; + html += ''; + + //adjust maxDate to reflect the maxSpan setting in order to + //grey out end dates beyond the maxSpan + if (this.endDate == null && this.maxSpan) { + var maxLimit = this.startDate.clone().add(this.maxSpan).endOf('day'); + if (!maxDate || maxLimit.isBefore(maxDate)) { + maxDate = maxLimit; + } + } + + for (var row = 0; row < 6; row++) { + html += ''; + + // add week number + if (this.showWeekNumbers) + html += ''; + else if (this.showISOWeekNumbers) + html += ''; + + for (var col = 0; col < 7; col++) { + + var classes = []; + + //highlight today's date + if (calendar[row][col].isSame(new Date(), "day")) + classes.push('today'); + + //highlight weekends + if (calendar[row][col].isoWeekday() > 5) + classes.push('weekend'); + + //grey out the dates in other months displayed at beginning and end of this calendar + if (calendar[row][col].month() != calendar[1][1].month()) + classes.push('off', 'ends'); + + //don't allow selection of dates before the minimum date + if (this.minDate && calendar[row][col].isBefore(this.minDate, 'day')) + classes.push('off', 'disabled'); + + //don't allow selection of dates after the maximum date + if (maxDate && calendar[row][col].isAfter(maxDate, 'day')) + classes.push('off', 'disabled'); + + //don't allow selection of date if a custom function decides it's invalid + if (this.isInvalidDate(calendar[row][col])) + classes.push('off', 'disabled'); + + //highlight the currently selected start date + if (calendar[row][col].format('YYYY-MM-DD') == this.startDate.format('YYYY-MM-DD')) + classes.push('active', 'start-date'); + + //highlight the currently selected end date + if (this.endDate != null && calendar[row][col].format('YYYY-MM-DD') == this.endDate.format('YYYY-MM-DD')) + classes.push('active', 'end-date'); + + //highlight dates in-between the selected dates + if (this.endDate != null && calendar[row][col] > this.startDate && calendar[row][col] < this.endDate) + classes.push('in-range'); + + //apply custom classes for this date + var isCustom = this.isCustomDate(calendar[row][col]); + if (isCustom !== false) { + if (typeof isCustom === 'string') + classes.push(isCustom); + else + Array.prototype.push.apply(classes, isCustom); + } + + var cname = '', disabled = false; + for (var i = 0; i < classes.length; i++) { + cname += classes[i] + ' '; + if (classes[i] == 'disabled') + disabled = true; + } + if (!disabled) + cname += 'available'; + + html += ''; + + } + html += ''; + } + + html += ''; + html += '
      ' + dateHtml + '
      ' + this.locale.weekLabel + '' + dayOfWeek + '
      ' + calendar[row][0].week() + '' + calendar[row][0].isoWeek() + '' + calendar[row][col].date() + '
      '; + + this.container.find('.drp-calendar.' + side + ' .calendar-table').html(html); + + }, + + renderTimePicker: function(side) { + + // Don't bother updating the time picker if it's currently disabled + // because an end date hasn't been clicked yet + if (side == 'right' && !this.endDate) return; + + var html, selected, minDate, maxDate = this.maxDate; + + if (this.maxSpan && (!this.maxDate || this.startDate.clone().add(this.maxSpan).isBefore(this.maxDate))) + maxDate = this.startDate.clone().add(this.maxSpan); + + if (side == 'left') { + selected = this.startDate.clone(); + minDate = this.minDate; + } else if (side == 'right') { + selected = this.endDate.clone(); + minDate = this.startDate; + + //Preserve the time already selected + var timeSelector = this.container.find('.drp-calendar.right .calendar-time'); + if (timeSelector.html() != '') { + + selected.hour(!isNaN(selected.hour()) ? selected.hour() : timeSelector.find('.hourselect option:selected').val()); + selected.minute(!isNaN(selected.minute()) ? selected.minute() : timeSelector.find('.minuteselect option:selected').val()); + selected.second(!isNaN(selected.second()) ? selected.second() : timeSelector.find('.secondselect option:selected').val()); + + if (!this.timePicker24Hour) { + var ampm = timeSelector.find('.ampmselect option:selected').val(); + if (ampm === 'PM' && selected.hour() < 12) + selected.hour(selected.hour() + 12); + if (ampm === 'AM' && selected.hour() === 12) + selected.hour(0); + } + + } + + if (selected.isBefore(this.startDate)) + selected = this.startDate.clone(); + + if (maxDate && selected.isAfter(maxDate)) + selected = maxDate.clone(); + + } + + // + // hours + // + + html = ' '; + + // + // minutes + // + + html += ': '; + + // + // seconds + // + + if (this.timePickerSeconds) { + html += ': '; + } + + // + // AM/PM + // + + if (!this.timePicker24Hour) { + html += ''; + } + + this.container.find('.drp-calendar.' + side + ' .calendar-time').html(html); + + }, + + updateFormInputs: function() { + + if (this.singleDatePicker || (this.endDate && (this.startDate.isBefore(this.endDate) || this.startDate.isSame(this.endDate)))) { + this.container.find('button.applyBtn').removeAttr('disabled'); + } else { + this.container.find('button.applyBtn').attr('disabled', 'disabled'); + } + + }, + + move: function() { + var parentOffset = { top: 0, left: 0 }, + containerTop; + var parentRightEdge = $(window).width(); + if (!this.parentEl.is('body')) { + parentOffset = { + top: this.parentEl.offset().top - this.parentEl.scrollTop(), + left: this.parentEl.offset().left - this.parentEl.scrollLeft() + }; + parentRightEdge = this.parentEl[0].clientWidth + this.parentEl.offset().left; + } + + if (this.drops == 'up') + containerTop = this.element.offset().top - this.container.outerHeight() - parentOffset.top; + else + containerTop = this.element.offset().top + this.element.outerHeight() - parentOffset.top; + + // Force the container to it's actual width + this.container.css({ + top: 0, + left: 0, + right: 'auto' + }); + var containerWidth = this.container.outerWidth(); + + this.container[this.drops == 'up' ? 'addClass' : 'removeClass']('drop-up'); + + if (this.opens == 'left') { + var containerRight = parentRightEdge - this.element.offset().left - this.element.outerWidth(); + if (containerWidth + containerRight > $(window).width()) { + this.container.css({ + top: containerTop, + right: 'auto', + left: 9 + }); + } else { + this.container.css({ + top: containerTop, + right: containerRight, + left: 'auto' + }); + } + } else if (this.opens == 'center') { + var containerLeft = this.element.offset().left - parentOffset.left + this.element.outerWidth() / 2 + - containerWidth / 2; + if (containerLeft < 0) { + this.container.css({ + top: containerTop, + right: 'auto', + left: 9 + }); + } else if (containerLeft + containerWidth > $(window).width()) { + this.container.css({ + top: containerTop, + left: 'auto', + right: 0 + }); + } else { + this.container.css({ + top: containerTop, + left: containerLeft, + right: 'auto' + }); + } + } else { + var containerLeft = this.element.offset().left - parentOffset.left; + if (containerLeft + containerWidth > $(window).width()) { + this.container.css({ + top: containerTop, + left: 'auto', + right: 0 + }); + } else { + this.container.css({ + top: containerTop, + left: containerLeft, + right: 'auto' + }); + } + } + }, + + show: function(e) { + if (this.isShowing) return; + + // Create a click proxy that is private to this instance of datepicker, for unbinding + this._outsideClickProxy = $.proxy(function(e) { this.outsideClick(e); }, this); + + // Bind global datepicker mousedown for hiding and + $(document) + .on('mousedown.daterangepicker', this._outsideClickProxy) + // also support mobile devices + .on('touchend.daterangepicker', this._outsideClickProxy) + // also explicitly play nice with Bootstrap dropdowns, which stopPropagation when clicking them + .on('click.daterangepicker', '[data-toggle=dropdown]', this._outsideClickProxy) + // and also close when focus changes to outside the picker (eg. tabbing between controls) + .on('focusin.daterangepicker', this._outsideClickProxy); + + // Reposition the picker if the window is resized while it's open + $(window).on('resize.daterangepicker', $.proxy(function(e) { this.move(e); }, this)); + + this.oldStartDate = this.startDate.clone(); + this.oldEndDate = this.endDate.clone(); + this.previousRightTime = this.endDate.clone(); + + this.updateView(); + this.container.show(); + this.move(); + this.element.trigger('show.daterangepicker', this); + this.isShowing = true; + }, + + hide: function(e) { + if (!this.isShowing) return; + + //incomplete date selection, revert to last values + if (!this.endDate) { + this.startDate = this.oldStartDate.clone(); + this.endDate = this.oldEndDate.clone(); + } + + //if a new date range was selected, invoke the user callback function + if (!this.startDate.isSame(this.oldStartDate) || !this.endDate.isSame(this.oldEndDate)) + this.callback(this.startDate.clone(), this.endDate.clone(), this.chosenLabel); + + //if picker is attached to a text input, update it + this.updateElement(); + + $(document).off('.daterangepicker'); + $(window).off('.daterangepicker'); + this.container.hide(); + this.element.trigger('hide.daterangepicker', this); + this.isShowing = false; + }, + + toggle: function(e) { + if (this.isShowing) { + this.hide(); + } else { + this.show(); + } + }, + + outsideClick: function(e) { + var target = $(e.target); + // if the page is clicked anywhere except within the daterangerpicker/button + // itself then call this.hide() + if ( + // ie modal dialog fix + e.type == "focusin" || + target.closest(this.element).length || + target.closest(this.container).length || + target.closest('.calendar-table').length + ) return; + this.hide(); + this.element.trigger('outsideClick.daterangepicker', this); + }, + + showCalendars: function() { + this.container.addClass('show-calendar'); + this.move(); + this.element.trigger('showCalendar.daterangepicker', this); + }, + + hideCalendars: function() { + this.container.removeClass('show-calendar'); + this.element.trigger('hideCalendar.daterangepicker', this); + }, + + clickRange: function(e) { + var label = e.target.getAttribute('data-range-key'); + this.chosenLabel = label; + if (label == this.locale.customRangeLabel) { + this.showCalendars(); + } else { + var dates = this.ranges[label]; + this.startDate = dates[0]; + this.endDate = dates[1]; + + if (!this.timePicker) { + this.startDate.startOf('day'); + this.endDate.endOf('day'); + } + + if (!this.alwaysShowCalendars) + this.hideCalendars(); + this.clickApply(); + } + }, + + clickPrev: function(e) { + var cal = $(e.target).parents('.drp-calendar'); + if (cal.hasClass('left')) { + this.leftCalendar.month.subtract(1, 'month'); + if (this.linkedCalendars) + this.rightCalendar.month.subtract(1, 'month'); + } else { + this.rightCalendar.month.subtract(1, 'month'); + } + this.updateCalendars(); + }, + + clickNext: function(e) { + var cal = $(e.target).parents('.drp-calendar'); + if (cal.hasClass('left')) { + this.leftCalendar.month.add(1, 'month'); + } else { + this.rightCalendar.month.add(1, 'month'); + if (this.linkedCalendars) + this.leftCalendar.month.add(1, 'month'); + } + this.updateCalendars(); + }, + + hoverDate: function(e) { + + //ignore dates that can't be selected + if (!$(e.target).hasClass('available')) return; + + var title = $(e.target).attr('data-title'); + var row = title.substr(1, 1); + var col = title.substr(3, 1); + var cal = $(e.target).parents('.drp-calendar'); + var date = cal.hasClass('left') ? this.leftCalendar.calendar[row][col] : this.rightCalendar.calendar[row][col]; + + //highlight the dates between the start date and the date being hovered as a potential end date + var leftCalendar = this.leftCalendar; + var rightCalendar = this.rightCalendar; + var startDate = this.startDate; + if (!this.endDate) { + this.container.find('.drp-calendar tbody td').each(function(index, el) { + + //skip week numbers, only look at dates + if ($(el).hasClass('week')) return; + + var title = $(el).attr('data-title'); + var row = title.substr(1, 1); + var col = title.substr(3, 1); + var cal = $(el).parents('.drp-calendar'); + var dt = cal.hasClass('left') ? leftCalendar.calendar[row][col] : rightCalendar.calendar[row][col]; + + if ((dt.isAfter(startDate) && dt.isBefore(date)) || dt.isSame(date, 'day')) { + $(el).addClass('in-range'); + } else { + $(el).removeClass('in-range'); + } + + }); + } + + }, + + clickDate: function(e) { + + if (!$(e.target).hasClass('available')) return; + + var title = $(e.target).attr('data-title'); + var row = title.substr(1, 1); + var col = title.substr(3, 1); + var cal = $(e.target).parents('.drp-calendar'); + var date = cal.hasClass('left') ? this.leftCalendar.calendar[row][col] : this.rightCalendar.calendar[row][col]; + + // + // this function needs to do a few things: + // * alternate between selecting a start and end date for the range, + // * if the time picker is enabled, apply the hour/minute/second from the select boxes to the clicked date + // * if autoapply is enabled, and an end date was chosen, apply the selection + // * if single date picker mode, and time picker isn't enabled, apply the selection immediately + // * if one of the inputs above the calendars was focused, cancel that manual input + // + + if (this.endDate || date.isBefore(this.startDate, 'day')) { //picking start + if (this.timePicker) { + var hour = parseInt(this.container.find('.left .hourselect').val(), 10); + if (!this.timePicker24Hour) { + var ampm = this.container.find('.left .ampmselect').val(); + if (ampm === 'PM' && hour < 12) + hour += 12; + if (ampm === 'AM' && hour === 12) + hour = 0; + } + var minute = parseInt(this.container.find('.left .minuteselect').val(), 10); + if (isNaN(minute)) { + minute = parseInt(this.container.find('.left .minuteselect option:last').val(), 10); + } + var second = this.timePickerSeconds ? parseInt(this.container.find('.left .secondselect').val(), 10) : 0; + date = date.clone().hour(hour).minute(minute).second(second); + } + this.endDate = null; + this.setStartDate(date.clone()); + } else if (!this.endDate && date.isBefore(this.startDate)) { + //special case: clicking the same date for start/end, + //but the time of the end date is before the start date + this.setEndDate(this.startDate.clone()); + } else { // picking end + if (this.timePicker) { + var hour = parseInt(this.container.find('.right .hourselect').val(), 10); + if (!this.timePicker24Hour) { + var ampm = this.container.find('.right .ampmselect').val(); + if (ampm === 'PM' && hour < 12) + hour += 12; + if (ampm === 'AM' && hour === 12) + hour = 0; + } + var minute = parseInt(this.container.find('.right .minuteselect').val(), 10); + if (isNaN(minute)) { + minute = parseInt(this.container.find('.right .minuteselect option:last').val(), 10); + } + var second = this.timePickerSeconds ? parseInt(this.container.find('.right .secondselect').val(), 10) : 0; + date = date.clone().hour(hour).minute(minute).second(second); + } + this.setEndDate(date.clone()); + if (this.autoApply) { + this.calculateChosenLabel(); + this.clickApply(); + } + } + + if (this.singleDatePicker) { + this.setEndDate(this.startDate); + if (!this.timePicker) + this.clickApply(); + } + + this.updateView(); + + //This is to cancel the blur event handler if the mouse was in one of the inputs + e.stopPropagation(); + + }, + + calculateChosenLabel: function () { + var customRange = true; + var i = 0; + for (var range in this.ranges) { + if (this.timePicker) { + var format = this.timePickerSeconds ? "YYYY-MM-DD HH:mm:ss" : "YYYY-MM-DD HH:mm"; + //ignore times when comparing dates if time picker seconds is not enabled + if (this.startDate.format(format) == this.ranges[range][0].format(format) && this.endDate.format(format) == this.ranges[range][1].format(format)) { + customRange = false; + this.chosenLabel = this.container.find('.ranges li:eq(' + i + ')').addClass('active').attr('data-range-key'); + break; + } + } else { + //ignore times when comparing dates if time picker is not enabled + if (this.startDate.format('YYYY-MM-DD') == this.ranges[range][0].format('YYYY-MM-DD') && this.endDate.format('YYYY-MM-DD') == this.ranges[range][1].format('YYYY-MM-DD')) { + customRange = false; + this.chosenLabel = this.container.find('.ranges li:eq(' + i + ')').addClass('active').attr('data-range-key'); + break; + } + } + i++; + } + if (customRange) { + if (this.showCustomRangeLabel) { + this.chosenLabel = this.container.find('.ranges li:last').addClass('active').attr('data-range-key'); + } else { + this.chosenLabel = null; + } + this.showCalendars(); + } + }, + + clickApply: function(e) { + this.hide(); + this.element.trigger('apply.daterangepicker', this); + }, + + clickCancel: function(e) { + this.startDate = this.oldStartDate; + this.endDate = this.oldEndDate; + this.hide(); + this.element.trigger('cancel.daterangepicker', this); + }, + + monthOrYearChanged: function(e) { + var isLeft = $(e.target).closest('.drp-calendar').hasClass('left'), + leftOrRight = isLeft ? 'left' : 'right', + cal = this.container.find('.drp-calendar.'+leftOrRight); + + // Month must be Number for new moment versions + var month = parseInt(cal.find('.monthselect').val(), 10); + var year = cal.find('.yearselect').val(); + + if (!isLeft) { + if (year < this.startDate.year() || (year == this.startDate.year() && month < this.startDate.month())) { + month = this.startDate.month(); + year = this.startDate.year(); + } + } + + if (this.minDate) { + if (year < this.minDate.year() || (year == this.minDate.year() && month < this.minDate.month())) { + month = this.minDate.month(); + year = this.minDate.year(); + } + } + + if (this.maxDate) { + if (year > this.maxDate.year() || (year == this.maxDate.year() && month > this.maxDate.month())) { + month = this.maxDate.month(); + year = this.maxDate.year(); + } + } + + if (isLeft) { + this.leftCalendar.month.month(month).year(year); + if (this.linkedCalendars) + this.rightCalendar.month = this.leftCalendar.month.clone().add(1, 'month'); + } else { + this.rightCalendar.month.month(month).year(year); + if (this.linkedCalendars) + this.leftCalendar.month = this.rightCalendar.month.clone().subtract(1, 'month'); + } + this.updateCalendars(); + }, + + timeChanged: function(e) { + + var cal = $(e.target).closest('.drp-calendar'), + isLeft = cal.hasClass('left'); + + var hour = parseInt(cal.find('.hourselect').val(), 10); + var minute = parseInt(cal.find('.minuteselect').val(), 10); + if (isNaN(minute)) { + minute = parseInt(cal.find('.minuteselect option:last').val(), 10); + } + var second = this.timePickerSeconds ? parseInt(cal.find('.secondselect').val(), 10) : 0; + + if (!this.timePicker24Hour) { + var ampm = cal.find('.ampmselect').val(); + if (ampm === 'PM' && hour < 12) + hour += 12; + if (ampm === 'AM' && hour === 12) + hour = 0; + } + + if (isLeft) { + var start = this.startDate.clone(); + start.hour(hour); + start.minute(minute); + start.second(second); + this.setStartDate(start); + if (this.singleDatePicker) { + this.endDate = this.startDate.clone(); + } else if (this.endDate && this.endDate.format('YYYY-MM-DD') == start.format('YYYY-MM-DD') && this.endDate.isBefore(start)) { + this.setEndDate(start.clone()); + } + } else if (this.endDate) { + var end = this.endDate.clone(); + end.hour(hour); + end.minute(minute); + end.second(second); + this.setEndDate(end); + } + + //update the calendars so all clickable dates reflect the new time component + this.updateCalendars(); + + //update the form inputs above the calendars with the new time + this.updateFormInputs(); + + //re-render the time pickers because changing one selection can affect what's enabled in another + this.renderTimePicker('left'); + this.renderTimePicker('right'); + + }, + + elementChanged: function() { + if (!this.element.is('input')) return; + if (!this.element.val().length) return; + + var dateString = this.element.val().split(this.locale.separator), + start = null, + end = null; + + if (dateString.length === 2) { + start = moment(dateString[0], this.locale.format); + end = moment(dateString[1], this.locale.format); + } + + if (this.singleDatePicker || start === null || end === null) { + start = moment(this.element.val(), this.locale.format); + end = start; + } + + if (!start.isValid() || !end.isValid()) return; + + this.setStartDate(start); + this.setEndDate(end); + this.updateView(); + }, + + keydown: function(e) { + //hide on tab or enter + if ((e.keyCode === 9) || (e.keyCode === 13)) { + this.hide(); + } + + //hide on esc and prevent propagation + if (e.keyCode === 27) { + e.preventDefault(); + e.stopPropagation(); + + this.hide(); + } + }, + + updateElement: function() { + if (this.element.is('input') && this.autoUpdateInput) { + var newValue = this.startDate.format(this.locale.format); + if (!this.singleDatePicker) { + newValue += this.locale.separator + this.endDate.format(this.locale.format); + } + if (newValue !== this.element.val()) { + this.element.val(newValue).trigger('change'); + } + } + }, + + remove: function() { + this.container.remove(); + this.element.off('.daterangepicker'); + this.element.removeData(); + } + + }; + + $.fn.daterangepicker = function(options, callback) { + var implementOptions = $.extend(true, {}, $.fn.daterangepicker.defaultOptions, options); + this.each(function() { + var el = $(this); + if (el.data('daterangepicker')) + el.data('daterangepicker').remove(); + el.data('daterangepicker', new DateRangePicker(el, implementOptions, callback)); + }); + return this; + }; + + return DateRangePicker; + +})); diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/af.js b/ext/phpbbstudio/ass/adm/style/js/i18n/af.js new file mode 100644 index 0000000..2a1a75b --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/af.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/af",[],function(){return{errorLoading:function(){return"Die resultate kon nie gelaai word nie."},inputTooLong:function(e){var n=e.input.length-e.maximum,r="Verwyders asseblief "+n+" character";return 1!=n&&(r+="s"),r},inputTooShort:function(e){return"Voer asseblief "+(e.minimum-e.input.length)+" of meer karakters"},loadingMore:function(){return"Meer resultate word gelaai…"},maximumSelected:function(e){var n="Kies asseblief net "+e.maximum+" item";return 1!=e.maximum&&(n+="s"),n},noResults:function(){return"Geen resultate gevind"},searching:function(){return"Besig…"},removeAllItems:function(){return"Verwyder alle items"}}}),e.define,e.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/ar.js b/ext/phpbbstudio/ass/adm/style/js/i18n/ar.js new file mode 100644 index 0000000..2c7914d --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/ar.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/ar",[],function(){return{errorLoading:function(){return"لا يمكن تحميل النتائج"},inputTooLong:function(n){return"الرجاء حذف "+(n.input.length-n.maximum)+" عناصر"},inputTooShort:function(n){return"الرجاء إضافة "+(n.minimum-n.input.length)+" عناصر"},loadingMore:function(){return"جاري تحميل نتائج إضافية..."},maximumSelected:function(n){return"تستطيع إختيار "+n.maximum+" بنود فقط"},noResults:function(){return"لم يتم العثور على أي نتائج"},searching:function(){return"جاري البحث…"},removeAllItems:function(){return"قم بإزالة كل العناصر"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/az.js b/ext/phpbbstudio/ass/adm/style/js/i18n/az.js new file mode 100644 index 0000000..735c930 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/az.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/az",[],function(){return{inputTooLong:function(n){return n.input.length-n.maximum+" simvol silin"},inputTooShort:function(n){return n.minimum-n.input.length+" simvol daxil edin"},loadingMore:function(){return"Daha çox nəticə yüklənir…"},maximumSelected:function(n){return"Sadəcə "+n.maximum+" element seçə bilərsiniz"},noResults:function(){return"Nəticə tapılmadı"},searching:function(){return"Axtarılır…"},removeAllItems:function(){return"Bütün elementləri sil"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/bg.js b/ext/phpbbstudio/ass/adm/style/js/i18n/bg.js new file mode 100644 index 0000000..c631c29 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/bg.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/bg",[],function(){return{inputTooLong:function(n){var e=n.input.length-n.maximum,u="Моля въведете с "+e+" по-малко символ";return e>1&&(u+="a"),u},inputTooShort:function(n){var e=n.minimum-n.input.length,u="Моля въведете още "+e+" символ";return e>1&&(u+="a"),u},loadingMore:function(){return"Зареждат се още…"},maximumSelected:function(n){var e="Можете да направите до "+n.maximum+" ";return n.maximum>1?e+="избора":e+="избор",e},noResults:function(){return"Няма намерени съвпадения"},searching:function(){return"Търсене…"},removeAllItems:function(){return"Премахнете всички елементи"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/bn.js b/ext/phpbbstudio/ass/adm/style/js/i18n/bn.js new file mode 100644 index 0000000..b61888d --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/bn.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/bn",[],function(){return{errorLoading:function(){return"ফলাফলগুলি লোড করা যায়নি।"},inputTooLong:function(n){var e=n.input.length-n.maximum,u="অনুগ্রহ করে "+e+" টি অক্ষর মুছে দিন।";return 1!=e&&(u="অনুগ্রহ করে "+e+" টি অক্ষর মুছে দিন।"),u},inputTooShort:function(n){return n.minimum-n.input.length+" টি অক্ষর অথবা অধিক অক্ষর লিখুন।"},loadingMore:function(){return"আরো ফলাফল লোড হচ্ছে ..."},maximumSelected:function(n){var e=n.maximum+" টি আইটেম নির্বাচন করতে পারবেন।";return 1!=n.maximum&&(e=n.maximum+" টি আইটেম নির্বাচন করতে পারবেন।"),e},noResults:function(){return"কোন ফলাফল পাওয়া যায়নি।"},searching:function(){return"অনুসন্ধান করা হচ্ছে ..."}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/bs.js b/ext/phpbbstudio/ass/adm/style/js/i18n/bs.js new file mode 100644 index 0000000..cafa057 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/bs.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/bs",[],function(){function e(e,n,r,t){return e%10==1&&e%100!=11?n:e%10>=2&&e%10<=4&&(e%100<12||e%100>14)?r:t}return{errorLoading:function(){return"Preuzimanje nije uspijelo."},inputTooLong:function(n){var r=n.input.length-n.maximum,t="Obrišite "+r+" simbol";return t+=e(r,"","a","a")},inputTooShort:function(n){var r=n.minimum-n.input.length,t="Ukucajte bar još "+r+" simbol";return t+=e(r,"","a","a")},loadingMore:function(){return"Preuzimanje još rezultata…"},maximumSelected:function(n){var r="Možete izabrati samo "+n.maximum+" stavk";return r+=e(n.maximum,"u","e","i")},noResults:function(){return"Ništa nije pronađeno"},searching:function(){return"Pretraga…"},removeAllItems:function(){return"Uklonite sve stavke"}}}),e.define,e.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/ca.js b/ext/phpbbstudio/ass/adm/style/js/i18n/ca.js new file mode 100644 index 0000000..614bfbb --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/ca.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/ca",[],function(){return{errorLoading:function(){return"La càrrega ha fallat"},inputTooLong:function(e){var n=e.input.length-e.maximum,r="Si us plau, elimina "+n+" car";return r+=1==n?"àcter":"àcters"},inputTooShort:function(e){var n=e.minimum-e.input.length,r="Si us plau, introdueix "+n+" car";return r+=1==n?"àcter":"àcters"},loadingMore:function(){return"Carregant més resultats…"},maximumSelected:function(e){var n="Només es pot seleccionar "+e.maximum+" element";return 1!=e.maximum&&(n+="s"),n},noResults:function(){return"No s'han trobat resultats"},searching:function(){return"Cercant…"},removeAllItems:function(){return"Treu tots els elements"}}}),e.define,e.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/cs.js b/ext/phpbbstudio/ass/adm/style/js/i18n/cs.js new file mode 100644 index 0000000..a4285a2 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/cs.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/cs",[],function(){function e(e,n){switch(e){case 2:return n?"dva":"dvě";case 3:return"tři";case 4:return"čtyři"}return""}return{errorLoading:function(){return"Výsledky nemohly být načteny."},inputTooLong:function(n){var t=n.input.length-n.maximum;return 1==t?"Prosím, zadejte o jeden znak méně.":t<=4?"Prosím, zadejte o "+e(t,!0)+" znaky méně.":"Prosím, zadejte o "+t+" znaků méně."},inputTooShort:function(n){var t=n.minimum-n.input.length;return 1==t?"Prosím, zadejte ještě jeden znak.":t<=4?"Prosím, zadejte ještě další "+e(t,!0)+" znaky.":"Prosím, zadejte ještě dalších "+t+" znaků."},loadingMore:function(){return"Načítají se další výsledky…"},maximumSelected:function(n){var t=n.maximum;return 1==t?"Můžete zvolit jen jednu položku.":t<=4?"Můžete zvolit maximálně "+e(t,!1)+" položky.":"Můžete zvolit maximálně "+t+" položek."},noResults:function(){return"Nenalezeny žádné položky."},searching:function(){return"Vyhledávání…"},removeAllItems:function(){return"Odstraňte všechny položky"}}}),e.define,e.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/da.js b/ext/phpbbstudio/ass/adm/style/js/i18n/da.js new file mode 100644 index 0000000..4deec69 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/da.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/da",[],function(){return{errorLoading:function(){return"Resultaterne kunne ikke indlæses."},inputTooLong:function(e){return"Angiv venligst "+(e.input.length-e.maximum)+" tegn mindre"},inputTooShort:function(e){return"Angiv venligst "+(e.minimum-e.input.length)+" tegn mere"},loadingMore:function(){return"Indlæser flere resultater…"},maximumSelected:function(e){var n="Du kan kun vælge "+e.maximum+" emne";return 1!=e.maximum&&(n+="r"),n},noResults:function(){return"Ingen resultater fundet"},searching:function(){return"Søger…"},removeAllItems:function(){return"Fjern alle elementer"}}}),e.define,e.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/de.js b/ext/phpbbstudio/ass/adm/style/js/i18n/de.js new file mode 100644 index 0000000..8ab511d --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/de.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/de",[],function(){return{errorLoading:function(){return"Die Ergebnisse konnten nicht geladen werden."},inputTooLong:function(e){return"Bitte "+(e.input.length-e.maximum)+" Zeichen weniger eingeben"},inputTooShort:function(e){return"Bitte "+(e.minimum-e.input.length)+" Zeichen mehr eingeben"},loadingMore:function(){return"Lade mehr Ergebnisse…"},maximumSelected:function(e){var n="Sie können nur "+e.maximum+" Element";return 1!=e.maximum&&(n+="e"),n+=" auswählen"},noResults:function(){return"Keine Übereinstimmungen gefunden"},searching:function(){return"Suche…"},removeAllItems:function(){return"Entferne alle Elemente"}}}),e.define,e.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/dsb.js b/ext/phpbbstudio/ass/adm/style/js/i18n/dsb.js new file mode 100644 index 0000000..495ad0e --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/dsb.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/dsb",[],function(){var n=["znamuško","znamušce","znamuška","znamuškow"],e=["zapisk","zapiska","zapiski","zapiskow"],u=function(n,e){return 1===n?e[0]:2===n?e[1]:n>2&&n<=4?e[2]:n>=5?e[3]:void 0};return{errorLoading:function(){return"Wuslědki njejsu se dali zacytaś."},inputTooLong:function(e){var a=e.input.length-e.maximum;return"Pšosym lašuj "+a+" "+u(a,n)},inputTooShort:function(e){var a=e.minimum-e.input.length;return"Pšosym zapódaj nanejmjenjej "+a+" "+u(a,n)},loadingMore:function(){return"Dalšne wuslědki se zacytaju…"},maximumSelected:function(n){return"Móžoš jano "+n.maximum+" "+u(n.maximum,e)+"wubraś."},noResults:function(){return"Žedne wuslědki namakane"},searching:function(){return"Pyta se…"},removeAllItems:function(){return"Remove all items"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/el.js b/ext/phpbbstudio/ass/adm/style/js/i18n/el.js new file mode 100644 index 0000000..6c30d6a --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/el.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/el",[],function(){return{errorLoading:function(){return"Τα αποτελέσματα δεν μπόρεσαν να φορτώσουν."},inputTooLong:function(n){var e=n.input.length-n.maximum,u="Παρακαλώ διαγράψτε "+e+" χαρακτήρ";return 1==e&&(u+="α"),1!=e&&(u+="ες"),u},inputTooShort:function(n){return"Παρακαλώ συμπληρώστε "+(n.minimum-n.input.length)+" ή περισσότερους χαρακτήρες"},loadingMore:function(){return"Φόρτωση περισσότερων αποτελεσμάτων…"},maximumSelected:function(n){var e="Μπορείτε να επιλέξετε μόνο "+n.maximum+" επιλογ";return 1==n.maximum&&(e+="ή"),1!=n.maximum&&(e+="ές"),e},noResults:function(){return"Δεν βρέθηκαν αποτελέσματα"},searching:function(){return"Αναζήτηση…"},removeAllItems:function(){return"Καταργήστε όλα τα στοιχεία"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/en.js b/ext/phpbbstudio/ass/adm/style/js/i18n/en.js new file mode 100644 index 0000000..47c8431 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/en.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/en",[],function(){return{errorLoading:function(){return"The results could not be loaded."},inputTooLong:function(e){var n=e.input.length-e.maximum,r="Please delete "+n+" character";return 1!=n&&(r+="s"),r},inputTooShort:function(e){return"Please enter "+(e.minimum-e.input.length)+" or more characters"},loadingMore:function(){return"Loading more results…"},maximumSelected:function(e){var n="You can only select "+e.maximum+" item";return 1!=e.maximum&&(n+="s"),n},noResults:function(){return"No results found"},searching:function(){return"Searching…"},removeAllItems:function(){return"Remove all items"}}}),e.define,e.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/es.js b/ext/phpbbstudio/ass/adm/style/js/i18n/es.js new file mode 100644 index 0000000..81a754f --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/es.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/es",[],function(){return{errorLoading:function(){return"No se pudieron cargar los resultados"},inputTooLong:function(e){var n=e.input.length-e.maximum,r="Por favor, elimine "+n+" car";return r+=1==n?"ácter":"acteres"},inputTooShort:function(e){var n=e.minimum-e.input.length,r="Por favor, introduzca "+n+" car";return r+=1==n?"ácter":"acteres"},loadingMore:function(){return"Cargando más resultados…"},maximumSelected:function(e){var n="Sólo puede seleccionar "+e.maximum+" elemento";return 1!=e.maximum&&(n+="s"),n},noResults:function(){return"No se encontraron resultados"},searching:function(){return"Buscando…"},removeAllItems:function(){return"Eliminar todos los elementos"}}}),e.define,e.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/et.js b/ext/phpbbstudio/ass/adm/style/js/i18n/et.js new file mode 100644 index 0000000..e58ed7f --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/et.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/et",[],function(){return{inputTooLong:function(e){var n=e.input.length-e.maximum,t="Sisesta "+n+" täht";return 1!=n&&(t+="e"),t+=" vähem"},inputTooShort:function(e){var n=e.minimum-e.input.length,t="Sisesta "+n+" täht";return 1!=n&&(t+="e"),t+=" rohkem"},loadingMore:function(){return"Laen tulemusi…"},maximumSelected:function(e){var n="Saad vaid "+e.maximum+" tulemus";return 1==e.maximum?n+="e":n+="t",n+=" valida"},noResults:function(){return"Tulemused puuduvad"},searching:function(){return"Otsin…"},removeAllItems:function(){return"Eemalda kõik esemed"}}}),e.define,e.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/eu.js b/ext/phpbbstudio/ass/adm/style/js/i18n/eu.js new file mode 100644 index 0000000..9b30a95 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/eu.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/eu",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Idatzi ";return n+=1==t?"karaktere bat":t+" karaktere",n+=" gutxiago"},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Idatzi ";return n+=1==t?"karaktere bat":t+" karaktere",n+=" gehiago"},loadingMore:function(){return"Emaitza gehiago kargatzen…"},maximumSelected:function(e){return 1===e.maximum?"Elementu bakarra hauta dezakezu":e.maximum+" elementu hauta ditzakezu soilik"},noResults:function(){return"Ez da bat datorrenik aurkitu"},searching:function(){return"Bilatzen…"},removeAllItems:function(){return"Kendu elementu guztiak"}}}),e.define,e.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/fa.js b/ext/phpbbstudio/ass/adm/style/js/i18n/fa.js new file mode 100644 index 0000000..cd6da47 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/fa.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/fa",[],function(){return{errorLoading:function(){return"امکان بارگذاری نتایج وجود ندارد."},inputTooLong:function(n){return"لطفاً "+(n.input.length-n.maximum)+" کاراکتر را حذف نمایید"},inputTooShort:function(n){return"لطفاً تعداد "+(n.minimum-n.input.length)+" کاراکتر یا بیشتر وارد نمایید"},loadingMore:function(){return"در حال بارگذاری نتایج بیشتر..."},maximumSelected:function(n){return"شما تنها می‌توانید "+n.maximum+" آیتم را انتخاب نمایید"},noResults:function(){return"هیچ نتیجه‌ای یافت نشد"},searching:function(){return"در حال جستجو..."},removeAllItems:function(){return"همه موارد را حذف کنید"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/fi.js b/ext/phpbbstudio/ass/adm/style/js/i18n/fi.js new file mode 100644 index 0000000..492391f --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/fi.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/fi",[],function(){return{errorLoading:function(){return"Tuloksia ei saatu ladattua."},inputTooLong:function(n){return"Ole hyvä ja anna "+(n.input.length-n.maximum)+" merkkiä vähemmän"},inputTooShort:function(n){return"Ole hyvä ja anna "+(n.minimum-n.input.length)+" merkkiä lisää"},loadingMore:function(){return"Ladataan lisää tuloksia…"},maximumSelected:function(n){return"Voit valita ainoastaan "+n.maximum+" kpl"},noResults:function(){return"Ei tuloksia"},searching:function(){return"Haetaan…"},removeAllItems:function(){return"Poista kaikki kohteet"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/fr.js b/ext/phpbbstudio/ass/adm/style/js/i18n/fr.js new file mode 100644 index 0000000..3b725f3 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/fr.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/fr",[],function(){return{errorLoading:function(){return"Les résultats ne peuvent pas être chargés."},inputTooLong:function(e){var n=e.input.length-e.maximum;return"Supprimez "+n+" caractère"+(n>1?"s":"")},inputTooShort:function(e){var n=e.minimum-e.input.length;return"Saisissez au moins "+n+" caractère"+(n>1?"s":"")},loadingMore:function(){return"Chargement de résultats supplémentaires…"},maximumSelected:function(e){return"Vous pouvez seulement sélectionner "+e.maximum+" élément"+(e.maximum>1?"s":"")},noResults:function(){return"Aucun résultat trouvé"},searching:function(){return"Recherche en cours…"},removeAllItems:function(){return"Supprimer tous les éléments"}}}),e.define,e.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/gl.js b/ext/phpbbstudio/ass/adm/style/js/i18n/gl.js new file mode 100644 index 0000000..8df845b --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/gl.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/gl",[],function(){return{errorLoading:function(){return"Non foi posíbel cargar os resultados."},inputTooLong:function(e){var n=e.input.length-e.maximum;return 1===n?"Elimine un carácter":"Elimine "+n+" caracteres"},inputTooShort:function(e){var n=e.minimum-e.input.length;return 1===n?"Engada un carácter":"Engada "+n+" caracteres"},loadingMore:function(){return"Cargando máis resultados…"},maximumSelected:function(e){return 1===e.maximum?"Só pode seleccionar un elemento":"Só pode seleccionar "+e.maximum+" elementos"},noResults:function(){return"Non se atoparon resultados"},searching:function(){return"Buscando…"},removeAllItems:function(){return"Elimina todos os elementos"}}}),e.define,e.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/he.js b/ext/phpbbstudio/ass/adm/style/js/i18n/he.js new file mode 100644 index 0000000..b95e596 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/he.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/he",[],function(){return{errorLoading:function(){return"שגיאה בטעינת התוצאות"},inputTooLong:function(n){var e=n.input.length-n.maximum,r="נא למחוק ";return r+=1===e?"תו אחד":e+" תווים"},inputTooShort:function(n){var e=n.minimum-n.input.length,r="נא להכניס ";return r+=1===e?"תו אחד":e+" תווים",r+=" או יותר"},loadingMore:function(){return"טוען תוצאות נוספות…"},maximumSelected:function(n){var e="באפשרותך לבחור עד ";return 1===n.maximum?e+="פריט אחד":e+=n.maximum+" פריטים",e},noResults:function(){return"לא נמצאו תוצאות"},searching:function(){return"מחפש…"},removeAllItems:function(){return"הסר את כל הפריטים"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/hi.js b/ext/phpbbstudio/ass/adm/style/js/i18n/hi.js new file mode 100644 index 0000000..e539ae5 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/hi.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/hi",[],function(){return{errorLoading:function(){return"परिणामों को लोड नहीं किया जा सका।"},inputTooLong:function(n){var e=n.input.length-n.maximum,r=e+" अक्षर को हटा दें";return e>1&&(r=e+" अक्षरों को हटा दें "),r},inputTooShort:function(n){return"कृपया "+(n.minimum-n.input.length)+" या अधिक अक्षर दर्ज करें"},loadingMore:function(){return"अधिक परिणाम लोड हो रहे है..."},maximumSelected:function(n){return"आप केवल "+n.maximum+" आइटम का चयन कर सकते हैं"},noResults:function(){return"कोई परिणाम नहीं मिला"},searching:function(){return"खोज रहा है..."},removeAllItems:function(){return"सभी वस्तुओं को हटा दें"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/hr.js b/ext/phpbbstudio/ass/adm/style/js/i18n/hr.js new file mode 100644 index 0000000..26e5ae7 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/hr.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/hr",[],function(){function n(n){var e=" "+n+" znak";return n%10<5&&n%10>0&&(n%100<5||n%100>19)?n%10>1&&(e+="a"):e+="ova",e}return{errorLoading:function(){return"Preuzimanje nije uspjelo."},inputTooLong:function(e){return"Unesite "+n(e.input.length-e.maximum)},inputTooShort:function(e){return"Unesite još "+n(e.minimum-e.input.length)},loadingMore:function(){return"Učitavanje rezultata…"},maximumSelected:function(n){return"Maksimalan broj odabranih stavki je "+n.maximum},noResults:function(){return"Nema rezultata"},searching:function(){return"Pretraga…"},removeAllItems:function(){return"Ukloni sve stavke"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/hsb.js b/ext/phpbbstudio/ass/adm/style/js/i18n/hsb.js new file mode 100644 index 0000000..48323ad --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/hsb.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/hsb",[],function(){var n=["znamješko","znamješce","znamješka","znamješkow"],e=["zapisk","zapiskaj","zapiski","zapiskow"],u=function(n,e){return 1===n?e[0]:2===n?e[1]:n>2&&n<=4?e[2]:n>=5?e[3]:void 0};return{errorLoading:function(){return"Wuslědki njedachu so začitać."},inputTooLong:function(e){var a=e.input.length-e.maximum;return"Prošu zhašej "+a+" "+u(a,n)},inputTooShort:function(e){var a=e.minimum-e.input.length;return"Prošu zapodaj znajmjeńša "+a+" "+u(a,n)},loadingMore:function(){return"Dalše wuslědki so začitaja…"},maximumSelected:function(n){return"Móžeš jenož "+n.maximum+" "+u(n.maximum,e)+"wubrać"},noResults:function(){return"Žane wuslědki namakane"},searching:function(){return"Pyta so…"},removeAllItems:function(){return"Remove all items"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/hu.js b/ext/phpbbstudio/ass/adm/style/js/i18n/hu.js new file mode 100644 index 0000000..441cd63 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/hu.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/hu",[],function(){return{errorLoading:function(){return"Az eredmények betöltése nem sikerült."},inputTooLong:function(e){return"Túl hosszú. "+(e.input.length-e.maximum)+" karakterrel több, mint kellene."},inputTooShort:function(e){return"Túl rövid. Még "+(e.minimum-e.input.length)+" karakter hiányzik."},loadingMore:function(){return"Töltés…"},maximumSelected:function(e){return"Csak "+e.maximum+" elemet lehet kiválasztani."},noResults:function(){return"Nincs találat."},searching:function(){return"Keresés…"},removeAllItems:function(){return"Távolítson el minden elemet"}}}),e.define,e.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/hy.js b/ext/phpbbstudio/ass/adm/style/js/i18n/hy.js new file mode 100644 index 0000000..b94f512 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/hy.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/hy",[],function(){return{errorLoading:function(){return"Արդյունքները հնարավոր չէ բեռնել։"},inputTooLong:function(n){return"Խնդրում ենք հեռացնել "+(n.input.length-n.maximum)+" նշան"},inputTooShort:function(n){return"Խնդրում ենք մուտքագրել "+(n.minimum-n.input.length)+" կամ ավել նշաններ"},loadingMore:function(){return"Բեռնվում են նոր արդյունքներ․․․"},maximumSelected:function(n){return"Դուք կարող եք ընտրել առավելագույնը "+n.maximum+" կետ"},noResults:function(){return"Արդյունքներ չեն գտնվել"},searching:function(){return"Որոնում․․․"},removeAllItems:function(){return"Հեռացնել բոլոր տարրերը"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/id.js b/ext/phpbbstudio/ass/adm/style/js/i18n/id.js new file mode 100644 index 0000000..c880bf5 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/id.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/id",[],function(){return{errorLoading:function(){return"Data tidak boleh diambil."},inputTooLong:function(n){return"Hapuskan "+(n.input.length-n.maximum)+" huruf"},inputTooShort:function(n){return"Masukkan "+(n.minimum-n.input.length)+" huruf lagi"},loadingMore:function(){return"Mengambil data…"},maximumSelected:function(n){return"Anda hanya dapat memilih "+n.maximum+" pilihan"},noResults:function(){return"Tidak ada data yang sesuai"},searching:function(){return"Mencari…"},removeAllItems:function(){return"Hapus semua item"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/is.js b/ext/phpbbstudio/ass/adm/style/js/i18n/is.js new file mode 100644 index 0000000..99f318b --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/is.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/is",[],function(){return{inputTooLong:function(n){var t=n.input.length-n.maximum,e="Vinsamlegast styttið texta um "+t+" staf";return t<=1?e:e+"i"},inputTooShort:function(n){var t=n.minimum-n.input.length,e="Vinsamlegast skrifið "+t+" staf";return t>1&&(e+="i"),e+=" í viðbót"},loadingMore:function(){return"Sæki fleiri niðurstöður…"},maximumSelected:function(n){return"Þú getur aðeins valið "+n.maximum+" atriði"},noResults:function(){return"Ekkert fannst"},searching:function(){return"Leita…"},removeAllItems:function(){return"Fjarlægðu öll atriði"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/it.js b/ext/phpbbstudio/ass/adm/style/js/i18n/it.js new file mode 100644 index 0000000..a5223f4 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/it.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/it",[],function(){return{errorLoading:function(){return"I risultati non possono essere caricati."},inputTooLong:function(e){var n=e.input.length-e.maximum,t="Per favore cancella "+n+" caratter";return t+=1!==n?"i":"e"},inputTooShort:function(e){return"Per favore inserisci "+(e.minimum-e.input.length)+" o più caratteri"},loadingMore:function(){return"Caricando più risultati…"},maximumSelected:function(e){var n="Puoi selezionare solo "+e.maximum+" element";return 1!==e.maximum?n+="i":n+="o",n},noResults:function(){return"Nessun risultato trovato"},searching:function(){return"Sto cercando…"},removeAllItems:function(){return"Rimuovi tutti gli oggetti"}}}),e.define,e.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/ja.js b/ext/phpbbstudio/ass/adm/style/js/i18n/ja.js new file mode 100644 index 0000000..e75885e --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/ja.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/ja",[],function(){return{errorLoading:function(){return"結果が読み込まれませんでした"},inputTooLong:function(n){return n.input.length-n.maximum+" 文字を削除してください"},inputTooShort:function(n){return"少なくとも "+(n.minimum-n.input.length)+" 文字を入力してください"},loadingMore:function(){return"読み込み中…"},maximumSelected:function(n){return n.maximum+" 件しか選択できません"},noResults:function(){return"対象が見つかりません"},searching:function(){return"検索しています…"},removeAllItems:function(){return"すべてのアイテムを削除"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/ka.js b/ext/phpbbstudio/ass/adm/style/js/i18n/ka.js new file mode 100644 index 0000000..677065a --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/ka.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/ka",[],function(){return{errorLoading:function(){return"მონაცემების ჩატვირთვა შეუძლებელია."},inputTooLong:function(n){return"გთხოვთ აკრიფეთ "+(n.input.length-n.maximum)+" სიმბოლოთი ნაკლები"},inputTooShort:function(n){return"გთხოვთ აკრიფეთ "+(n.minimum-n.input.length)+" სიმბოლო ან მეტი"},loadingMore:function(){return"მონაცემების ჩატვირთვა…"},maximumSelected:function(n){return"თქვენ შეგიძლიათ აირჩიოთ არაუმეტეს "+n.maximum+" ელემენტი"},noResults:function(){return"რეზულტატი არ მოიძებნა"},searching:function(){return"ძიება…"},removeAllItems:function(){return"ამოიღე ყველა ელემენტი"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/km.js b/ext/phpbbstudio/ass/adm/style/js/i18n/km.js new file mode 100644 index 0000000..6a09e92 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/km.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/km",[],function(){return{errorLoading:function(){return"មិនអាចទាញយកទិន្នន័យ"},inputTooLong:function(n){return"សូមលុបចេញ "+(n.input.length-n.maximum)+" អក្សរ"},inputTooShort:function(n){return"សូមបញ្ចូល"+(n.minimum-n.input.length)+" អក្សរ រឺ ច្រើនជាងនេះ"},loadingMore:function(){return"កំពុងទាញយកទិន្នន័យបន្ថែម..."},maximumSelected:function(n){return"អ្នកអាចជ្រើសរើសបានតែ "+n.maximum+" ជម្រើសប៉ុណ្ណោះ"},noResults:function(){return"មិនមានលទ្ធផល"},searching:function(){return"កំពុងស្វែងរក..."},removeAllItems:function(){return"លុបធាតុទាំងអស់"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/ko.js b/ext/phpbbstudio/ass/adm/style/js/i18n/ko.js new file mode 100644 index 0000000..10ac13d --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/ko.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/ko",[],function(){return{errorLoading:function(){return"결과를 불러올 수 없습니다."},inputTooLong:function(n){return"너무 깁니다. "+(n.input.length-n.maximum)+" 글자 지워주세요."},inputTooShort:function(n){return"너무 짧습니다. "+(n.minimum-n.input.length)+" 글자 더 입력해주세요."},loadingMore:function(){return"불러오는 중…"},maximumSelected:function(n){return"최대 "+n.maximum+"개까지만 선택 가능합니다."},noResults:function(){return"결과가 없습니다."},searching:function(){return"검색 중…"},removeAllItems:function(){return"모든 항목 삭제"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/lt.js b/ext/phpbbstudio/ass/adm/style/js/i18n/lt.js new file mode 100644 index 0000000..3097941 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/lt.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/lt",[],function(){function n(n,e,i,t){return n%10==1&&(n%100<11||n%100>19)?e:n%10>=2&&n%10<=9&&(n%100<11||n%100>19)?i:t}return{inputTooLong:function(e){var i=e.input.length-e.maximum,t="Pašalinkite "+i+" simbol";return t+=n(i,"į","ius","ių")},inputTooShort:function(e){var i=e.minimum-e.input.length,t="Įrašykite dar "+i+" simbol";return t+=n(i,"į","ius","ių")},loadingMore:function(){return"Kraunama daugiau rezultatų…"},maximumSelected:function(e){var i="Jūs galite pasirinkti tik "+e.maximum+" element";return i+=n(e.maximum,"ą","us","ų")},noResults:function(){return"Atitikmenų nerasta"},searching:function(){return"Ieškoma…"},removeAllItems:function(){return"Pašalinti visus elementus"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/lv.js b/ext/phpbbstudio/ass/adm/style/js/i18n/lv.js new file mode 100644 index 0000000..4ec8b72 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/lv.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/lv",[],function(){function e(e,n,u,i){return 11===e?n:e%10==1?u:i}return{inputTooLong:function(n){var u=n.input.length-n.maximum,i="Lūdzu ievadiet par "+u;return(i+=" simbol"+e(u,"iem","u","iem"))+" mazāk"},inputTooShort:function(n){var u=n.minimum-n.input.length,i="Lūdzu ievadiet vēl "+u;return i+=" simbol"+e(u,"us","u","us")},loadingMore:function(){return"Datu ielāde…"},maximumSelected:function(n){var u="Jūs varat izvēlēties ne vairāk kā "+n.maximum;return u+=" element"+e(n.maximum,"us","u","us")},noResults:function(){return"Sakritību nav"},searching:function(){return"Meklēšana…"},removeAllItems:function(){return"Noņemt visus vienumus"}}}),e.define,e.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/mk.js b/ext/phpbbstudio/ass/adm/style/js/i18n/mk.js new file mode 100644 index 0000000..e303ac0 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/mk.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/mk",[],function(){return{inputTooLong:function(n){var e=(n.input.length,n.maximum,"Ве молиме внесете "+n.maximum+" помалку карактер");return 1!==n.maximum&&(e+="и"),e},inputTooShort:function(n){var e=(n.minimum,n.input.length,"Ве молиме внесете уште "+n.maximum+" карактер");return 1!==n.maximum&&(e+="и"),e},loadingMore:function(){return"Вчитување резултати…"},maximumSelected:function(n){var e="Можете да изберете само "+n.maximum+" ставк";return 1===n.maximum?e+="а":e+="и",e},noResults:function(){return"Нема пронајдено совпаѓања"},searching:function(){return"Пребарување…"},removeAllItems:function(){return"Отстрани ги сите предмети"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/ms.js b/ext/phpbbstudio/ass/adm/style/js/i18n/ms.js new file mode 100644 index 0000000..11f0ce9 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/ms.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/ms",[],function(){return{errorLoading:function(){return"Keputusan tidak berjaya dimuatkan."},inputTooLong:function(n){return"Sila hapuskan "+(n.input.length-n.maximum)+" aksara"},inputTooShort:function(n){return"Sila masukkan "+(n.minimum-n.input.length)+" atau lebih aksara"},loadingMore:function(){return"Sedang memuatkan keputusan…"},maximumSelected:function(n){return"Anda hanya boleh memilih "+n.maximum+" pilihan"},noResults:function(){return"Tiada padanan yang ditemui"},searching:function(){return"Mencari…"},removeAllItems:function(){return"Keluarkan semua item"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/nb.js b/ext/phpbbstudio/ass/adm/style/js/i18n/nb.js new file mode 100644 index 0000000..6d73059 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/nb.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/nb",[],function(){return{errorLoading:function(){return"Kunne ikke hente resultater."},inputTooLong:function(e){return"Vennligst fjern "+(e.input.length-e.maximum)+" tegn"},inputTooShort:function(e){return"Vennligst skriv inn "+(e.minimum-e.input.length)+" tegn til"},loadingMore:function(){return"Laster flere resultater…"},maximumSelected:function(e){return"Du kan velge maks "+e.maximum+" elementer"},noResults:function(){return"Ingen treff"},searching:function(){return"Søker…"},removeAllItems:function(){return"Fjern alle elementer"}}}),e.define,e.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/ne.js b/ext/phpbbstudio/ass/adm/style/js/i18n/ne.js new file mode 100644 index 0000000..dabc198 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/ne.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/ne",[],function(){return{errorLoading:function(){return"नतिजाहरु देखाउन सकिएन।"},inputTooLong:function(n){var e=n.input.length-n.maximum,u="कृपया "+e+" अक्षर मेटाउनुहोस्।";return 1!=e&&(u+="कृपया "+e+" अक्षरहरु मेटाउनुहोस्।"),u},inputTooShort:function(n){return"कृपया बाँकी रहेका "+(n.minimum-n.input.length)+" वा अरु धेरै अक्षरहरु भर्नुहोस्।"},loadingMore:function(){return"अरु नतिजाहरु भरिँदैछन् …"},maximumSelected:function(n){var e="तँपाई "+n.maximum+" वस्तु मात्र छान्न पाउँनुहुन्छ।";return 1!=n.maximum&&(e="तँपाई "+n.maximum+" वस्तुहरु मात्र छान्न पाउँनुहुन्छ।"),e},noResults:function(){return"कुनै पनि नतिजा भेटिएन।"},searching:function(){return"खोजि हुँदैछ…"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/nl.js b/ext/phpbbstudio/ass/adm/style/js/i18n/nl.js new file mode 100644 index 0000000..a2fec10 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/nl.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/nl",[],function(){return{errorLoading:function(){return"De resultaten konden niet worden geladen."},inputTooLong:function(e){return"Gelieve "+(e.input.length-e.maximum)+" karakters te verwijderen"},inputTooShort:function(e){return"Gelieve "+(e.minimum-e.input.length)+" of meer karakters in te voeren"},loadingMore:function(){return"Meer resultaten laden…"},maximumSelected:function(e){var n=1==e.maximum?"kan":"kunnen",r="Er "+n+" maar "+e.maximum+" item";return 1!=e.maximum&&(r+="s"),r+=" worden geselecteerd"},noResults:function(){return"Geen resultaten gevonden…"},searching:function(){return"Zoeken…"},removeAllItems:function(){return"Verwijder alle items"}}}),e.define,e.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/pl.js b/ext/phpbbstudio/ass/adm/style/js/i18n/pl.js new file mode 100644 index 0000000..fd196ff --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/pl.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/pl",[],function(){var n=["znak","znaki","znaków"],e=["element","elementy","elementów"],r=function(n,e){return 1===n?e[0]:n>1&&n<=4?e[1]:n>=5?e[2]:void 0};return{errorLoading:function(){return"Nie można załadować wyników."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Usuń "+t+" "+r(t,n)},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Podaj przynajmniej "+t+" "+r(t,n)},loadingMore:function(){return"Trwa ładowanie…"},maximumSelected:function(n){return"Możesz zaznaczyć tylko "+n.maximum+" "+r(n.maximum,e)},noResults:function(){return"Brak wyników"},searching:function(){return"Trwa wyszukiwanie…"},removeAllItems:function(){return"Usuń wszystkie przedmioty"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/ps.js b/ext/phpbbstudio/ass/adm/style/js/i18n/ps.js new file mode 100644 index 0000000..fbff72a --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/ps.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/ps",[],function(){return{errorLoading:function(){return"پايلي نه سي ترلاسه کېدای"},inputTooLong:function(n){var e=n.input.length-n.maximum,r="د مهربانۍ لمخي "+e+" توری ړنګ کړئ";return 1!=e&&(r=r.replace("توری","توري")),r},inputTooShort:function(n){return"لږ تر لږه "+(n.minimum-n.input.length)+" يا ډېر توري وليکئ"},loadingMore:function(){return"نوري پايلي ترلاسه کيږي..."},maximumSelected:function(n){var e="تاسو يوازي "+n.maximum+" قلم په نښه کولای سی";return 1!=n.maximum&&(e=e.replace("قلم","قلمونه")),e},noResults:function(){return"پايلي و نه موندل سوې"},searching:function(){return"لټول کيږي..."},removeAllItems:function(){return"ټول توکي لرې کړئ"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/pt-BR.js b/ext/phpbbstudio/ass/adm/style/js/i18n/pt-BR.js new file mode 100644 index 0000000..f5e98c1 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/pt-BR.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/pt-BR",[],function(){return{errorLoading:function(){return"Os resultados não puderam ser carregados."},inputTooLong:function(e){var n=e.input.length-e.maximum,r="Apague "+n+" caracter";return 1!=n&&(r+="es"),r},inputTooShort:function(e){return"Digite "+(e.minimum-e.input.length)+" ou mais caracteres"},loadingMore:function(){return"Carregando mais resultados…"},maximumSelected:function(e){var n="Você só pode selecionar "+e.maximum+" ite";return 1==e.maximum?n+="m":n+="ns",n},noResults:function(){return"Nenhum resultado encontrado"},searching:function(){return"Buscando…"},removeAllItems:function(){return"Remover todos os itens"}}}),e.define,e.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/pt.js b/ext/phpbbstudio/ass/adm/style/js/i18n/pt.js new file mode 100644 index 0000000..cca8dcc --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/pt.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/pt",[],function(){return{errorLoading:function(){return"Os resultados não puderam ser carregados."},inputTooLong:function(e){var r=e.input.length-e.maximum,n="Por favor apague "+r+" ";return n+=1!=r?"caracteres":"caractere"},inputTooShort:function(e){return"Introduza "+(e.minimum-e.input.length)+" ou mais caracteres"},loadingMore:function(){return"A carregar mais resultados…"},maximumSelected:function(e){var r="Apenas pode seleccionar "+e.maximum+" ";return r+=1!=e.maximum?"itens":"item"},noResults:function(){return"Sem resultados"},searching:function(){return"A procurar…"},removeAllItems:function(){return"Remover todos os itens"}}}),e.define,e.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/ro.js b/ext/phpbbstudio/ass/adm/style/js/i18n/ro.js new file mode 100644 index 0000000..303f11a --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/ro.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/ro",[],function(){return{errorLoading:function(){return"Rezultatele nu au putut fi incărcate."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Vă rugăm să ștergeți"+t+" caracter";return 1!==t&&(n+="e"),n},inputTooShort:function(e){return"Vă rugăm să introduceți "+(e.minimum-e.input.length)+" sau mai multe caractere"},loadingMore:function(){return"Se încarcă mai multe rezultate…"},maximumSelected:function(e){var t="Aveți voie să selectați cel mult "+e.maximum;return t+=" element",1!==e.maximum&&(t+="e"),t},noResults:function(){return"Nu au fost găsite rezultate"},searching:function(){return"Căutare…"},removeAllItems:function(){return"Eliminați toate elementele"}}}),e.define,e.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/ru.js b/ext/phpbbstudio/ass/adm/style/js/i18n/ru.js new file mode 100644 index 0000000..48b43b7 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/ru.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/ru",[],function(){function n(n,e,r,u){return n%10<5&&n%10>0&&n%100<5||n%100>20?n%10>1?r:e:u}return{errorLoading:function(){return"Невозможно загрузить результаты"},inputTooLong:function(e){var r=e.input.length-e.maximum,u="Пожалуйста, введите на "+r+" символ";return u+=n(r,"","a","ов"),u+=" меньше"},inputTooShort:function(e){var r=e.minimum-e.input.length,u="Пожалуйста, введите ещё хотя бы "+r+" символ";return u+=n(r,"","a","ов")},loadingMore:function(){return"Загрузка данных…"},maximumSelected:function(e){var r="Вы можете выбрать не более "+e.maximum+" элемент";return r+=n(e.maximum,"","a","ов")},noResults:function(){return"Совпадений не найдено"},searching:function(){return"Поиск…"},removeAllItems:function(){return"Удалить все элементы"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/sk.js b/ext/phpbbstudio/ass/adm/style/js/i18n/sk.js new file mode 100644 index 0000000..4fe9346 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/sk.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/sk",[],function(){var e={2:function(e){return e?"dva":"dve"},3:function(){return"tri"},4:function(){return"štyri"}};return{errorLoading:function(){return"Výsledky sa nepodarilo načítať."},inputTooLong:function(n){var t=n.input.length-n.maximum;return 1==t?"Prosím, zadajte o jeden znak menej":t>=2&&t<=4?"Prosím, zadajte o "+e[t](!0)+" znaky menej":"Prosím, zadajte o "+t+" znakov menej"},inputTooShort:function(n){var t=n.minimum-n.input.length;return 1==t?"Prosím, zadajte ešte jeden znak":t<=4?"Prosím, zadajte ešte ďalšie "+e[t](!0)+" znaky":"Prosím, zadajte ešte ďalších "+t+" znakov"},loadingMore:function(){return"Načítanie ďalších výsledkov…"},maximumSelected:function(n){return 1==n.maximum?"Môžete zvoliť len jednu položku":n.maximum>=2&&n.maximum<=4?"Môžete zvoliť najviac "+e[n.maximum](!1)+" položky":"Môžete zvoliť najviac "+n.maximum+" položiek"},noResults:function(){return"Nenašli sa žiadne položky"},searching:function(){return"Vyhľadávanie…"},removeAllItems:function(){return"Odstráňte všetky položky"}}}),e.define,e.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/sl.js b/ext/phpbbstudio/ass/adm/style/js/i18n/sl.js new file mode 100644 index 0000000..30706bc --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/sl.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/sl",[],function(){return{errorLoading:function(){return"Zadetkov iskanja ni bilo mogoče naložiti."},inputTooLong:function(e){var n=e.input.length-e.maximum,t="Prosim zbrišite "+n+" znak";return 2==n?t+="a":1!=n&&(t+="e"),t},inputTooShort:function(e){var n=e.minimum-e.input.length,t="Prosim vpišite še "+n+" znak";return 2==n?t+="a":1!=n&&(t+="e"),t},loadingMore:function(){return"Nalagam več zadetkov…"},maximumSelected:function(e){var n="Označite lahko največ "+e.maximum+" predmet";return 2==e.maximum?n+="a":1!=e.maximum&&(n+="e"),n},noResults:function(){return"Ni zadetkov."},searching:function(){return"Iščem…"},removeAllItems:function(){return"Odstranite vse elemente"}}}),e.define,e.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/sq.js b/ext/phpbbstudio/ass/adm/style/js/i18n/sq.js new file mode 100644 index 0000000..99528e1 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/sq.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/sq",[],function(){return{errorLoading:function(){return"Rezultatet nuk mund të ngarkoheshin."},inputTooLong:function(e){var n=e.input.length-e.maximum,t="Të lutem fshi "+n+" karakter";return 1!=n&&(t+="e"),t},inputTooShort:function(e){return"Të lutem shkruaj "+(e.minimum-e.input.length)+" ose më shumë karaktere"},loadingMore:function(){return"Duke ngarkuar më shumë rezultate…"},maximumSelected:function(e){var n="Mund të zgjedhësh vetëm "+e.maximum+" element";return 1!=e.maximum&&(n+="e"),n},noResults:function(){return"Nuk u gjet asnjë rezultat"},searching:function(){return"Duke kërkuar…"},removeAllItems:function(){return"Hiq të gjitha sendet"}}}),e.define,e.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/sr-Cyrl.js b/ext/phpbbstudio/ass/adm/style/js/i18n/sr-Cyrl.js new file mode 100644 index 0000000..9c180c8 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/sr-Cyrl.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/sr-Cyrl",[],function(){function n(n,e,r,u){return n%10==1&&n%100!=11?e:n%10>=2&&n%10<=4&&(n%100<12||n%100>14)?r:u}return{errorLoading:function(){return"Преузимање није успело."},inputTooLong:function(e){var r=e.input.length-e.maximum,u="Обришите "+r+" симбол";return u+=n(r,"","а","а")},inputTooShort:function(e){var r=e.minimum-e.input.length,u="Укуцајте бар још "+r+" симбол";return u+=n(r,"","а","а")},loadingMore:function(){return"Преузимање још резултата…"},maximumSelected:function(e){var r="Можете изабрати само "+e.maximum+" ставк";return r+=n(e.maximum,"у","е","и")},noResults:function(){return"Ништа није пронађено"},searching:function(){return"Претрага…"},removeAllItems:function(){return"Уклоните све ставке"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/sr.js b/ext/phpbbstudio/ass/adm/style/js/i18n/sr.js new file mode 100644 index 0000000..579e312 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/sr.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/sr",[],function(){function n(n,e,r,t){return n%10==1&&n%100!=11?e:n%10>=2&&n%10<=4&&(n%100<12||n%100>14)?r:t}return{errorLoading:function(){return"Preuzimanje nije uspelo."},inputTooLong:function(e){var r=e.input.length-e.maximum,t="Obrišite "+r+" simbol";return t+=n(r,"","a","a")},inputTooShort:function(e){var r=e.minimum-e.input.length,t="Ukucajte bar još "+r+" simbol";return t+=n(r,"","a","a")},loadingMore:function(){return"Preuzimanje još rezultata…"},maximumSelected:function(e){var r="Možete izabrati samo "+e.maximum+" stavk";return r+=n(e.maximum,"u","e","i")},noResults:function(){return"Ništa nije pronađeno"},searching:function(){return"Pretraga…"},removeAllItems:function(){return"Уклоните све ставке"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/sv.js b/ext/phpbbstudio/ass/adm/style/js/i18n/sv.js new file mode 100644 index 0000000..a89cf7e --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/sv.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/sv",[],function(){return{errorLoading:function(){return"Resultat kunde inte laddas."},inputTooLong:function(n){return"Vänligen sudda ut "+(n.input.length-n.maximum)+" tecken"},inputTooShort:function(n){return"Vänligen skriv in "+(n.minimum-n.input.length)+" eller fler tecken"},loadingMore:function(){return"Laddar fler resultat…"},maximumSelected:function(n){return"Du kan max välja "+n.maximum+" element"},noResults:function(){return"Inga träffar"},searching:function(){return"Söker…"},removeAllItems:function(){return"Ta bort alla objekt"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/th.js b/ext/phpbbstudio/ass/adm/style/js/i18n/th.js new file mode 100644 index 0000000..536fe83 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/th.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/th",[],function(){return{errorLoading:function(){return"ไม่สามารถค้นข้อมูลได้"},inputTooLong:function(n){return"โปรดลบออก "+(n.input.length-n.maximum)+" ตัวอักษร"},inputTooShort:function(n){return"โปรดพิมพ์เพิ่มอีก "+(n.minimum-n.input.length)+" ตัวอักษร"},loadingMore:function(){return"กำลังค้นข้อมูลเพิ่ม…"},maximumSelected:function(n){return"คุณสามารถเลือกได้ไม่เกิน "+n.maximum+" รายการ"},noResults:function(){return"ไม่พบข้อมูล"},searching:function(){return"กำลังค้นข้อมูล…"},removeAllItems:function(){return"ลบรายการทั้งหมด"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/tk.js b/ext/phpbbstudio/ass/adm/style/js/i18n/tk.js new file mode 100644 index 0000000..0a392a0 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/tk.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/tk",[],function(){return{errorLoading:function(){return"Netije ýüklenmedi."},inputTooLong:function(e){return e.input.length-e.maximum+" harp bozuň."},inputTooShort:function(e){return"Ýene-de iň az "+(e.minimum-e.input.length)+" harp ýazyň."},loadingMore:function(){return"Köpräk netije görkezilýär…"},maximumSelected:function(e){return"Diňe "+e.maximum+" sanysyny saýlaň."},noResults:function(){return"Netije tapylmady."},searching:function(){return"Gözlenýär…"},removeAllItems:function(){return"Remove all items"}}}),e.define,e.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/tr.js b/ext/phpbbstudio/ass/adm/style/js/i18n/tr.js new file mode 100644 index 0000000..e81f326 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/tr.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/tr",[],function(){return{errorLoading:function(){return"Sonuç yüklenemedi"},inputTooLong:function(n){return n.input.length-n.maximum+" karakter daha girmelisiniz"},inputTooShort:function(n){return"En az "+(n.minimum-n.input.length)+" karakter daha girmelisiniz"},loadingMore:function(){return"Daha fazla…"},maximumSelected:function(n){return"Sadece "+n.maximum+" seçim yapabilirsiniz"},noResults:function(){return"Sonuç bulunamadı"},searching:function(){return"Aranıyor…"},removeAllItems:function(){return"Tüm öğeleri kaldır"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/uk.js b/ext/phpbbstudio/ass/adm/style/js/i18n/uk.js new file mode 100644 index 0000000..6e5fe03 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/uk.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/uk",[],function(){function n(n,e,u,r){return n%100>10&&n%100<15?r:n%10==1?e:n%10>1&&n%10<5?u:r}return{errorLoading:function(){return"Неможливо завантажити результати"},inputTooLong:function(e){return"Будь ласка, видаліть "+(e.input.length-e.maximum)+" "+n(e.maximum,"літеру","літери","літер")},inputTooShort:function(n){return"Будь ласка, введіть "+(n.minimum-n.input.length)+" або більше літер"},loadingMore:function(){return"Завантаження інших результатів…"},maximumSelected:function(e){return"Ви можете вибрати лише "+e.maximum+" "+n(e.maximum,"пункт","пункти","пунктів")},noResults:function(){return"Нічого не знайдено"},searching:function(){return"Пошук…"},removeAllItems:function(){return"Видалити всі елементи"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/vi.js b/ext/phpbbstudio/ass/adm/style/js/i18n/vi.js new file mode 100644 index 0000000..8409a1d --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/vi.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/vi",[],function(){return{inputTooLong:function(n){return"Vui lòng xóa bớt "+(n.input.length-n.maximum)+" ký tự"},inputTooShort:function(n){return"Vui lòng nhập thêm từ "+(n.minimum-n.input.length)+" ký tự trở lên"},loadingMore:function(){return"Đang lấy thêm kết quả…"},maximumSelected:function(n){return"Chỉ có thể chọn được "+n.maximum+" lựa chọn"},noResults:function(){return"Không tìm thấy kết quả"},searching:function(){return"Đang tìm…"},removeAllItems:function(){return"Xóa tất cả các mục"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/zh-CN.js b/ext/phpbbstudio/ass/adm/style/js/i18n/zh-CN.js new file mode 100644 index 0000000..7e42fd3 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/zh-CN.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/zh-CN",[],function(){return{errorLoading:function(){return"无法载入结果。"},inputTooLong:function(n){return"请删除"+(n.input.length-n.maximum)+"个字符"},inputTooShort:function(n){return"请再输入至少"+(n.minimum-n.input.length)+"个字符"},loadingMore:function(){return"载入更多结果…"},maximumSelected:function(n){return"最多只能选择"+n.maximum+"个项目"},noResults:function(){return"未找到结果"},searching:function(){return"搜索中…"},removeAllItems:function(){return"删除所有项目"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/i18n/zh-TW.js b/ext/phpbbstudio/ass/adm/style/js/i18n/zh-TW.js new file mode 100644 index 0000000..05bae5c --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/i18n/zh-TW.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/zh-TW",[],function(){return{inputTooLong:function(n){return"請刪掉"+(n.input.length-n.maximum)+"個字元"},inputTooShort:function(n){return"請再輸入"+(n.minimum-n.input.length)+"個字元"},loadingMore:function(){return"載入中…"},maximumSelected:function(n){return"你只能選擇最多"+n.maximum+"項"},noResults:function(){return"沒有找到相符的項目"},searching:function(){return"搜尋中…"},removeAllItems:function(){return"刪除所有項目"}}}),n.define,n.require}(); \ No newline at end of file diff --git a/ext/phpbbstudio/ass/adm/style/js/moment.min.js b/ext/phpbbstudio/ass/adm/style/js/moment.min.js new file mode 100644 index 0000000..5787a40 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/moment.min.js @@ -0,0 +1 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.moment=t()}(this,function(){"use strict";var e,i;function c(){return e.apply(null,arguments)}function o(e){return e instanceof Array||"[object Array]"===Object.prototype.toString.call(e)}function u(e){return null!=e&&"[object Object]"===Object.prototype.toString.call(e)}function l(e){return void 0===e}function h(e){return"number"==typeof e||"[object Number]"===Object.prototype.toString.call(e)}function d(e){return e instanceof Date||"[object Date]"===Object.prototype.toString.call(e)}function f(e,t){var n,s=[];for(n=0;n>>0,s=0;sSe(e)?(r=e+1,o-Se(e)):(r=e,o),{year:r,dayOfYear:a}}function Ie(e,t,n){var s,i,r=Ve(e.year(),t,n),a=Math.floor((e.dayOfYear()-r-1)/7)+1;return a<1?s=a+Ae(i=e.year()-1,t,n):a>Ae(e.year(),t,n)?(s=a-Ae(e.year(),t,n),i=e.year()+1):(i=e.year(),s=a),{week:s,year:i}}function Ae(e,t,n){var s=Ve(e,t,n),i=Ve(e+1,t,n);return(Se(e)-s+i)/7}I("w",["ww",2],"wo","week"),I("W",["WW",2],"Wo","isoWeek"),C("week","w"),C("isoWeek","W"),F("week",5),F("isoWeek",5),ue("w",B),ue("ww",B,z),ue("W",B),ue("WW",B,z),fe(["w","ww","W","WW"],function(e,t,n,s){t[s.substr(0,1)]=D(e)});function je(e,t){return e.slice(t,7).concat(e.slice(0,t))}I("d",0,"do","day"),I("dd",0,0,function(e){return this.localeData().weekdaysMin(this,e)}),I("ddd",0,0,function(e){return this.localeData().weekdaysShort(this,e)}),I("dddd",0,0,function(e){return this.localeData().weekdays(this,e)}),I("e",0,0,"weekday"),I("E",0,0,"isoWeekday"),C("day","d"),C("weekday","e"),C("isoWeekday","E"),F("day",11),F("weekday",11),F("isoWeekday",11),ue("d",B),ue("e",B),ue("E",B),ue("dd",function(e,t){return t.weekdaysMinRegex(e)}),ue("ddd",function(e,t){return t.weekdaysShortRegex(e)}),ue("dddd",function(e,t){return t.weekdaysRegex(e)}),fe(["dd","ddd","dddd"],function(e,t,n,s){var i=n._locale.weekdaysParse(e,s,n._strict);null!=i?t.d=i:g(n).invalidWeekday=e}),fe(["d","e","E"],function(e,t,n,s){t[s]=D(e)});var Ze="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_");var ze="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_");var $e="Su_Mo_Tu_We_Th_Fr_Sa".split("_");var qe=ae;var Je=ae;var Be=ae;function Qe(){function e(e,t){return t.length-e.length}var t,n,s,i,r,a=[],o=[],u=[],l=[];for(t=0;t<7;t++)n=y([2e3,1]).day(t),s=this.weekdaysMin(n,""),i=this.weekdaysShort(n,""),r=this.weekdays(n,""),a.push(s),o.push(i),u.push(r),l.push(s),l.push(i),l.push(r);for(a.sort(e),o.sort(e),u.sort(e),l.sort(e),t=0;t<7;t++)o[t]=he(o[t]),u[t]=he(u[t]),l[t]=he(l[t]);this._weekdaysRegex=new RegExp("^("+l.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+u.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+o.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+a.join("|")+")","i")}function Xe(){return this.hours()%12||12}function Ke(e,t){I(e,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),t)})}function et(e,t){return t._meridiemParse}I("H",["HH",2],0,"hour"),I("h",["hh",2],0,Xe),I("k",["kk",2],0,function(){return this.hours()||24}),I("hmm",0,0,function(){return""+Xe.apply(this)+L(this.minutes(),2)}),I("hmmss",0,0,function(){return""+Xe.apply(this)+L(this.minutes(),2)+L(this.seconds(),2)}),I("Hmm",0,0,function(){return""+this.hours()+L(this.minutes(),2)}),I("Hmmss",0,0,function(){return""+this.hours()+L(this.minutes(),2)+L(this.seconds(),2)}),Ke("a",!0),Ke("A",!1),C("hour","h"),F("hour",13),ue("a",et),ue("A",et),ue("H",B),ue("h",B),ue("k",B),ue("HH",B,z),ue("hh",B,z),ue("kk",B,z),ue("hmm",Q),ue("hmmss",X),ue("Hmm",Q),ue("Hmmss",X),ce(["H","HH"],ge),ce(["k","kk"],function(e,t,n){var s=D(e);t[ge]=24===s?0:s}),ce(["a","A"],function(e,t,n){n._isPm=n._locale.isPM(e),n._meridiem=e}),ce(["h","hh"],function(e,t,n){t[ge]=D(e),g(n).bigHour=!0}),ce("hmm",function(e,t,n){var s=e.length-2;t[ge]=D(e.substr(0,s)),t[ve]=D(e.substr(s)),g(n).bigHour=!0}),ce("hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[ge]=D(e.substr(0,s)),t[ve]=D(e.substr(s,2)),t[pe]=D(e.substr(i)),g(n).bigHour=!0}),ce("Hmm",function(e,t,n){var s=e.length-2;t[ge]=D(e.substr(0,s)),t[ve]=D(e.substr(s))}),ce("Hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[ge]=D(e.substr(0,s)),t[ve]=D(e.substr(s,2)),t[pe]=D(e.substr(i))});var tt,nt=Te("Hours",!0),st={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:Ce,monthsShort:He,week:{dow:0,doy:6},weekdays:Ze,weekdaysMin:$e,weekdaysShort:ze,meridiemParse:/[ap]\.?m?\.?/i},it={},rt={};function at(e){return e?e.toLowerCase().replace("_","-"):e}function ot(e){var t=null;if(!it[e]&&"undefined"!=typeof module&&module&&module.exports)try{t=tt._abbr,require("./locale/"+e),ut(t)}catch(e){}return it[e]}function ut(e,t){var n;return e&&((n=l(t)?ht(e):lt(e,t))?tt=n:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+e+" not found. Did you forget to load it?")),tt._abbr}function lt(e,t){if(null===t)return delete it[e],null;var n,s=st;if(t.abbr=e,null!=it[e])T("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),s=it[e]._config;else if(null!=t.parentLocale)if(null!=it[t.parentLocale])s=it[t.parentLocale]._config;else{if(null==(n=ot(t.parentLocale)))return rt[t.parentLocale]||(rt[t.parentLocale]=[]),rt[t.parentLocale].push({name:e,config:t}),null;s=n._config}return it[e]=new P(x(s,t)),rt[e]&&rt[e].forEach(function(e){lt(e.name,e.config)}),ut(e),it[e]}function ht(e){var t;if(e&&e._locale&&e._locale._abbr&&(e=e._locale._abbr),!e)return tt;if(!o(e)){if(t=ot(e))return t;e=[e]}return function(e){for(var t,n,s,i,r=0;r=t&&a(i,n,!0)>=t-1)break;t--}r++}return tt}(e)}function dt(e){var t,n=e._a;return n&&-2===g(e).overflow&&(t=n[_e]<0||11Pe(n[me],n[_e])?ye:n[ge]<0||24Ae(n,r,a)?g(e)._overflowWeeks=!0:null!=u?g(e)._overflowWeekday=!0:(o=Ee(n,s,i,r,a),e._a[me]=o.year,e._dayOfYear=o.dayOfYear)}(e),null!=e._dayOfYear&&(r=ct(e._a[me],s[me]),(e._dayOfYear>Se(r)||0===e._dayOfYear)&&(g(e)._overflowDayOfYear=!0),n=Ge(r,0,e._dayOfYear),e._a[_e]=n.getUTCMonth(),e._a[ye]=n.getUTCDate()),t=0;t<3&&null==e._a[t];++t)e._a[t]=a[t]=s[t];for(;t<7;t++)e._a[t]=a[t]=null==e._a[t]?2===t?1:0:e._a[t];24===e._a[ge]&&0===e._a[ve]&&0===e._a[pe]&&0===e._a[we]&&(e._nextDay=!0,e._a[ge]=0),e._d=(e._useUTC?Ge:function(e,t,n,s,i,r,a){var o;return e<100&&0<=e?(o=new Date(e+400,t,n,s,i,r,a),isFinite(o.getFullYear())&&o.setFullYear(e)):o=new Date(e,t,n,s,i,r,a),o}).apply(null,a),i=e._useUTC?e._d.getUTCDay():e._d.getDay(),null!=e._tzm&&e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),e._nextDay&&(e._a[ge]=24),e._w&&void 0!==e._w.d&&e._w.d!==i&&(g(e).weekdayMismatch=!0)}}var mt=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,_t=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,yt=/Z|[+-]\d\d(?::?\d\d)?/,gt=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],vt=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],pt=/^\/?Date\((\-?\d+)/i;function wt(e){var t,n,s,i,r,a,o=e._i,u=mt.exec(o)||_t.exec(o);if(u){for(g(e).iso=!0,t=0,n=gt.length;tn.valueOf():n.valueOf()this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},mn.isLocal=function(){return!!this.isValid()&&!this._isUTC},mn.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},mn.isUtc=Et,mn.isUTC=Et,mn.zoneAbbr=function(){return this._isUTC?"UTC":""},mn.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},mn.dates=n("dates accessor is deprecated. Use date instead.",un),mn.months=n("months accessor is deprecated. Use month instead",Ue),mn.years=n("years accessor is deprecated. Use year instead",Oe),mn.zone=n("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",function(e,t){return null!=e?("string"!=typeof e&&(e=-e),this.utcOffset(e,t),this):-this.utcOffset()}),mn.isDSTShifted=n("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",function(){if(!l(this._isDSTShifted))return this._isDSTShifted;var e={};if(w(e,this),(e=Ot(e))._a){var t=e._isUTC?y(e._a):bt(e._a);this._isDSTShifted=this.isValid()&&0 0) { + name.splice(i - 1, 2); + i -= 2; + } + } + } + //end trimDots + + name = name.join('/'); + } + + //Apply map config if available. + if ((baseParts || starMap) && map) { + nameParts = name.split('/'); + + for (i = nameParts.length; i > 0; i -= 1) { + nameSegment = nameParts.slice(0, i).join("/"); + + if (baseParts) { + //Find the longest baseName segment match in the config. + //So, do joins on the biggest to smallest lengths of baseParts. + for (j = baseParts.length; j > 0; j -= 1) { + mapValue = map[baseParts.slice(0, j).join('/')]; + + //baseName segment has config, find if it has one for + //this name. + if (mapValue) { + mapValue = mapValue[nameSegment]; + if (mapValue) { + //Match, update name to the new value. + foundMap = mapValue; + foundI = i; + break; + } + } + } + } + + if (foundMap) { + break; + } + + //Check for a star map match, but just hold on to it, + //if there is a shorter segment match later in a matching + //config, then favor over this star map. + if (!foundStarMap && starMap && starMap[nameSegment]) { + foundStarMap = starMap[nameSegment]; + starI = i; + } + } + + if (!foundMap && foundStarMap) { + foundMap = foundStarMap; + foundI = starI; + } + + if (foundMap) { + nameParts.splice(0, foundI, foundMap); + name = nameParts.join('/'); + } + } + + return name; + } + + function makeRequire(relName, forceSync) { + return function () { + //A version of a require function that passes a moduleName + //value for items that may need to + //look up paths relative to the moduleName + var args = aps.call(arguments, 0); + + //If first arg is not require('string'), and there is only + //one arg, it is the array form without a callback. Insert + //a null so that the following concat is correct. + if (typeof args[0] !== 'string' && args.length === 1) { + args.push(null); + } + return req.apply(undef, args.concat([relName, forceSync])); + }; + } + + function makeNormalize(relName) { + return function (name) { + return normalize(name, relName); + }; + } + + function makeLoad(depName) { + return function (value) { + defined[depName] = value; + }; + } + + function callDep(name) { + if (hasProp(waiting, name)) { + var args = waiting[name]; + delete waiting[name]; + defining[name] = true; + main.apply(undef, args); + } + + if (!hasProp(defined, name) && !hasProp(defining, name)) { + throw new Error('No ' + name); + } + return defined[name]; + } + + //Turns a plugin!resource to [plugin, resource] + //with the plugin being undefined if the name + //did not have a plugin prefix. + function splitPrefix(name) { + var prefix, + index = name ? name.indexOf('!') : -1; + if (index > -1) { + prefix = name.substring(0, index); + name = name.substring(index + 1, name.length); + } + return [prefix, name]; + } + + //Creates a parts array for a relName where first part is plugin ID, + //second part is resource ID. Assumes relName has already been normalized. + function makeRelParts(relName) { + return relName ? splitPrefix(relName) : []; + } + + /** + * Makes a name map, normalizing the name, and using a plugin + * for normalization if necessary. Grabs a ref to plugin + * too, as an optimization. + */ + makeMap = function (name, relParts) { + var plugin, + parts = splitPrefix(name), + prefix = parts[0], + relResourceName = relParts[1]; + + name = parts[1]; + + if (prefix) { + prefix = normalize(prefix, relResourceName); + plugin = callDep(prefix); + } + + //Normalize according + if (prefix) { + if (plugin && plugin.normalize) { + name = plugin.normalize(name, makeNormalize(relResourceName)); + } else { + name = normalize(name, relResourceName); + } + } else { + name = normalize(name, relResourceName); + parts = splitPrefix(name); + prefix = parts[0]; + name = parts[1]; + if (prefix) { + plugin = callDep(prefix); + } + } + + //Using ridiculous property names for space reasons + return { + f: prefix ? prefix + '!' + name : name, //fullName + n: name, + pr: prefix, + p: plugin + }; + }; + + function makeConfig(name) { + return function () { + return (config && config.config && config.config[name]) || {}; + }; + } + + handlers = { + require: function (name) { + return makeRequire(name); + }, + exports: function (name) { + var e = defined[name]; + if (typeof e !== 'undefined') { + return e; + } else { + return (defined[name] = {}); + } + }, + module: function (name) { + return { + id: name, + uri: '', + exports: defined[name], + config: makeConfig(name) + }; + } + }; + + main = function (name, deps, callback, relName) { + var cjsModule, depName, ret, map, i, relParts, + args = [], + callbackType = typeof callback, + usingExports; + + //Use name if no relName + relName = relName || name; + relParts = makeRelParts(relName); + + //Call the callback to define the module, if necessary. + if (callbackType === 'undefined' || callbackType === 'function') { + //Pull out the defined dependencies and pass the ordered + //values to the callback. + //Default to [require, exports, module] if no deps + deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps; + for (i = 0; i < deps.length; i += 1) { + map = makeMap(deps[i], relParts); + depName = map.f; + + //Fast path CommonJS standard dependencies. + if (depName === "require") { + args[i] = handlers.require(name); + } else if (depName === "exports") { + //CommonJS module spec 1.1 + args[i] = handlers.exports(name); + usingExports = true; + } else if (depName === "module") { + //CommonJS module spec 1.1 + cjsModule = args[i] = handlers.module(name); + } else if (hasProp(defined, depName) || + hasProp(waiting, depName) || + hasProp(defining, depName)) { + args[i] = callDep(depName); + } else if (map.p) { + map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {}); + args[i] = defined[depName]; + } else { + throw new Error(name + ' missing ' + depName); + } + } + + ret = callback ? callback.apply(defined[name], args) : undefined; + + if (name) { + //If setting exports via "module" is in play, + //favor that over return value and exports. After that, + //favor a non-undefined return value over exports use. + if (cjsModule && cjsModule.exports !== undef && + cjsModule.exports !== defined[name]) { + defined[name] = cjsModule.exports; + } else if (ret !== undef || !usingExports) { + //Use the return value from the function. + defined[name] = ret; + } + } + } else if (name) { + //May just be an object definition for the module. Only + //worry about defining if have a module name. + defined[name] = callback; + } + }; + + requirejs = require = req = function (deps, callback, relName, forceSync, alt) { + if (typeof deps === "string") { + if (handlers[deps]) { + //callback in this case is really relName + return handlers[deps](callback); + } + //Just return the module wanted. In this scenario, the + //deps arg is the module name, and second arg (if passed) + //is just the relName. + //Normalize module name, if it contains . or .. + return callDep(makeMap(deps, makeRelParts(callback)).f); + } else if (!deps.splice) { + //deps is a config object, not an array. + config = deps; + if (config.deps) { + req(config.deps, config.callback); + } + if (!callback) { + return; + } + + if (callback.splice) { + //callback is an array, which means it is a dependency list. + //Adjust args if there are dependencies + deps = callback; + callback = relName; + relName = null; + } else { + deps = undef; + } + } + + //Support require(['a']) + callback = callback || function () {}; + + //If relName is a function, it is an errback handler, + //so remove it. + if (typeof relName === 'function') { + relName = forceSync; + forceSync = alt; + } + + //Simulate async callback; + if (forceSync) { + main(undef, deps, callback, relName); + } else { + //Using a non-zero value because of concern for what old browsers + //do, and latest browsers "upgrade" to 4 if lower value is used: + //http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout: + //If want a value immediately, use require('id') instead -- something + //that works in almond on the global level, but not guaranteed and + //unlikely to work in other AMD implementations. + setTimeout(function () { + main(undef, deps, callback, relName); + }, 4); + } + + return req; + }; + + /** + * Just drops the config on the floor, but returns req in case + * the config return value is used. + */ + req.config = function (cfg) { + return req(cfg); + }; + + /** + * Expose module registry for debugging and tooling + */ + requirejs._defined = defined; + + define = function (name, deps, callback) { + if (typeof name !== 'string') { + throw new Error('See almond README: incorrect module build, no module name'); + } + + //This module may not have dependencies + if (!deps.splice) { + //deps is not an array, so probably means + //an object literal or factory function for + //the value. Adjust args. + callback = deps; + deps = []; + } + + if (!hasProp(defined, name) && !hasProp(waiting, name)) { + waiting[name] = [name, deps, callback]; + } + }; + + define.amd = { + jQuery: true + }; +}()); + +S2.requirejs = requirejs;S2.require = require;S2.define = define; +} +}()); +S2.define("almond", function(){}); + +/* global jQuery:false, $:false */ +S2.define('jquery',[],function () { + var _$ = jQuery || $; + + if (_$ == null && console && console.error) { + console.error( + 'Select2: An instance of jQuery or a jQuery-compatible library was not ' + + 'found. Make sure that you are including jQuery before Select2 on your ' + + 'web page.' + ); + } + + return _$; +}); + +S2.define('select2/utils',[ + 'jquery' +], function ($) { + var Utils = {}; + + Utils.Extend = function (ChildClass, SuperClass) { + var __hasProp = {}.hasOwnProperty; + + function BaseConstructor () { + this.constructor = ChildClass; + } + + for (var key in SuperClass) { + if (__hasProp.call(SuperClass, key)) { + ChildClass[key] = SuperClass[key]; + } + } + + BaseConstructor.prototype = SuperClass.prototype; + ChildClass.prototype = new BaseConstructor(); + ChildClass.__super__ = SuperClass.prototype; + + return ChildClass; + }; + + function getMethods (theClass) { + var proto = theClass.prototype; + + var methods = []; + + for (var methodName in proto) { + var m = proto[methodName]; + + if (typeof m !== 'function') { + continue; + } + + if (methodName === 'constructor') { + continue; + } + + methods.push(methodName); + } + + return methods; + } + + Utils.Decorate = function (SuperClass, DecoratorClass) { + var decoratedMethods = getMethods(DecoratorClass); + var superMethods = getMethods(SuperClass); + + function DecoratedClass () { + var unshift = Array.prototype.unshift; + + var argCount = DecoratorClass.prototype.constructor.length; + + var calledConstructor = SuperClass.prototype.constructor; + + if (argCount > 0) { + unshift.call(arguments, SuperClass.prototype.constructor); + + calledConstructor = DecoratorClass.prototype.constructor; + } + + calledConstructor.apply(this, arguments); + } + + DecoratorClass.displayName = SuperClass.displayName; + + function ctr () { + this.constructor = DecoratedClass; + } + + DecoratedClass.prototype = new ctr(); + + for (var m = 0; m < superMethods.length; m++) { + var superMethod = superMethods[m]; + + DecoratedClass.prototype[superMethod] = + SuperClass.prototype[superMethod]; + } + + var calledMethod = function (methodName) { + // Stub out the original method if it's not decorating an actual method + var originalMethod = function () {}; + + if (methodName in DecoratedClass.prototype) { + originalMethod = DecoratedClass.prototype[methodName]; + } + + var decoratedMethod = DecoratorClass.prototype[methodName]; + + return function () { + var unshift = Array.prototype.unshift; + + unshift.call(arguments, originalMethod); + + return decoratedMethod.apply(this, arguments); + }; + }; + + for (var d = 0; d < decoratedMethods.length; d++) { + var decoratedMethod = decoratedMethods[d]; + + DecoratedClass.prototype[decoratedMethod] = calledMethod(decoratedMethod); + } + + return DecoratedClass; + }; + + var Observable = function () { + this.listeners = {}; + }; + + Observable.prototype.on = function (event, callback) { + this.listeners = this.listeners || {}; + + if (event in this.listeners) { + this.listeners[event].push(callback); + } else { + this.listeners[event] = [callback]; + } + }; + + Observable.prototype.trigger = function (event) { + var slice = Array.prototype.slice; + var params = slice.call(arguments, 1); + + this.listeners = this.listeners || {}; + + // Params should always come in as an array + if (params == null) { + params = []; + } + + // If there are no arguments to the event, use a temporary object + if (params.length === 0) { + params.push({}); + } + + // Set the `_type` of the first object to the event + params[0]._type = event; + + if (event in this.listeners) { + this.invoke(this.listeners[event], slice.call(arguments, 1)); + } + + if ('*' in this.listeners) { + this.invoke(this.listeners['*'], arguments); + } + }; + + Observable.prototype.invoke = function (listeners, params) { + for (var i = 0, len = listeners.length; i < len; i++) { + listeners[i].apply(this, params); + } + }; + + Utils.Observable = Observable; + + Utils.generateChars = function (length) { + var chars = ''; + + for (var i = 0; i < length; i++) { + var randomChar = Math.floor(Math.random() * 36); + chars += randomChar.toString(36); + } + + return chars; + }; + + Utils.bind = function (func, context) { + return function () { + func.apply(context, arguments); + }; + }; + + Utils._convertData = function (data) { + for (var originalKey in data) { + var keys = originalKey.split('-'); + + var dataLevel = data; + + if (keys.length === 1) { + continue; + } + + for (var k = 0; k < keys.length; k++) { + var key = keys[k]; + + // Lowercase the first letter + // By default, dash-separated becomes camelCase + key = key.substring(0, 1).toLowerCase() + key.substring(1); + + if (!(key in dataLevel)) { + dataLevel[key] = {}; + } + + if (k == keys.length - 1) { + dataLevel[key] = data[originalKey]; + } + + dataLevel = dataLevel[key]; + } + + delete data[originalKey]; + } + + return data; + }; + + Utils.hasScroll = function (index, el) { + // Adapted from the function created by @ShadowScripter + // and adapted by @BillBarry on the Stack Exchange Code Review website. + // The original code can be found at + // http://codereview.stackexchange.com/q/13338 + // and was designed to be used with the Sizzle selector engine. + + var $el = $(el); + var overflowX = el.style.overflowX; + var overflowY = el.style.overflowY; + + //Check both x and y declarations + if (overflowX === overflowY && + (overflowY === 'hidden' || overflowY === 'visible')) { + return false; + } + + if (overflowX === 'scroll' || overflowY === 'scroll') { + return true; + } + + return ($el.innerHeight() < el.scrollHeight || + $el.innerWidth() < el.scrollWidth); + }; + + Utils.escapeMarkup = function (markup) { + var replaceMap = { + '\\': '\', + '&': '&', + '<': '<', + '>': '>', + '"': '"', + '\'': ''', + '/': '/' + }; + + // Do not try to escape the markup if it's not a string + if (typeof markup !== 'string') { + return markup; + } + + return String(markup).replace(/[&<>"'\/\\]/g, function (match) { + return replaceMap[match]; + }); + }; + + // Append an array of jQuery nodes to a given element. + Utils.appendMany = function ($element, $nodes) { + // jQuery 1.7.x does not support $.fn.append() with an array + // Fall back to a jQuery object collection using $.fn.add() + if ($.fn.jquery.substr(0, 3) === '1.7') { + var $jqNodes = $(); + + $.map($nodes, function (node) { + $jqNodes = $jqNodes.add(node); + }); + + $nodes = $jqNodes; + } + + $element.append($nodes); + }; + + // Cache objects in Utils.__cache instead of $.data (see #4346) + Utils.__cache = {}; + + var id = 0; + Utils.GetUniqueElementId = function (element) { + // Get a unique element Id. If element has no id, + // creates a new unique number, stores it in the id + // attribute and returns the new id. + // If an id already exists, it simply returns it. + + var select2Id = element.getAttribute('data-select2-id'); + if (select2Id == null) { + // If element has id, use it. + if (element.id) { + select2Id = element.id; + element.setAttribute('data-select2-id', select2Id); + } else { + element.setAttribute('data-select2-id', ++id); + select2Id = id.toString(); + } + } + return select2Id; + }; + + Utils.StoreData = function (element, name, value) { + // Stores an item in the cache for a specified element. + // name is the cache key. + var id = Utils.GetUniqueElementId(element); + if (!Utils.__cache[id]) { + Utils.__cache[id] = {}; + } + + Utils.__cache[id][name] = value; + }; + + Utils.GetData = function (element, name) { + // Retrieves a value from the cache by its key (name) + // name is optional. If no name specified, return + // all cache items for the specified element. + // and for a specified element. + var id = Utils.GetUniqueElementId(element); + if (name) { + if (Utils.__cache[id]) { + if (Utils.__cache[id][name] != null) { + return Utils.__cache[id][name]; + } + return $(element).data(name); // Fallback to HTML5 data attribs. + } + return $(element).data(name); // Fallback to HTML5 data attribs. + } else { + return Utils.__cache[id]; + } + }; + + Utils.RemoveData = function (element) { + // Removes all cached items for a specified element. + var id = Utils.GetUniqueElementId(element); + if (Utils.__cache[id] != null) { + delete Utils.__cache[id]; + } + + element.removeAttribute('data-select2-id'); + }; + + return Utils; +}); + +S2.define('select2/results',[ + 'jquery', + './utils' +], function ($, Utils) { + function Results ($element, options, dataAdapter) { + this.$element = $element; + this.data = dataAdapter; + this.options = options; + + Results.__super__.constructor.call(this); + } + + Utils.Extend(Results, Utils.Observable); + + Results.prototype.render = function () { + var $results = $( + '
        ' + ); + + if (this.options.get('multiple')) { + $results.attr('aria-multiselectable', 'true'); + } + + this.$results = $results; + + return $results; + }; + + Results.prototype.clear = function () { + this.$results.empty(); + }; + + Results.prototype.displayMessage = function (params) { + var escapeMarkup = this.options.get('escapeMarkup'); + + this.clear(); + this.hideLoading(); + + var $message = $( + '' + ); + + var message = this.options.get('translations').get(params.message); + + $message.append( + escapeMarkup( + message(params.args) + ) + ); + + $message[0].className += ' select2-results__message'; + + this.$results.append($message); + }; + + Results.prototype.hideMessages = function () { + this.$results.find('.select2-results__message').remove(); + }; + + Results.prototype.append = function (data) { + this.hideLoading(); + + var $options = []; + + if (data.results == null || data.results.length === 0) { + if (this.$results.children().length === 0) { + this.trigger('results:message', { + message: 'noResults' + }); + } + + return; + } + + data.results = this.sort(data.results); + + for (var d = 0; d < data.results.length; d++) { + var item = data.results[d]; + + var $option = this.option(item); + + $options.push($option); + } + + this.$results.append($options); + }; + + Results.prototype.position = function ($results, $dropdown) { + var $resultsContainer = $dropdown.find('.select2-results'); + $resultsContainer.append($results); + }; + + Results.prototype.sort = function (data) { + var sorter = this.options.get('sorter'); + + return sorter(data); + }; + + Results.prototype.highlightFirstItem = function () { + var $options = this.$results + .find('.select2-results__option[aria-selected]'); + + var $selected = $options.filter('[aria-selected=true]'); + + // Check if there are any selected options + if ($selected.length > 0) { + // If there are selected options, highlight the first + $selected.first().trigger('mouseenter'); + } else { + // If there are no selected options, highlight the first option + // in the dropdown + $options.first().trigger('mouseenter'); + } + + this.ensureHighlightVisible(); + }; + + Results.prototype.setClasses = function () { + var self = this; + + this.data.current(function (selected) { + var selectedIds = $.map(selected, function (s) { + return s.id.toString(); + }); + + var $options = self.$results + .find('.select2-results__option[aria-selected]'); + + $options.each(function () { + var $option = $(this); + + var item = Utils.GetData(this, 'data'); + + // id needs to be converted to a string when comparing + var id = '' + item.id; + + if ((item.element != null && item.element.selected) || + (item.element == null && $.inArray(id, selectedIds) > -1)) { + $option.attr('aria-selected', 'true'); + } else { + $option.attr('aria-selected', 'false'); + } + }); + + }); + }; + + Results.prototype.showLoading = function (params) { + this.hideLoading(); + + var loadingMore = this.options.get('translations').get('searching'); + + var loading = { + disabled: true, + loading: true, + text: loadingMore(params) + }; + var $loading = this.option(loading); + $loading.className += ' loading-results'; + + this.$results.prepend($loading); + }; + + Results.prototype.hideLoading = function () { + this.$results.find('.loading-results').remove(); + }; + + Results.prototype.option = function (data) { + var option = document.createElement('li'); + option.className = 'select2-results__option'; + + var attrs = { + 'role': 'option', + 'aria-selected': 'false' + }; + + var matches = window.Element.prototype.matches || + window.Element.prototype.msMatchesSelector || + window.Element.prototype.webkitMatchesSelector; + + if ((data.element != null && matches.call(data.element, ':disabled')) || + (data.element == null && data.disabled)) { + delete attrs['aria-selected']; + attrs['aria-disabled'] = 'true'; + } + + if (data.id == null) { + delete attrs['aria-selected']; + } + + if (data._resultId != null) { + option.id = data._resultId; + } + + if (data.title) { + option.title = data.title; + } + + if (data.children) { + attrs.role = 'group'; + attrs['aria-label'] = data.text; + delete attrs['aria-selected']; + } + + for (var attr in attrs) { + var val = attrs[attr]; + + option.setAttribute(attr, val); + } + + if (data.children) { + var $option = $(option); + + var label = document.createElement('strong'); + label.className = 'select2-results__group'; + + var $label = $(label); + this.template(data, label); + + var $children = []; + + for (var c = 0; c < data.children.length; c++) { + var child = data.children[c]; + + var $child = this.option(child); + + $children.push($child); + } + + var $childrenContainer = $('
          ', { + 'class': 'select2-results__options select2-results__options--nested' + }); + + $childrenContainer.append($children); + + $option.append(label); + $option.append($childrenContainer); + } else { + this.template(data, option); + } + + Utils.StoreData(option, 'data', data); + + return option; + }; + + Results.prototype.bind = function (container, $container) { + var self = this; + + var id = container.id + '-results'; + + this.$results.attr('id', id); + + container.on('results:all', function (params) { + self.clear(); + self.append(params.data); + + if (container.isOpen()) { + self.setClasses(); + self.highlightFirstItem(); + } + }); + + container.on('results:append', function (params) { + self.append(params.data); + + if (container.isOpen()) { + self.setClasses(); + } + }); + + container.on('query', function (params) { + self.hideMessages(); + self.showLoading(params); + }); + + container.on('select', function () { + if (!container.isOpen()) { + return; + } + + self.setClasses(); + + if (self.options.get('scrollAfterSelect')) { + self.highlightFirstItem(); + } + }); + + container.on('unselect', function () { + if (!container.isOpen()) { + return; + } + + self.setClasses(); + + if (self.options.get('scrollAfterSelect')) { + self.highlightFirstItem(); + } + }); + + container.on('open', function () { + // When the dropdown is open, aria-expended="true" + self.$results.attr('aria-expanded', 'true'); + self.$results.attr('aria-hidden', 'false'); + + self.setClasses(); + self.ensureHighlightVisible(); + }); + + container.on('close', function () { + // When the dropdown is closed, aria-expended="false" + self.$results.attr('aria-expanded', 'false'); + self.$results.attr('aria-hidden', 'true'); + self.$results.removeAttr('aria-activedescendant'); + }); + + container.on('results:toggle', function () { + var $highlighted = self.getHighlightedResults(); + + if ($highlighted.length === 0) { + return; + } + + $highlighted.trigger('mouseup'); + }); + + container.on('results:select', function () { + var $highlighted = self.getHighlightedResults(); + + if ($highlighted.length === 0) { + return; + } + + var data = Utils.GetData($highlighted[0], 'data'); + + if ($highlighted.attr('aria-selected') == 'true') { + self.trigger('close', {}); + } else { + self.trigger('select', { + data: data + }); + } + }); + + container.on('results:previous', function () { + var $highlighted = self.getHighlightedResults(); + + var $options = self.$results.find('[aria-selected]'); + + var currentIndex = $options.index($highlighted); + + // If we are already at the top, don't move further + // If no options, currentIndex will be -1 + if (currentIndex <= 0) { + return; + } + + var nextIndex = currentIndex - 1; + + // If none are highlighted, highlight the first + if ($highlighted.length === 0) { + nextIndex = 0; + } + + var $next = $options.eq(nextIndex); + + $next.trigger('mouseenter'); + + var currentOffset = self.$results.offset().top; + var nextTop = $next.offset().top; + var nextOffset = self.$results.scrollTop() + (nextTop - currentOffset); + + if (nextIndex === 0) { + self.$results.scrollTop(0); + } else if (nextTop - currentOffset < 0) { + self.$results.scrollTop(nextOffset); + } + }); + + container.on('results:next', function () { + var $highlighted = self.getHighlightedResults(); + + var $options = self.$results.find('[aria-selected]'); + + var currentIndex = $options.index($highlighted); + + var nextIndex = currentIndex + 1; + + // If we are at the last option, stay there + if (nextIndex >= $options.length) { + return; + } + + var $next = $options.eq(nextIndex); + + $next.trigger('mouseenter'); + + var currentOffset = self.$results.offset().top + + self.$results.outerHeight(false); + var nextBottom = $next.offset().top + $next.outerHeight(false); + var nextOffset = self.$results.scrollTop() + nextBottom - currentOffset; + + if (nextIndex === 0) { + self.$results.scrollTop(0); + } else if (nextBottom > currentOffset) { + self.$results.scrollTop(nextOffset); + } + }); + + container.on('results:focus', function (params) { + params.element.addClass('select2-results__option--highlighted'); + }); + + container.on('results:message', function (params) { + self.displayMessage(params); + }); + + if ($.fn.mousewheel) { + this.$results.on('mousewheel', function (e) { + var top = self.$results.scrollTop(); + + var bottom = self.$results.get(0).scrollHeight - top + e.deltaY; + + var isAtTop = e.deltaY > 0 && top - e.deltaY <= 0; + var isAtBottom = e.deltaY < 0 && bottom <= self.$results.height(); + + if (isAtTop) { + self.$results.scrollTop(0); + + e.preventDefault(); + e.stopPropagation(); + } else if (isAtBottom) { + self.$results.scrollTop( + self.$results.get(0).scrollHeight - self.$results.height() + ); + + e.preventDefault(); + e.stopPropagation(); + } + }); + } + + this.$results.on('mouseup', '.select2-results__option[aria-selected]', + function (evt) { + var $this = $(this); + + var data = Utils.GetData(this, 'data'); + + if ($this.attr('aria-selected') === 'true') { + if (self.options.get('multiple')) { + self.trigger('unselect', { + originalEvent: evt, + data: data + }); + } else { + self.trigger('close', {}); + } + + return; + } + + self.trigger('select', { + originalEvent: evt, + data: data + }); + }); + + this.$results.on('mouseenter', '.select2-results__option[aria-selected]', + function (evt) { + var data = Utils.GetData(this, 'data'); + + self.getHighlightedResults() + .removeClass('select2-results__option--highlighted'); + + self.trigger('results:focus', { + data: data, + element: $(this) + }); + }); + }; + + Results.prototype.getHighlightedResults = function () { + var $highlighted = this.$results + .find('.select2-results__option--highlighted'); + + return $highlighted; + }; + + Results.prototype.destroy = function () { + this.$results.remove(); + }; + + Results.prototype.ensureHighlightVisible = function () { + var $highlighted = this.getHighlightedResults(); + + if ($highlighted.length === 0) { + return; + } + + var $options = this.$results.find('[aria-selected]'); + + var currentIndex = $options.index($highlighted); + + var currentOffset = this.$results.offset().top; + var nextTop = $highlighted.offset().top; + var nextOffset = this.$results.scrollTop() + (nextTop - currentOffset); + + var offsetDelta = nextTop - currentOffset; + nextOffset -= $highlighted.outerHeight(false) * 2; + + if (currentIndex <= 2) { + this.$results.scrollTop(0); + } else if (offsetDelta > this.$results.outerHeight() || offsetDelta < 0) { + this.$results.scrollTop(nextOffset); + } + }; + + Results.prototype.template = function (result, container) { + var template = this.options.get('templateResult'); + var escapeMarkup = this.options.get('escapeMarkup'); + + var content = template(result, container); + + if (content == null) { + container.style.display = 'none'; + } else if (typeof content === 'string') { + container.innerHTML = escapeMarkup(content); + } else { + $(container).append(content); + } + }; + + return Results; +}); + +S2.define('select2/keys',[ + +], function () { + var KEYS = { + BACKSPACE: 8, + TAB: 9, + ENTER: 13, + SHIFT: 16, + CTRL: 17, + ALT: 18, + ESC: 27, + SPACE: 32, + PAGE_UP: 33, + PAGE_DOWN: 34, + END: 35, + HOME: 36, + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40, + DELETE: 46 + }; + + return KEYS; +}); + +S2.define('select2/selection/base',[ + 'jquery', + '../utils', + '../keys' +], function ($, Utils, KEYS) { + function BaseSelection ($element, options) { + this.$element = $element; + this.options = options; + + BaseSelection.__super__.constructor.call(this); + } + + Utils.Extend(BaseSelection, Utils.Observable); + + BaseSelection.prototype.render = function () { + var $selection = $( + '' + ); + + this._tabindex = 0; + + if (Utils.GetData(this.$element[0], 'old-tabindex') != null) { + this._tabindex = Utils.GetData(this.$element[0], 'old-tabindex'); + } else if (this.$element.attr('tabindex') != null) { + this._tabindex = this.$element.attr('tabindex'); + } + + $selection.attr('title', this.$element.attr('title')); + $selection.attr('tabindex', this._tabindex); + $selection.attr('aria-disabled', 'false'); + + this.$selection = $selection; + + return $selection; + }; + + BaseSelection.prototype.bind = function (container, $container) { + var self = this; + + var resultsId = container.id + '-results'; + + this.container = container; + + this.$selection.on('focus', function (evt) { + self.trigger('focus', evt); + }); + + this.$selection.on('blur', function (evt) { + self._handleBlur(evt); + }); + + this.$selection.on('keydown', function (evt) { + self.trigger('keypress', evt); + + if (evt.which === KEYS.SPACE) { + evt.preventDefault(); + } + }); + + container.on('results:focus', function (params) { + self.$selection.attr('aria-activedescendant', params.data._resultId); + }); + + container.on('selection:update', function (params) { + self.update(params.data); + }); + + container.on('open', function () { + // When the dropdown is open, aria-expanded="true" + self.$selection.attr('aria-expanded', 'true'); + self.$selection.attr('aria-owns', resultsId); + + self._attachCloseHandler(container); + }); + + container.on('close', function () { + // When the dropdown is closed, aria-expanded="false" + self.$selection.attr('aria-expanded', 'false'); + self.$selection.removeAttr('aria-activedescendant'); + self.$selection.removeAttr('aria-owns'); + + self.$selection.trigger('focus'); + + self._detachCloseHandler(container); + }); + + container.on('enable', function () { + self.$selection.attr('tabindex', self._tabindex); + self.$selection.attr('aria-disabled', 'false'); + }); + + container.on('disable', function () { + self.$selection.attr('tabindex', '-1'); + self.$selection.attr('aria-disabled', 'true'); + }); + }; + + BaseSelection.prototype._handleBlur = function (evt) { + var self = this; + + // This needs to be delayed as the active element is the body when the tab + // key is pressed, possibly along with others. + window.setTimeout(function () { + // Don't trigger `blur` if the focus is still in the selection + if ( + (document.activeElement == self.$selection[0]) || + ($.contains(self.$selection[0], document.activeElement)) + ) { + return; + } + + self.trigger('blur', evt); + }, 1); + }; + + BaseSelection.prototype._attachCloseHandler = function (container) { + + $(document.body).on('mousedown.select2.' + container.id, function (e) { + var $target = $(e.target); + + var $select = $target.closest('.select2'); + + var $all = $('.select2.select2-container--open'); + + $all.each(function () { + if (this == $select[0]) { + return; + } + + var $element = Utils.GetData(this, 'element'); + + $element.select2('close'); + }); + }); + }; + + BaseSelection.prototype._detachCloseHandler = function (container) { + $(document.body).off('mousedown.select2.' + container.id); + }; + + BaseSelection.prototype.position = function ($selection, $container) { + var $selectionContainer = $container.find('.selection'); + $selectionContainer.append($selection); + }; + + BaseSelection.prototype.destroy = function () { + this._detachCloseHandler(this.container); + }; + + BaseSelection.prototype.update = function (data) { + throw new Error('The `update` method must be defined in child classes.'); + }; + + return BaseSelection; +}); + +S2.define('select2/selection/single',[ + 'jquery', + './base', + '../utils', + '../keys' +], function ($, BaseSelection, Utils, KEYS) { + function SingleSelection () { + SingleSelection.__super__.constructor.apply(this, arguments); + } + + Utils.Extend(SingleSelection, BaseSelection); + + SingleSelection.prototype.render = function () { + var $selection = SingleSelection.__super__.render.call(this); + + $selection.addClass('select2-selection--single'); + + $selection.html( + '' + + '' + + '' + + '' + ); + + return $selection; + }; + + SingleSelection.prototype.bind = function (container, $container) { + var self = this; + + SingleSelection.__super__.bind.apply(this, arguments); + + var id = container.id + '-container'; + + this.$selection.find('.select2-selection__rendered') + .attr('id', id) + .attr('role', 'textbox') + .attr('aria-readonly', 'true'); + this.$selection.attr('aria-labelledby', id); + + this.$selection.on('mousedown', function (evt) { + // Only respond to left clicks + if (evt.which !== 1) { + return; + } + + self.trigger('toggle', { + originalEvent: evt + }); + }); + + this.$selection.on('focus', function (evt) { + // User focuses on the container + }); + + this.$selection.on('blur', function (evt) { + // User exits the container + }); + + container.on('focus', function (evt) { + if (!container.isOpen()) { + self.$selection.trigger('focus'); + } + }); + }; + + SingleSelection.prototype.clear = function () { + var $rendered = this.$selection.find('.select2-selection__rendered'); + $rendered.empty(); + $rendered.removeAttr('title'); // clear tooltip on empty + }; + + SingleSelection.prototype.display = function (data, container) { + var template = this.options.get('templateSelection'); + var escapeMarkup = this.options.get('escapeMarkup'); + + return escapeMarkup(template(data, container)); + }; + + SingleSelection.prototype.selectionContainer = function () { + return $(''); + }; + + SingleSelection.prototype.update = function (data) { + if (data.length === 0) { + this.clear(); + return; + } + + var selection = data[0]; + + var $rendered = this.$selection.find('.select2-selection__rendered'); + var formatted = this.display(selection, $rendered); + + $rendered.empty().append(formatted); + + var title = selection.title || selection.text; + + if (title) { + $rendered.attr('title', title); + } else { + $rendered.removeAttr('title'); + } + }; + + return SingleSelection; +}); + +S2.define('select2/selection/multiple',[ + 'jquery', + './base', + '../utils' +], function ($, BaseSelection, Utils) { + function MultipleSelection ($element, options) { + MultipleSelection.__super__.constructor.apply(this, arguments); + } + + Utils.Extend(MultipleSelection, BaseSelection); + + MultipleSelection.prototype.render = function () { + var $selection = MultipleSelection.__super__.render.call(this); + + $selection.addClass('select2-selection--multiple'); + + $selection.html( + '
            ' + ); + + return $selection; + }; + + MultipleSelection.prototype.bind = function (container, $container) { + var self = this; + + MultipleSelection.__super__.bind.apply(this, arguments); + + this.$selection.on('click', function (evt) { + self.trigger('toggle', { + originalEvent: evt + }); + }); + + this.$selection.on( + 'click', + '.select2-selection__choice__remove', + function (evt) { + // Ignore the event if it is disabled + if (self.options.get('disabled')) { + return; + } + + var $remove = $(this); + var $selection = $remove.parent(); + + var data = Utils.GetData($selection[0], 'data'); + + self.trigger('unselect', { + originalEvent: evt, + data: data + }); + } + ); + }; + + MultipleSelection.prototype.clear = function () { + var $rendered = this.$selection.find('.select2-selection__rendered'); + $rendered.empty(); + $rendered.removeAttr('title'); + }; + + MultipleSelection.prototype.display = function (data, container) { + var template = this.options.get('templateSelection'); + var escapeMarkup = this.options.get('escapeMarkup'); + + return escapeMarkup(template(data, container)); + }; + + MultipleSelection.prototype.selectionContainer = function () { + var $container = $( + '
          • ' + + '' + + '×' + + '' + + '
          • ' + ); + + return $container; + }; + + MultipleSelection.prototype.update = function (data) { + this.clear(); + + if (data.length === 0) { + return; + } + + var $selections = []; + + for (var d = 0; d < data.length; d++) { + var selection = data[d]; + + var $selection = this.selectionContainer(); + var formatted = this.display(selection, $selection); + + $selection.append(formatted); + + var title = selection.title || selection.text; + + if (title) { + $selection.attr('title', title); + } + + Utils.StoreData($selection[0], 'data', selection); + + $selections.push($selection); + } + + var $rendered = this.$selection.find('.select2-selection__rendered'); + + Utils.appendMany($rendered, $selections); + }; + + return MultipleSelection; +}); + +S2.define('select2/selection/placeholder',[ + '../utils' +], function (Utils) { + function Placeholder (decorated, $element, options) { + this.placeholder = this.normalizePlaceholder(options.get('placeholder')); + + decorated.call(this, $element, options); + } + + Placeholder.prototype.normalizePlaceholder = function (_, placeholder) { + if (typeof placeholder === 'string') { + placeholder = { + id: '', + text: placeholder + }; + } + + return placeholder; + }; + + Placeholder.prototype.createPlaceholder = function (decorated, placeholder) { + var $placeholder = this.selectionContainer(); + + $placeholder.html(this.display(placeholder)); + $placeholder.addClass('select2-selection__placeholder') + .removeClass('select2-selection__choice'); + + return $placeholder; + }; + + Placeholder.prototype.update = function (decorated, data) { + var singlePlaceholder = ( + data.length == 1 && data[0].id != this.placeholder.id + ); + var multipleSelections = data.length > 1; + + if (multipleSelections || singlePlaceholder) { + return decorated.call(this, data); + } + + this.clear(); + + var $placeholder = this.createPlaceholder(this.placeholder); + + this.$selection.find('.select2-selection__rendered').append($placeholder); + }; + + return Placeholder; +}); + +S2.define('select2/selection/allowClear',[ + 'jquery', + '../keys', + '../utils' +], function ($, KEYS, Utils) { + function AllowClear () { } + + AllowClear.prototype.bind = function (decorated, container, $container) { + var self = this; + + decorated.call(this, container, $container); + + if (this.placeholder == null) { + if (this.options.get('debug') && window.console && console.error) { + console.error( + 'Select2: The `allowClear` option should be used in combination ' + + 'with the `placeholder` option.' + ); + } + } + + this.$selection.on('mousedown', '.select2-selection__clear', + function (evt) { + self._handleClear(evt); + }); + + container.on('keypress', function (evt) { + self._handleKeyboardClear(evt, container); + }); + }; + + AllowClear.prototype._handleClear = function (_, evt) { + // Ignore the event if it is disabled + if (this.options.get('disabled')) { + return; + } + + var $clear = this.$selection.find('.select2-selection__clear'); + + // Ignore the event if nothing has been selected + if ($clear.length === 0) { + return; + } + + evt.stopPropagation(); + + var data = Utils.GetData($clear[0], 'data'); + + var previousVal = this.$element.val(); + this.$element.val(this.placeholder.id); + + var unselectData = { + data: data + }; + this.trigger('clear', unselectData); + if (unselectData.prevented) { + this.$element.val(previousVal); + return; + } + + for (var d = 0; d < data.length; d++) { + unselectData = { + data: data[d] + }; + + // Trigger the `unselect` event, so people can prevent it from being + // cleared. + this.trigger('unselect', unselectData); + + // If the event was prevented, don't clear it out. + if (unselectData.prevented) { + this.$element.val(previousVal); + return; + } + } + + this.$element.trigger('change'); + + this.trigger('toggle', {}); + }; + + AllowClear.prototype._handleKeyboardClear = function (_, evt, container) { + if (container.isOpen()) { + return; + } + + if (evt.which == KEYS.DELETE || evt.which == KEYS.BACKSPACE) { + this._handleClear(evt); + } + }; + + AllowClear.prototype.update = function (decorated, data) { + decorated.call(this, data); + + if (this.$selection.find('.select2-selection__placeholder').length > 0 || + data.length === 0) { + return; + } + + var removeAll = this.options.get('translations').get('removeAllItems'); + + var $remove = $( + '' + + '×' + + '' + ); + Utils.StoreData($remove[0], 'data', data); + + this.$selection.find('.select2-selection__rendered').prepend($remove); + }; + + return AllowClear; +}); + +S2.define('select2/selection/search',[ + 'jquery', + '../utils', + '../keys' +], function ($, Utils, KEYS) { + function Search (decorated, $element, options) { + decorated.call(this, $element, options); + } + + Search.prototype.render = function (decorated) { + var $search = $( + '' + ); + + this.$searchContainer = $search; + this.$search = $search.find('input'); + + var $rendered = decorated.call(this); + + this._transferTabIndex(); + + return $rendered; + }; + + Search.prototype.bind = function (decorated, container, $container) { + var self = this; + + var resultsId = container.id + '-results'; + + decorated.call(this, container, $container); + + container.on('open', function () { + self.$search.attr('aria-controls', resultsId); + self.$search.trigger('focus'); + }); + + container.on('close', function () { + self.$search.val(''); + self.$search.removeAttr('aria-controls'); + self.$search.removeAttr('aria-activedescendant'); + self.$search.trigger('focus'); + }); + + container.on('enable', function () { + self.$search.prop('disabled', false); + + self._transferTabIndex(); + }); + + container.on('disable', function () { + self.$search.prop('disabled', true); + }); + + container.on('focus', function (evt) { + self.$search.trigger('focus'); + }); + + container.on('results:focus', function (params) { + if (params.data._resultId) { + self.$search.attr('aria-activedescendant', params.data._resultId); + } else { + self.$search.removeAttr('aria-activedescendant'); + } + }); + + this.$selection.on('focusin', '.select2-search--inline', function (evt) { + self.trigger('focus', evt); + }); + + this.$selection.on('focusout', '.select2-search--inline', function (evt) { + self._handleBlur(evt); + }); + + this.$selection.on('keydown', '.select2-search--inline', function (evt) { + evt.stopPropagation(); + + self.trigger('keypress', evt); + + self._keyUpPrevented = evt.isDefaultPrevented(); + + var key = evt.which; + + if (key === KEYS.BACKSPACE && self.$search.val() === '') { + var $previousChoice = self.$searchContainer + .prev('.select2-selection__choice'); + + if ($previousChoice.length > 0) { + var item = Utils.GetData($previousChoice[0], 'data'); + + self.searchRemoveChoice(item); + + evt.preventDefault(); + } + } + }); + + this.$selection.on('click', '.select2-search--inline', function (evt) { + if (self.$search.val()) { + evt.stopPropagation(); + } + }); + + // Try to detect the IE version should the `documentMode` property that + // is stored on the document. This is only implemented in IE and is + // slightly cleaner than doing a user agent check. + // This property is not available in Edge, but Edge also doesn't have + // this bug. + var msie = document.documentMode; + var disableInputEvents = msie && msie <= 11; + + // Workaround for browsers which do not support the `input` event + // This will prevent double-triggering of events for browsers which support + // both the `keyup` and `input` events. + this.$selection.on( + 'input.searchcheck', + '.select2-search--inline', + function (evt) { + // IE will trigger the `input` event when a placeholder is used on a + // search box. To get around this issue, we are forced to ignore all + // `input` events in IE and keep using `keyup`. + if (disableInputEvents) { + self.$selection.off('input.search input.searchcheck'); + return; + } + + // Unbind the duplicated `keyup` event + self.$selection.off('keyup.search'); + } + ); + + this.$selection.on( + 'keyup.search input.search', + '.select2-search--inline', + function (evt) { + // IE will trigger the `input` event when a placeholder is used on a + // search box. To get around this issue, we are forced to ignore all + // `input` events in IE and keep using `keyup`. + if (disableInputEvents && evt.type === 'input') { + self.$selection.off('input.search input.searchcheck'); + return; + } + + var key = evt.which; + + // We can freely ignore events from modifier keys + if (key == KEYS.SHIFT || key == KEYS.CTRL || key == KEYS.ALT) { + return; + } + + // Tabbing will be handled during the `keydown` phase + if (key == KEYS.TAB) { + return; + } + + self.handleSearch(evt); + } + ); + }; + + /** + * This method will transfer the tabindex attribute from the rendered + * selection to the search box. This allows for the search box to be used as + * the primary focus instead of the selection container. + * + * @private + */ + Search.prototype._transferTabIndex = function (decorated) { + this.$search.attr('tabindex', this.$selection.attr('tabindex')); + this.$selection.attr('tabindex', '-1'); + }; + + Search.prototype.createPlaceholder = function (decorated, placeholder) { + this.$search.attr('placeholder', placeholder.text); + }; + + Search.prototype.update = function (decorated, data) { + var searchHadFocus = this.$search[0] == document.activeElement; + + this.$search.attr('placeholder', ''); + + decorated.call(this, data); + + this.$selection.find('.select2-selection__rendered') + .append(this.$searchContainer); + + this.resizeSearch(); + if (searchHadFocus) { + this.$search.trigger('focus'); + } + }; + + Search.prototype.handleSearch = function () { + this.resizeSearch(); + + if (!this._keyUpPrevented) { + var input = this.$search.val(); + + this.trigger('query', { + term: input + }); + } + + this._keyUpPrevented = false; + }; + + Search.prototype.searchRemoveChoice = function (decorated, item) { + this.trigger('unselect', { + data: item + }); + + this.$search.val(item.text); + this.handleSearch(); + }; + + Search.prototype.resizeSearch = function () { + this.$search.css('width', '25px'); + + var width = ''; + + if (this.$search.attr('placeholder') !== '') { + width = this.$selection.find('.select2-selection__rendered').width(); + } else { + var minimumWidth = this.$search.val().length + 1; + + width = (minimumWidth * 0.75) + 'em'; + } + + this.$search.css('width', width); + }; + + return Search; +}); + +S2.define('select2/selection/eventRelay',[ + 'jquery' +], function ($) { + function EventRelay () { } + + EventRelay.prototype.bind = function (decorated, container, $container) { + var self = this; + var relayEvents = [ + 'open', 'opening', + 'close', 'closing', + 'select', 'selecting', + 'unselect', 'unselecting', + 'clear', 'clearing' + ]; + + var preventableEvents = [ + 'opening', 'closing', 'selecting', 'unselecting', 'clearing' + ]; + + decorated.call(this, container, $container); + + container.on('*', function (name, params) { + // Ignore events that should not be relayed + if ($.inArray(name, relayEvents) === -1) { + return; + } + + // The parameters should always be an object + params = params || {}; + + // Generate the jQuery event for the Select2 event + var evt = $.Event('select2:' + name, { + params: params + }); + + self.$element.trigger(evt); + + // Only handle preventable events if it was one + if ($.inArray(name, preventableEvents) === -1) { + return; + } + + params.prevented = evt.isDefaultPrevented(); + }); + }; + + return EventRelay; +}); + +S2.define('select2/translation',[ + 'jquery', + 'require' +], function ($, require) { + function Translation (dict) { + this.dict = dict || {}; + } + + Translation.prototype.all = function () { + return this.dict; + }; + + Translation.prototype.get = function (key) { + return this.dict[key]; + }; + + Translation.prototype.extend = function (translation) { + this.dict = $.extend({}, translation.all(), this.dict); + }; + + // Static functions + + Translation._cache = {}; + + Translation.loadPath = function (path) { + if (!(path in Translation._cache)) { + var translations = require(path); + + Translation._cache[path] = translations; + } + + return new Translation(Translation._cache[path]); + }; + + return Translation; +}); + +S2.define('select2/diacritics',[ + +], function () { + var diacritics = { + '\u24B6': 'A', + '\uFF21': 'A', + '\u00C0': 'A', + '\u00C1': 'A', + '\u00C2': 'A', + '\u1EA6': 'A', + '\u1EA4': 'A', + '\u1EAA': 'A', + '\u1EA8': 'A', + '\u00C3': 'A', + '\u0100': 'A', + '\u0102': 'A', + '\u1EB0': 'A', + '\u1EAE': 'A', + '\u1EB4': 'A', + '\u1EB2': 'A', + '\u0226': 'A', + '\u01E0': 'A', + '\u00C4': 'A', + '\u01DE': 'A', + '\u1EA2': 'A', + '\u00C5': 'A', + '\u01FA': 'A', + '\u01CD': 'A', + '\u0200': 'A', + '\u0202': 'A', + '\u1EA0': 'A', + '\u1EAC': 'A', + '\u1EB6': 'A', + '\u1E00': 'A', + '\u0104': 'A', + '\u023A': 'A', + '\u2C6F': 'A', + '\uA732': 'AA', + '\u00C6': 'AE', + '\u01FC': 'AE', + '\u01E2': 'AE', + '\uA734': 'AO', + '\uA736': 'AU', + '\uA738': 'AV', + '\uA73A': 'AV', + '\uA73C': 'AY', + '\u24B7': 'B', + '\uFF22': 'B', + '\u1E02': 'B', + '\u1E04': 'B', + '\u1E06': 'B', + '\u0243': 'B', + '\u0182': 'B', + '\u0181': 'B', + '\u24B8': 'C', + '\uFF23': 'C', + '\u0106': 'C', + '\u0108': 'C', + '\u010A': 'C', + '\u010C': 'C', + '\u00C7': 'C', + '\u1E08': 'C', + '\u0187': 'C', + '\u023B': 'C', + '\uA73E': 'C', + '\u24B9': 'D', + '\uFF24': 'D', + '\u1E0A': 'D', + '\u010E': 'D', + '\u1E0C': 'D', + '\u1E10': 'D', + '\u1E12': 'D', + '\u1E0E': 'D', + '\u0110': 'D', + '\u018B': 'D', + '\u018A': 'D', + '\u0189': 'D', + '\uA779': 'D', + '\u01F1': 'DZ', + '\u01C4': 'DZ', + '\u01F2': 'Dz', + '\u01C5': 'Dz', + '\u24BA': 'E', + '\uFF25': 'E', + '\u00C8': 'E', + '\u00C9': 'E', + '\u00CA': 'E', + '\u1EC0': 'E', + '\u1EBE': 'E', + '\u1EC4': 'E', + '\u1EC2': 'E', + '\u1EBC': 'E', + '\u0112': 'E', + '\u1E14': 'E', + '\u1E16': 'E', + '\u0114': 'E', + '\u0116': 'E', + '\u00CB': 'E', + '\u1EBA': 'E', + '\u011A': 'E', + '\u0204': 'E', + '\u0206': 'E', + '\u1EB8': 'E', + '\u1EC6': 'E', + '\u0228': 'E', + '\u1E1C': 'E', + '\u0118': 'E', + '\u1E18': 'E', + '\u1E1A': 'E', + '\u0190': 'E', + '\u018E': 'E', + '\u24BB': 'F', + '\uFF26': 'F', + '\u1E1E': 'F', + '\u0191': 'F', + '\uA77B': 'F', + '\u24BC': 'G', + '\uFF27': 'G', + '\u01F4': 'G', + '\u011C': 'G', + '\u1E20': 'G', + '\u011E': 'G', + '\u0120': 'G', + '\u01E6': 'G', + '\u0122': 'G', + '\u01E4': 'G', + '\u0193': 'G', + '\uA7A0': 'G', + '\uA77D': 'G', + '\uA77E': 'G', + '\u24BD': 'H', + '\uFF28': 'H', + '\u0124': 'H', + '\u1E22': 'H', + '\u1E26': 'H', + '\u021E': 'H', + '\u1E24': 'H', + '\u1E28': 'H', + '\u1E2A': 'H', + '\u0126': 'H', + '\u2C67': 'H', + '\u2C75': 'H', + '\uA78D': 'H', + '\u24BE': 'I', + '\uFF29': 'I', + '\u00CC': 'I', + '\u00CD': 'I', + '\u00CE': 'I', + '\u0128': 'I', + '\u012A': 'I', + '\u012C': 'I', + '\u0130': 'I', + '\u00CF': 'I', + '\u1E2E': 'I', + '\u1EC8': 'I', + '\u01CF': 'I', + '\u0208': 'I', + '\u020A': 'I', + '\u1ECA': 'I', + '\u012E': 'I', + '\u1E2C': 'I', + '\u0197': 'I', + '\u24BF': 'J', + '\uFF2A': 'J', + '\u0134': 'J', + '\u0248': 'J', + '\u24C0': 'K', + '\uFF2B': 'K', + '\u1E30': 'K', + '\u01E8': 'K', + '\u1E32': 'K', + '\u0136': 'K', + '\u1E34': 'K', + '\u0198': 'K', + '\u2C69': 'K', + '\uA740': 'K', + '\uA742': 'K', + '\uA744': 'K', + '\uA7A2': 'K', + '\u24C1': 'L', + '\uFF2C': 'L', + '\u013F': 'L', + '\u0139': 'L', + '\u013D': 'L', + '\u1E36': 'L', + '\u1E38': 'L', + '\u013B': 'L', + '\u1E3C': 'L', + '\u1E3A': 'L', + '\u0141': 'L', + '\u023D': 'L', + '\u2C62': 'L', + '\u2C60': 'L', + '\uA748': 'L', + '\uA746': 'L', + '\uA780': 'L', + '\u01C7': 'LJ', + '\u01C8': 'Lj', + '\u24C2': 'M', + '\uFF2D': 'M', + '\u1E3E': 'M', + '\u1E40': 'M', + '\u1E42': 'M', + '\u2C6E': 'M', + '\u019C': 'M', + '\u24C3': 'N', + '\uFF2E': 'N', + '\u01F8': 'N', + '\u0143': 'N', + '\u00D1': 'N', + '\u1E44': 'N', + '\u0147': 'N', + '\u1E46': 'N', + '\u0145': 'N', + '\u1E4A': 'N', + '\u1E48': 'N', + '\u0220': 'N', + '\u019D': 'N', + '\uA790': 'N', + '\uA7A4': 'N', + '\u01CA': 'NJ', + '\u01CB': 'Nj', + '\u24C4': 'O', + '\uFF2F': 'O', + '\u00D2': 'O', + '\u00D3': 'O', + '\u00D4': 'O', + '\u1ED2': 'O', + '\u1ED0': 'O', + '\u1ED6': 'O', + '\u1ED4': 'O', + '\u00D5': 'O', + '\u1E4C': 'O', + '\u022C': 'O', + '\u1E4E': 'O', + '\u014C': 'O', + '\u1E50': 'O', + '\u1E52': 'O', + '\u014E': 'O', + '\u022E': 'O', + '\u0230': 'O', + '\u00D6': 'O', + '\u022A': 'O', + '\u1ECE': 'O', + '\u0150': 'O', + '\u01D1': 'O', + '\u020C': 'O', + '\u020E': 'O', + '\u01A0': 'O', + '\u1EDC': 'O', + '\u1EDA': 'O', + '\u1EE0': 'O', + '\u1EDE': 'O', + '\u1EE2': 'O', + '\u1ECC': 'O', + '\u1ED8': 'O', + '\u01EA': 'O', + '\u01EC': 'O', + '\u00D8': 'O', + '\u01FE': 'O', + '\u0186': 'O', + '\u019F': 'O', + '\uA74A': 'O', + '\uA74C': 'O', + '\u0152': 'OE', + '\u01A2': 'OI', + '\uA74E': 'OO', + '\u0222': 'OU', + '\u24C5': 'P', + '\uFF30': 'P', + '\u1E54': 'P', + '\u1E56': 'P', + '\u01A4': 'P', + '\u2C63': 'P', + '\uA750': 'P', + '\uA752': 'P', + '\uA754': 'P', + '\u24C6': 'Q', + '\uFF31': 'Q', + '\uA756': 'Q', + '\uA758': 'Q', + '\u024A': 'Q', + '\u24C7': 'R', + '\uFF32': 'R', + '\u0154': 'R', + '\u1E58': 'R', + '\u0158': 'R', + '\u0210': 'R', + '\u0212': 'R', + '\u1E5A': 'R', + '\u1E5C': 'R', + '\u0156': 'R', + '\u1E5E': 'R', + '\u024C': 'R', + '\u2C64': 'R', + '\uA75A': 'R', + '\uA7A6': 'R', + '\uA782': 'R', + '\u24C8': 'S', + '\uFF33': 'S', + '\u1E9E': 'S', + '\u015A': 'S', + '\u1E64': 'S', + '\u015C': 'S', + '\u1E60': 'S', + '\u0160': 'S', + '\u1E66': 'S', + '\u1E62': 'S', + '\u1E68': 'S', + '\u0218': 'S', + '\u015E': 'S', + '\u2C7E': 'S', + '\uA7A8': 'S', + '\uA784': 'S', + '\u24C9': 'T', + '\uFF34': 'T', + '\u1E6A': 'T', + '\u0164': 'T', + '\u1E6C': 'T', + '\u021A': 'T', + '\u0162': 'T', + '\u1E70': 'T', + '\u1E6E': 'T', + '\u0166': 'T', + '\u01AC': 'T', + '\u01AE': 'T', + '\u023E': 'T', + '\uA786': 'T', + '\uA728': 'TZ', + '\u24CA': 'U', + '\uFF35': 'U', + '\u00D9': 'U', + '\u00DA': 'U', + '\u00DB': 'U', + '\u0168': 'U', + '\u1E78': 'U', + '\u016A': 'U', + '\u1E7A': 'U', + '\u016C': 'U', + '\u00DC': 'U', + '\u01DB': 'U', + '\u01D7': 'U', + '\u01D5': 'U', + '\u01D9': 'U', + '\u1EE6': 'U', + '\u016E': 'U', + '\u0170': 'U', + '\u01D3': 'U', + '\u0214': 'U', + '\u0216': 'U', + '\u01AF': 'U', + '\u1EEA': 'U', + '\u1EE8': 'U', + '\u1EEE': 'U', + '\u1EEC': 'U', + '\u1EF0': 'U', + '\u1EE4': 'U', + '\u1E72': 'U', + '\u0172': 'U', + '\u1E76': 'U', + '\u1E74': 'U', + '\u0244': 'U', + '\u24CB': 'V', + '\uFF36': 'V', + '\u1E7C': 'V', + '\u1E7E': 'V', + '\u01B2': 'V', + '\uA75E': 'V', + '\u0245': 'V', + '\uA760': 'VY', + '\u24CC': 'W', + '\uFF37': 'W', + '\u1E80': 'W', + '\u1E82': 'W', + '\u0174': 'W', + '\u1E86': 'W', + '\u1E84': 'W', + '\u1E88': 'W', + '\u2C72': 'W', + '\u24CD': 'X', + '\uFF38': 'X', + '\u1E8A': 'X', + '\u1E8C': 'X', + '\u24CE': 'Y', + '\uFF39': 'Y', + '\u1EF2': 'Y', + '\u00DD': 'Y', + '\u0176': 'Y', + '\u1EF8': 'Y', + '\u0232': 'Y', + '\u1E8E': 'Y', + '\u0178': 'Y', + '\u1EF6': 'Y', + '\u1EF4': 'Y', + '\u01B3': 'Y', + '\u024E': 'Y', + '\u1EFE': 'Y', + '\u24CF': 'Z', + '\uFF3A': 'Z', + '\u0179': 'Z', + '\u1E90': 'Z', + '\u017B': 'Z', + '\u017D': 'Z', + '\u1E92': 'Z', + '\u1E94': 'Z', + '\u01B5': 'Z', + '\u0224': 'Z', + '\u2C7F': 'Z', + '\u2C6B': 'Z', + '\uA762': 'Z', + '\u24D0': 'a', + '\uFF41': 'a', + '\u1E9A': 'a', + '\u00E0': 'a', + '\u00E1': 'a', + '\u00E2': 'a', + '\u1EA7': 'a', + '\u1EA5': 'a', + '\u1EAB': 'a', + '\u1EA9': 'a', + '\u00E3': 'a', + '\u0101': 'a', + '\u0103': 'a', + '\u1EB1': 'a', + '\u1EAF': 'a', + '\u1EB5': 'a', + '\u1EB3': 'a', + '\u0227': 'a', + '\u01E1': 'a', + '\u00E4': 'a', + '\u01DF': 'a', + '\u1EA3': 'a', + '\u00E5': 'a', + '\u01FB': 'a', + '\u01CE': 'a', + '\u0201': 'a', + '\u0203': 'a', + '\u1EA1': 'a', + '\u1EAD': 'a', + '\u1EB7': 'a', + '\u1E01': 'a', + '\u0105': 'a', + '\u2C65': 'a', + '\u0250': 'a', + '\uA733': 'aa', + '\u00E6': 'ae', + '\u01FD': 'ae', + '\u01E3': 'ae', + '\uA735': 'ao', + '\uA737': 'au', + '\uA739': 'av', + '\uA73B': 'av', + '\uA73D': 'ay', + '\u24D1': 'b', + '\uFF42': 'b', + '\u1E03': 'b', + '\u1E05': 'b', + '\u1E07': 'b', + '\u0180': 'b', + '\u0183': 'b', + '\u0253': 'b', + '\u24D2': 'c', + '\uFF43': 'c', + '\u0107': 'c', + '\u0109': 'c', + '\u010B': 'c', + '\u010D': 'c', + '\u00E7': 'c', + '\u1E09': 'c', + '\u0188': 'c', + '\u023C': 'c', + '\uA73F': 'c', + '\u2184': 'c', + '\u24D3': 'd', + '\uFF44': 'd', + '\u1E0B': 'd', + '\u010F': 'd', + '\u1E0D': 'd', + '\u1E11': 'd', + '\u1E13': 'd', + '\u1E0F': 'd', + '\u0111': 'd', + '\u018C': 'd', + '\u0256': 'd', + '\u0257': 'd', + '\uA77A': 'd', + '\u01F3': 'dz', + '\u01C6': 'dz', + '\u24D4': 'e', + '\uFF45': 'e', + '\u00E8': 'e', + '\u00E9': 'e', + '\u00EA': 'e', + '\u1EC1': 'e', + '\u1EBF': 'e', + '\u1EC5': 'e', + '\u1EC3': 'e', + '\u1EBD': 'e', + '\u0113': 'e', + '\u1E15': 'e', + '\u1E17': 'e', + '\u0115': 'e', + '\u0117': 'e', + '\u00EB': 'e', + '\u1EBB': 'e', + '\u011B': 'e', + '\u0205': 'e', + '\u0207': 'e', + '\u1EB9': 'e', + '\u1EC7': 'e', + '\u0229': 'e', + '\u1E1D': 'e', + '\u0119': 'e', + '\u1E19': 'e', + '\u1E1B': 'e', + '\u0247': 'e', + '\u025B': 'e', + '\u01DD': 'e', + '\u24D5': 'f', + '\uFF46': 'f', + '\u1E1F': 'f', + '\u0192': 'f', + '\uA77C': 'f', + '\u24D6': 'g', + '\uFF47': 'g', + '\u01F5': 'g', + '\u011D': 'g', + '\u1E21': 'g', + '\u011F': 'g', + '\u0121': 'g', + '\u01E7': 'g', + '\u0123': 'g', + '\u01E5': 'g', + '\u0260': 'g', + '\uA7A1': 'g', + '\u1D79': 'g', + '\uA77F': 'g', + '\u24D7': 'h', + '\uFF48': 'h', + '\u0125': 'h', + '\u1E23': 'h', + '\u1E27': 'h', + '\u021F': 'h', + '\u1E25': 'h', + '\u1E29': 'h', + '\u1E2B': 'h', + '\u1E96': 'h', + '\u0127': 'h', + '\u2C68': 'h', + '\u2C76': 'h', + '\u0265': 'h', + '\u0195': 'hv', + '\u24D8': 'i', + '\uFF49': 'i', + '\u00EC': 'i', + '\u00ED': 'i', + '\u00EE': 'i', + '\u0129': 'i', + '\u012B': 'i', + '\u012D': 'i', + '\u00EF': 'i', + '\u1E2F': 'i', + '\u1EC9': 'i', + '\u01D0': 'i', + '\u0209': 'i', + '\u020B': 'i', + '\u1ECB': 'i', + '\u012F': 'i', + '\u1E2D': 'i', + '\u0268': 'i', + '\u0131': 'i', + '\u24D9': 'j', + '\uFF4A': 'j', + '\u0135': 'j', + '\u01F0': 'j', + '\u0249': 'j', + '\u24DA': 'k', + '\uFF4B': 'k', + '\u1E31': 'k', + '\u01E9': 'k', + '\u1E33': 'k', + '\u0137': 'k', + '\u1E35': 'k', + '\u0199': 'k', + '\u2C6A': 'k', + '\uA741': 'k', + '\uA743': 'k', + '\uA745': 'k', + '\uA7A3': 'k', + '\u24DB': 'l', + '\uFF4C': 'l', + '\u0140': 'l', + '\u013A': 'l', + '\u013E': 'l', + '\u1E37': 'l', + '\u1E39': 'l', + '\u013C': 'l', + '\u1E3D': 'l', + '\u1E3B': 'l', + '\u017F': 'l', + '\u0142': 'l', + '\u019A': 'l', + '\u026B': 'l', + '\u2C61': 'l', + '\uA749': 'l', + '\uA781': 'l', + '\uA747': 'l', + '\u01C9': 'lj', + '\u24DC': 'm', + '\uFF4D': 'm', + '\u1E3F': 'm', + '\u1E41': 'm', + '\u1E43': 'm', + '\u0271': 'm', + '\u026F': 'm', + '\u24DD': 'n', + '\uFF4E': 'n', + '\u01F9': 'n', + '\u0144': 'n', + '\u00F1': 'n', + '\u1E45': 'n', + '\u0148': 'n', + '\u1E47': 'n', + '\u0146': 'n', + '\u1E4B': 'n', + '\u1E49': 'n', + '\u019E': 'n', + '\u0272': 'n', + '\u0149': 'n', + '\uA791': 'n', + '\uA7A5': 'n', + '\u01CC': 'nj', + '\u24DE': 'o', + '\uFF4F': 'o', + '\u00F2': 'o', + '\u00F3': 'o', + '\u00F4': 'o', + '\u1ED3': 'o', + '\u1ED1': 'o', + '\u1ED7': 'o', + '\u1ED5': 'o', + '\u00F5': 'o', + '\u1E4D': 'o', + '\u022D': 'o', + '\u1E4F': 'o', + '\u014D': 'o', + '\u1E51': 'o', + '\u1E53': 'o', + '\u014F': 'o', + '\u022F': 'o', + '\u0231': 'o', + '\u00F6': 'o', + '\u022B': 'o', + '\u1ECF': 'o', + '\u0151': 'o', + '\u01D2': 'o', + '\u020D': 'o', + '\u020F': 'o', + '\u01A1': 'o', + '\u1EDD': 'o', + '\u1EDB': 'o', + '\u1EE1': 'o', + '\u1EDF': 'o', + '\u1EE3': 'o', + '\u1ECD': 'o', + '\u1ED9': 'o', + '\u01EB': 'o', + '\u01ED': 'o', + '\u00F8': 'o', + '\u01FF': 'o', + '\u0254': 'o', + '\uA74B': 'o', + '\uA74D': 'o', + '\u0275': 'o', + '\u0153': 'oe', + '\u01A3': 'oi', + '\u0223': 'ou', + '\uA74F': 'oo', + '\u24DF': 'p', + '\uFF50': 'p', + '\u1E55': 'p', + '\u1E57': 'p', + '\u01A5': 'p', + '\u1D7D': 'p', + '\uA751': 'p', + '\uA753': 'p', + '\uA755': 'p', + '\u24E0': 'q', + '\uFF51': 'q', + '\u024B': 'q', + '\uA757': 'q', + '\uA759': 'q', + '\u24E1': 'r', + '\uFF52': 'r', + '\u0155': 'r', + '\u1E59': 'r', + '\u0159': 'r', + '\u0211': 'r', + '\u0213': 'r', + '\u1E5B': 'r', + '\u1E5D': 'r', + '\u0157': 'r', + '\u1E5F': 'r', + '\u024D': 'r', + '\u027D': 'r', + '\uA75B': 'r', + '\uA7A7': 'r', + '\uA783': 'r', + '\u24E2': 's', + '\uFF53': 's', + '\u00DF': 's', + '\u015B': 's', + '\u1E65': 's', + '\u015D': 's', + '\u1E61': 's', + '\u0161': 's', + '\u1E67': 's', + '\u1E63': 's', + '\u1E69': 's', + '\u0219': 's', + '\u015F': 's', + '\u023F': 's', + '\uA7A9': 's', + '\uA785': 's', + '\u1E9B': 's', + '\u24E3': 't', + '\uFF54': 't', + '\u1E6B': 't', + '\u1E97': 't', + '\u0165': 't', + '\u1E6D': 't', + '\u021B': 't', + '\u0163': 't', + '\u1E71': 't', + '\u1E6F': 't', + '\u0167': 't', + '\u01AD': 't', + '\u0288': 't', + '\u2C66': 't', + '\uA787': 't', + '\uA729': 'tz', + '\u24E4': 'u', + '\uFF55': 'u', + '\u00F9': 'u', + '\u00FA': 'u', + '\u00FB': 'u', + '\u0169': 'u', + '\u1E79': 'u', + '\u016B': 'u', + '\u1E7B': 'u', + '\u016D': 'u', + '\u00FC': 'u', + '\u01DC': 'u', + '\u01D8': 'u', + '\u01D6': 'u', + '\u01DA': 'u', + '\u1EE7': 'u', + '\u016F': 'u', + '\u0171': 'u', + '\u01D4': 'u', + '\u0215': 'u', + '\u0217': 'u', + '\u01B0': 'u', + '\u1EEB': 'u', + '\u1EE9': 'u', + '\u1EEF': 'u', + '\u1EED': 'u', + '\u1EF1': 'u', + '\u1EE5': 'u', + '\u1E73': 'u', + '\u0173': 'u', + '\u1E77': 'u', + '\u1E75': 'u', + '\u0289': 'u', + '\u24E5': 'v', + '\uFF56': 'v', + '\u1E7D': 'v', + '\u1E7F': 'v', + '\u028B': 'v', + '\uA75F': 'v', + '\u028C': 'v', + '\uA761': 'vy', + '\u24E6': 'w', + '\uFF57': 'w', + '\u1E81': 'w', + '\u1E83': 'w', + '\u0175': 'w', + '\u1E87': 'w', + '\u1E85': 'w', + '\u1E98': 'w', + '\u1E89': 'w', + '\u2C73': 'w', + '\u24E7': 'x', + '\uFF58': 'x', + '\u1E8B': 'x', + '\u1E8D': 'x', + '\u24E8': 'y', + '\uFF59': 'y', + '\u1EF3': 'y', + '\u00FD': 'y', + '\u0177': 'y', + '\u1EF9': 'y', + '\u0233': 'y', + '\u1E8F': 'y', + '\u00FF': 'y', + '\u1EF7': 'y', + '\u1E99': 'y', + '\u1EF5': 'y', + '\u01B4': 'y', + '\u024F': 'y', + '\u1EFF': 'y', + '\u24E9': 'z', + '\uFF5A': 'z', + '\u017A': 'z', + '\u1E91': 'z', + '\u017C': 'z', + '\u017E': 'z', + '\u1E93': 'z', + '\u1E95': 'z', + '\u01B6': 'z', + '\u0225': 'z', + '\u0240': 'z', + '\u2C6C': 'z', + '\uA763': 'z', + '\u0386': '\u0391', + '\u0388': '\u0395', + '\u0389': '\u0397', + '\u038A': '\u0399', + '\u03AA': '\u0399', + '\u038C': '\u039F', + '\u038E': '\u03A5', + '\u03AB': '\u03A5', + '\u038F': '\u03A9', + '\u03AC': '\u03B1', + '\u03AD': '\u03B5', + '\u03AE': '\u03B7', + '\u03AF': '\u03B9', + '\u03CA': '\u03B9', + '\u0390': '\u03B9', + '\u03CC': '\u03BF', + '\u03CD': '\u03C5', + '\u03CB': '\u03C5', + '\u03B0': '\u03C5', + '\u03CE': '\u03C9', + '\u03C2': '\u03C3', + '\u2019': '\'' + }; + + return diacritics; +}); + +S2.define('select2/data/base',[ + '../utils' +], function (Utils) { + function BaseAdapter ($element, options) { + BaseAdapter.__super__.constructor.call(this); + } + + Utils.Extend(BaseAdapter, Utils.Observable); + + BaseAdapter.prototype.current = function (callback) { + throw new Error('The `current` method must be defined in child classes.'); + }; + + BaseAdapter.prototype.query = function (params, callback) { + throw new Error('The `query` method must be defined in child classes.'); + }; + + BaseAdapter.prototype.bind = function (container, $container) { + // Can be implemented in subclasses + }; + + BaseAdapter.prototype.destroy = function () { + // Can be implemented in subclasses + }; + + BaseAdapter.prototype.generateResultId = function (container, data) { + var id = container.id + '-result-'; + + id += Utils.generateChars(4); + + if (data.id != null) { + id += '-' + data.id.toString(); + } else { + id += '-' + Utils.generateChars(4); + } + return id; + }; + + return BaseAdapter; +}); + +S2.define('select2/data/select',[ + './base', + '../utils', + 'jquery' +], function (BaseAdapter, Utils, $) { + function SelectAdapter ($element, options) { + this.$element = $element; + this.options = options; + + SelectAdapter.__super__.constructor.call(this); + } + + Utils.Extend(SelectAdapter, BaseAdapter); + + SelectAdapter.prototype.current = function (callback) { + var data = []; + var self = this; + + this.$element.find(':selected').each(function () { + var $option = $(this); + + var option = self.item($option); + + data.push(option); + }); + + callback(data); + }; + + SelectAdapter.prototype.select = function (data) { + var self = this; + + data.selected = true; + + // If data.element is a DOM node, use it instead + if ($(data.element).is('option')) { + data.element.selected = true; + + this.$element.trigger('change'); + + return; + } + + if (this.$element.prop('multiple')) { + this.current(function (currentData) { + var val = []; + + data = [data]; + data.push.apply(data, currentData); + + for (var d = 0; d < data.length; d++) { + var id = data[d].id; + + if ($.inArray(id, val) === -1) { + val.push(id); + } + } + + self.$element.val(val); + self.$element.trigger('change'); + }); + } else { + var val = data.id; + + this.$element.val(val); + this.$element.trigger('change'); + } + }; + + SelectAdapter.prototype.unselect = function (data) { + var self = this; + + if (!this.$element.prop('multiple')) { + return; + } + + data.selected = false; + + if ($(data.element).is('option')) { + data.element.selected = false; + + this.$element.trigger('change'); + + return; + } + + this.current(function (currentData) { + var val = []; + + for (var d = 0; d < currentData.length; d++) { + var id = currentData[d].id; + + if (id !== data.id && $.inArray(id, val) === -1) { + val.push(id); + } + } + + self.$element.val(val); + + self.$element.trigger('change'); + }); + }; + + SelectAdapter.prototype.bind = function (container, $container) { + var self = this; + + this.container = container; + + container.on('select', function (params) { + self.select(params.data); + }); + + container.on('unselect', function (params) { + self.unselect(params.data); + }); + }; + + SelectAdapter.prototype.destroy = function () { + // Remove anything added to child elements + this.$element.find('*').each(function () { + // Remove any custom data set by Select2 + Utils.RemoveData(this); + }); + }; + + SelectAdapter.prototype.query = function (params, callback) { + var data = []; + var self = this; + + var $options = this.$element.children(); + + $options.each(function () { + var $option = $(this); + + if (!$option.is('option') && !$option.is('optgroup')) { + return; + } + + var option = self.item($option); + + var matches = self.matches(params, option); + + if (matches !== null) { + data.push(matches); + } + }); + + callback({ + results: data + }); + }; + + SelectAdapter.prototype.addOptions = function ($options) { + Utils.appendMany(this.$element, $options); + }; + + SelectAdapter.prototype.option = function (data) { + var option; + + if (data.children) { + option = document.createElement('optgroup'); + option.label = data.text; + } else { + option = document.createElement('option'); + + if (option.textContent !== undefined) { + option.textContent = data.text; + } else { + option.innerText = data.text; + } + } + + if (data.id !== undefined) { + option.value = data.id; + } + + if (data.disabled) { + option.disabled = true; + } + + if (data.selected) { + option.selected = true; + } + + if (data.title) { + option.title = data.title; + } + + var $option = $(option); + + var normalizedData = this._normalizeItem(data); + normalizedData.element = option; + + // Override the option's data with the combined data + Utils.StoreData(option, 'data', normalizedData); + + return $option; + }; + + SelectAdapter.prototype.item = function ($option) { + var data = {}; + + data = Utils.GetData($option[0], 'data'); + + if (data != null) { + return data; + } + + if ($option.is('option')) { + data = { + id: $option.val(), + text: $option.text(), + disabled: $option.prop('disabled'), + selected: $option.prop('selected'), + title: $option.prop('title') + }; + } else if ($option.is('optgroup')) { + data = { + text: $option.prop('label'), + children: [], + title: $option.prop('title') + }; + + var $children = $option.children('option'); + var children = []; + + for (var c = 0; c < $children.length; c++) { + var $child = $($children[c]); + + var child = this.item($child); + + children.push(child); + } + + data.children = children; + } + + data = this._normalizeItem(data); + data.element = $option[0]; + + Utils.StoreData($option[0], 'data', data); + + return data; + }; + + SelectAdapter.prototype._normalizeItem = function (item) { + if (item !== Object(item)) { + item = { + id: item, + text: item + }; + } + + item = $.extend({}, { + text: '' + }, item); + + var defaults = { + selected: false, + disabled: false + }; + + if (item.id != null) { + item.id = item.id.toString(); + } + + if (item.text != null) { + item.text = item.text.toString(); + } + + if (item._resultId == null && item.id && this.container != null) { + item._resultId = this.generateResultId(this.container, item); + } + + return $.extend({}, defaults, item); + }; + + SelectAdapter.prototype.matches = function (params, data) { + var matcher = this.options.get('matcher'); + + return matcher(params, data); + }; + + return SelectAdapter; +}); + +S2.define('select2/data/array',[ + './select', + '../utils', + 'jquery' +], function (SelectAdapter, Utils, $) { + function ArrayAdapter ($element, options) { + this._dataToConvert = options.get('data') || []; + + ArrayAdapter.__super__.constructor.call(this, $element, options); + } + + Utils.Extend(ArrayAdapter, SelectAdapter); + + ArrayAdapter.prototype.bind = function (container, $container) { + ArrayAdapter.__super__.bind.call(this, container, $container); + + this.addOptions(this.convertToOptions(this._dataToConvert)); + }; + + ArrayAdapter.prototype.select = function (data) { + var $option = this.$element.find('option').filter(function (i, elm) { + return elm.value == data.id.toString(); + }); + + if ($option.length === 0) { + $option = this.option(data); + + this.addOptions($option); + } + + ArrayAdapter.__super__.select.call(this, data); + }; + + ArrayAdapter.prototype.convertToOptions = function (data) { + var self = this; + + var $existing = this.$element.find('option'); + var existingIds = $existing.map(function () { + return self.item($(this)).id; + }).get(); + + var $options = []; + + // Filter out all items except for the one passed in the argument + function onlyItem (item) { + return function () { + return $(this).val() == item.id; + }; + } + + for (var d = 0; d < data.length; d++) { + var item = this._normalizeItem(data[d]); + + // Skip items which were pre-loaded, only merge the data + if ($.inArray(item.id, existingIds) >= 0) { + var $existingOption = $existing.filter(onlyItem(item)); + + var existingData = this.item($existingOption); + var newData = $.extend(true, {}, item, existingData); + + var $newOption = this.option(newData); + + $existingOption.replaceWith($newOption); + + continue; + } + + var $option = this.option(item); + + if (item.children) { + var $children = this.convertToOptions(item.children); + + Utils.appendMany($option, $children); + } + + $options.push($option); + } + + return $options; + }; + + return ArrayAdapter; +}); + +S2.define('select2/data/ajax',[ + './array', + '../utils', + 'jquery' +], function (ArrayAdapter, Utils, $) { + function AjaxAdapter ($element, options) { + this.ajaxOptions = this._applyDefaults(options.get('ajax')); + + if (this.ajaxOptions.processResults != null) { + this.processResults = this.ajaxOptions.processResults; + } + + AjaxAdapter.__super__.constructor.call(this, $element, options); + } + + Utils.Extend(AjaxAdapter, ArrayAdapter); + + AjaxAdapter.prototype._applyDefaults = function (options) { + var defaults = { + data: function (params) { + return $.extend({}, params, { + q: params.term + }); + }, + transport: function (params, success, failure) { + var $request = $.ajax(params); + + $request.then(success); + $request.fail(failure); + + return $request; + } + }; + + return $.extend({}, defaults, options, true); + }; + + AjaxAdapter.prototype.processResults = function (results) { + return results; + }; + + AjaxAdapter.prototype.query = function (params, callback) { + var matches = []; + var self = this; + + if (this._request != null) { + // JSONP requests cannot always be aborted + if ($.isFunction(this._request.abort)) { + this._request.abort(); + } + + this._request = null; + } + + var options = $.extend({ + type: 'GET' + }, this.ajaxOptions); + + if (typeof options.url === 'function') { + options.url = options.url.call(this.$element, params); + } + + if (typeof options.data === 'function') { + options.data = options.data.call(this.$element, params); + } + + function request () { + var $request = options.transport(options, function (data) { + var results = self.processResults(data, params); + + if (self.options.get('debug') && window.console && console.error) { + // Check to make sure that the response included a `results` key. + if (!results || !results.results || !$.isArray(results.results)) { + console.error( + 'Select2: The AJAX results did not return an array in the ' + + '`results` key of the response.' + ); + } + } + + callback(results); + }, function () { + // Attempt to detect if a request was aborted + // Only works if the transport exposes a status property + if ('status' in $request && + ($request.status === 0 || $request.status === '0')) { + return; + } + + self.trigger('results:message', { + message: 'errorLoading' + }); + }); + + self._request = $request; + } + + if (this.ajaxOptions.delay && params.term != null) { + if (this._queryTimeout) { + window.clearTimeout(this._queryTimeout); + } + + this._queryTimeout = window.setTimeout(request, this.ajaxOptions.delay); + } else { + request(); + } + }; + + return AjaxAdapter; +}); + +S2.define('select2/data/tags',[ + 'jquery' +], function ($) { + function Tags (decorated, $element, options) { + var tags = options.get('tags'); + + var createTag = options.get('createTag'); + + if (createTag !== undefined) { + this.createTag = createTag; + } + + var insertTag = options.get('insertTag'); + + if (insertTag !== undefined) { + this.insertTag = insertTag; + } + + decorated.call(this, $element, options); + + if ($.isArray(tags)) { + for (var t = 0; t < tags.length; t++) { + var tag = tags[t]; + var item = this._normalizeItem(tag); + + var $option = this.option(item); + + this.$element.append($option); + } + } + } + + Tags.prototype.query = function (decorated, params, callback) { + var self = this; + + this._removeOldTags(); + + if (params.term == null || params.page != null) { + decorated.call(this, params, callback); + return; + } + + function wrapper (obj, child) { + var data = obj.results; + + for (var i = 0; i < data.length; i++) { + var option = data[i]; + + var checkChildren = ( + option.children != null && + !wrapper({ + results: option.children + }, true) + ); + + var optionText = (option.text || '').toUpperCase(); + var paramsTerm = (params.term || '').toUpperCase(); + + var checkText = optionText === paramsTerm; + + if (checkText || checkChildren) { + if (child) { + return false; + } + + obj.data = data; + callback(obj); + + return; + } + } + + if (child) { + return true; + } + + var tag = self.createTag(params); + + if (tag != null) { + var $option = self.option(tag); + $option.attr('data-select2-tag', true); + + self.addOptions([$option]); + + self.insertTag(data, tag); + } + + obj.results = data; + + callback(obj); + } + + decorated.call(this, params, wrapper); + }; + + Tags.prototype.createTag = function (decorated, params) { + var term = $.trim(params.term); + + if (term === '') { + return null; + } + + return { + id: term, + text: term + }; + }; + + Tags.prototype.insertTag = function (_, data, tag) { + data.unshift(tag); + }; + + Tags.prototype._removeOldTags = function (_) { + var $options = this.$element.find('option[data-select2-tag]'); + + $options.each(function () { + if (this.selected) { + return; + } + + $(this).remove(); + }); + }; + + return Tags; +}); + +S2.define('select2/data/tokenizer',[ + 'jquery' +], function ($) { + function Tokenizer (decorated, $element, options) { + var tokenizer = options.get('tokenizer'); + + if (tokenizer !== undefined) { + this.tokenizer = tokenizer; + } + + decorated.call(this, $element, options); + } + + Tokenizer.prototype.bind = function (decorated, container, $container) { + decorated.call(this, container, $container); + + this.$search = container.dropdown.$search || container.selection.$search || + $container.find('.select2-search__field'); + }; + + Tokenizer.prototype.query = function (decorated, params, callback) { + var self = this; + + function createAndSelect (data) { + // Normalize the data object so we can use it for checks + var item = self._normalizeItem(data); + + // Check if the data object already exists as a tag + // Select it if it doesn't + var $existingOptions = self.$element.find('option').filter(function () { + return $(this).val() === item.id; + }); + + // If an existing option wasn't found for it, create the option + if (!$existingOptions.length) { + var $option = self.option(item); + $option.attr('data-select2-tag', true); + + self._removeOldTags(); + self.addOptions([$option]); + } + + // Select the item, now that we know there is an option for it + select(item); + } + + function select (data) { + self.trigger('select', { + data: data + }); + } + + params.term = params.term || ''; + + var tokenData = this.tokenizer(params, this.options, createAndSelect); + + if (tokenData.term !== params.term) { + // Replace the search term if we have the search box + if (this.$search.length) { + this.$search.val(tokenData.term); + this.$search.trigger('focus'); + } + + params.term = tokenData.term; + } + + decorated.call(this, params, callback); + }; + + Tokenizer.prototype.tokenizer = function (_, params, options, callback) { + var separators = options.get('tokenSeparators') || []; + var term = params.term; + var i = 0; + + var createTag = this.createTag || function (params) { + return { + id: params.term, + text: params.term + }; + }; + + while (i < term.length) { + var termChar = term[i]; + + if ($.inArray(termChar, separators) === -1) { + i++; + + continue; + } + + var part = term.substr(0, i); + var partParams = $.extend({}, params, { + term: part + }); + + var data = createTag(partParams); + + if (data == null) { + i++; + continue; + } + + callback(data); + + // Reset the term to not include the tokenized portion + term = term.substr(i + 1) || ''; + i = 0; + } + + return { + term: term + }; + }; + + return Tokenizer; +}); + +S2.define('select2/data/minimumInputLength',[ + +], function () { + function MinimumInputLength (decorated, $e, options) { + this.minimumInputLength = options.get('minimumInputLength'); + + decorated.call(this, $e, options); + } + + MinimumInputLength.prototype.query = function (decorated, params, callback) { + params.term = params.term || ''; + + if (params.term.length < this.minimumInputLength) { + this.trigger('results:message', { + message: 'inputTooShort', + args: { + minimum: this.minimumInputLength, + input: params.term, + params: params + } + }); + + return; + } + + decorated.call(this, params, callback); + }; + + return MinimumInputLength; +}); + +S2.define('select2/data/maximumInputLength',[ + +], function () { + function MaximumInputLength (decorated, $e, options) { + this.maximumInputLength = options.get('maximumInputLength'); + + decorated.call(this, $e, options); + } + + MaximumInputLength.prototype.query = function (decorated, params, callback) { + params.term = params.term || ''; + + if (this.maximumInputLength > 0 && + params.term.length > this.maximumInputLength) { + this.trigger('results:message', { + message: 'inputTooLong', + args: { + maximum: this.maximumInputLength, + input: params.term, + params: params + } + }); + + return; + } + + decorated.call(this, params, callback); + }; + + return MaximumInputLength; +}); + +S2.define('select2/data/maximumSelectionLength',[ + +], function (){ + function MaximumSelectionLength (decorated, $e, options) { + this.maximumSelectionLength = options.get('maximumSelectionLength'); + + decorated.call(this, $e, options); + } + + MaximumSelectionLength.prototype.bind = + function (decorated, container, $container) { + var self = this; + + decorated.call(this, container, $container); + + container.on('select', function () { + self._checkIfMaximumSelected(); + }); + }; + + MaximumSelectionLength.prototype.query = + function (decorated, params, callback) { + var self = this; + + this._checkIfMaximumSelected(function () { + decorated.call(self, params, callback); + }); + }; + + MaximumSelectionLength.prototype._checkIfMaximumSelected = + function (_, successCallback) { + var self = this; + + this.current(function (currentData) { + var count = currentData != null ? currentData.length : 0; + if (self.maximumSelectionLength > 0 && + count >= self.maximumSelectionLength) { + self.trigger('results:message', { + message: 'maximumSelected', + args: { + maximum: self.maximumSelectionLength + } + }); + return; + } + + if (successCallback) { + successCallback(); + } + }); + }; + + return MaximumSelectionLength; +}); + +S2.define('select2/dropdown',[ + 'jquery', + './utils' +], function ($, Utils) { + function Dropdown ($element, options) { + this.$element = $element; + this.options = options; + + Dropdown.__super__.constructor.call(this); + } + + Utils.Extend(Dropdown, Utils.Observable); + + Dropdown.prototype.render = function () { + var $dropdown = $( + '' + + '' + + '' + ); + + $dropdown.attr('dir', this.options.get('dir')); + + this.$dropdown = $dropdown; + + return $dropdown; + }; + + Dropdown.prototype.bind = function () { + // Should be implemented in subclasses + }; + + Dropdown.prototype.position = function ($dropdown, $container) { + // Should be implemented in subclasses + }; + + Dropdown.prototype.destroy = function () { + // Remove the dropdown from the DOM + this.$dropdown.remove(); + }; + + return Dropdown; +}); + +S2.define('select2/dropdown/search',[ + 'jquery', + '../utils' +], function ($, Utils) { + function Search () { } + + Search.prototype.render = function (decorated) { + var $rendered = decorated.call(this); + + var $search = $( + '' + + '' + + '' + ); + + this.$searchContainer = $search; + this.$search = $search.find('input'); + + $rendered.prepend($search); + + return $rendered; + }; + + Search.prototype.bind = function (decorated, container, $container) { + var self = this; + + var resultsId = container.id + '-results'; + + decorated.call(this, container, $container); + + this.$search.on('keydown', function (evt) { + self.trigger('keypress', evt); + + self._keyUpPrevented = evt.isDefaultPrevented(); + }); + + // Workaround for browsers which do not support the `input` event + // This will prevent double-triggering of events for browsers which support + // both the `keyup` and `input` events. + this.$search.on('input', function (evt) { + // Unbind the duplicated `keyup` event + $(this).off('keyup'); + }); + + this.$search.on('keyup input', function (evt) { + self.handleSearch(evt); + }); + + container.on('open', function () { + self.$search.attr('tabindex', 0); + self.$search.attr('aria-controls', resultsId); + + self.$search.trigger('focus'); + + window.setTimeout(function () { + self.$search.trigger('focus'); + }, 0); + }); + + container.on('close', function () { + self.$search.attr('tabindex', -1); + self.$search.removeAttr('aria-controls'); + self.$search.removeAttr('aria-activedescendant'); + + self.$search.val(''); + self.$search.trigger('blur'); + }); + + container.on('focus', function () { + if (!container.isOpen()) { + self.$search.trigger('focus'); + } + }); + + container.on('results:all', function (params) { + if (params.query.term == null || params.query.term === '') { + var showSearch = self.showSearch(params); + + if (showSearch) { + self.$searchContainer.removeClass('select2-search--hide'); + } else { + self.$searchContainer.addClass('select2-search--hide'); + } + } + }); + + container.on('results:focus', function (params) { + if (params.data._resultId) { + self.$search.attr('aria-activedescendant', params.data._resultId); + } else { + self.$search.removeAttr('aria-activedescendant'); + } + }); + }; + + Search.prototype.handleSearch = function (evt) { + if (!this._keyUpPrevented) { + var input = this.$search.val(); + + this.trigger('query', { + term: input + }); + } + + this._keyUpPrevented = false; + }; + + Search.prototype.showSearch = function (_, params) { + return true; + }; + + return Search; +}); + +S2.define('select2/dropdown/hidePlaceholder',[ + +], function () { + function HidePlaceholder (decorated, $element, options, dataAdapter) { + this.placeholder = this.normalizePlaceholder(options.get('placeholder')); + + decorated.call(this, $element, options, dataAdapter); + } + + HidePlaceholder.prototype.append = function (decorated, data) { + data.results = this.removePlaceholder(data.results); + + decorated.call(this, data); + }; + + HidePlaceholder.prototype.normalizePlaceholder = function (_, placeholder) { + if (typeof placeholder === 'string') { + placeholder = { + id: '', + text: placeholder + }; + } + + return placeholder; + }; + + HidePlaceholder.prototype.removePlaceholder = function (_, data) { + var modifiedData = data.slice(0); + + for (var d = data.length - 1; d >= 0; d--) { + var item = data[d]; + + if (this.placeholder.id === item.id) { + modifiedData.splice(d, 1); + } + } + + return modifiedData; + }; + + return HidePlaceholder; +}); + +S2.define('select2/dropdown/infiniteScroll',[ + 'jquery' +], function ($) { + function InfiniteScroll (decorated, $element, options, dataAdapter) { + this.lastParams = {}; + + decorated.call(this, $element, options, dataAdapter); + + this.$loadingMore = this.createLoadingMore(); + this.loading = false; + } + + InfiniteScroll.prototype.append = function (decorated, data) { + this.$loadingMore.remove(); + this.loading = false; + + decorated.call(this, data); + + if (this.showLoadingMore(data)) { + this.$results.append(this.$loadingMore); + this.loadMoreIfNeeded(); + } + }; + + InfiniteScroll.prototype.bind = function (decorated, container, $container) { + var self = this; + + decorated.call(this, container, $container); + + container.on('query', function (params) { + self.lastParams = params; + self.loading = true; + }); + + container.on('query:append', function (params) { + self.lastParams = params; + self.loading = true; + }); + + this.$results.on('scroll', this.loadMoreIfNeeded.bind(this)); + }; + + InfiniteScroll.prototype.loadMoreIfNeeded = function () { + var isLoadMoreVisible = $.contains( + document.documentElement, + this.$loadingMore[0] + ); + + if (this.loading || !isLoadMoreVisible) { + return; + } + + var currentOffset = this.$results.offset().top + + this.$results.outerHeight(false); + var loadingMoreOffset = this.$loadingMore.offset().top + + this.$loadingMore.outerHeight(false); + + if (currentOffset + 50 >= loadingMoreOffset) { + this.loadMore(); + } + }; + + InfiniteScroll.prototype.loadMore = function () { + this.loading = true; + + var params = $.extend({}, {page: 1}, this.lastParams); + + params.page++; + + this.trigger('query:append', params); + }; + + InfiniteScroll.prototype.showLoadingMore = function (_, data) { + return data.pagination && data.pagination.more; + }; + + InfiniteScroll.prototype.createLoadingMore = function () { + var $option = $( + '
          • ' + ); + + var message = this.options.get('translations').get('loadingMore'); + + $option.html(message(this.lastParams)); + + return $option; + }; + + return InfiniteScroll; +}); + +S2.define('select2/dropdown/attachBody',[ + 'jquery', + '../utils' +], function ($, Utils) { + function AttachBody (decorated, $element, options) { + this.$dropdownParent = $(options.get('dropdownParent') || document.body); + + decorated.call(this, $element, options); + } + + AttachBody.prototype.bind = function (decorated, container, $container) { + var self = this; + + decorated.call(this, container, $container); + + container.on('open', function () { + self._showDropdown(); + self._attachPositioningHandler(container); + + // Must bind after the results handlers to ensure correct sizing + self._bindContainerResultHandlers(container); + }); + + container.on('close', function () { + self._hideDropdown(); + self._detachPositioningHandler(container); + }); + + this.$dropdownContainer.on('mousedown', function (evt) { + evt.stopPropagation(); + }); + }; + + AttachBody.prototype.destroy = function (decorated) { + decorated.call(this); + + this.$dropdownContainer.remove(); + }; + + AttachBody.prototype.position = function (decorated, $dropdown, $container) { + // Clone all of the container classes + $dropdown.attr('class', $container.attr('class')); + + $dropdown.removeClass('select2'); + $dropdown.addClass('select2-container--open'); + + $dropdown.css({ + position: 'absolute', + top: -999999 + }); + + this.$container = $container; + }; + + AttachBody.prototype.render = function (decorated) { + var $container = $(''); + + var $dropdown = decorated.call(this); + $container.append($dropdown); + + this.$dropdownContainer = $container; + + return $container; + }; + + AttachBody.prototype._hideDropdown = function (decorated) { + this.$dropdownContainer.detach(); + }; + + AttachBody.prototype._bindContainerResultHandlers = + function (decorated, container) { + + // These should only be bound once + if (this._containerResultsHandlersBound) { + return; + } + + var self = this; + + container.on('results:all', function () { + self._positionDropdown(); + self._resizeDropdown(); + }); + + container.on('results:append', function () { + self._positionDropdown(); + self._resizeDropdown(); + }); + + container.on('results:message', function () { + self._positionDropdown(); + self._resizeDropdown(); + }); + + container.on('select', function () { + self._positionDropdown(); + self._resizeDropdown(); + }); + + container.on('unselect', function () { + self._positionDropdown(); + self._resizeDropdown(); + }); + + this._containerResultsHandlersBound = true; + }; + + AttachBody.prototype._attachPositioningHandler = + function (decorated, container) { + var self = this; + + var scrollEvent = 'scroll.select2.' + container.id; + var resizeEvent = 'resize.select2.' + container.id; + var orientationEvent = 'orientationchange.select2.' + container.id; + + var $watchers = this.$container.parents().filter(Utils.hasScroll); + $watchers.each(function () { + Utils.StoreData(this, 'select2-scroll-position', { + x: $(this).scrollLeft(), + y: $(this).scrollTop() + }); + }); + + $watchers.on(scrollEvent, function (ev) { + var position = Utils.GetData(this, 'select2-scroll-position'); + $(this).scrollTop(position.y); + }); + + $(window).on(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent, + function (e) { + self._positionDropdown(); + self._resizeDropdown(); + }); + }; + + AttachBody.prototype._detachPositioningHandler = + function (decorated, container) { + var scrollEvent = 'scroll.select2.' + container.id; + var resizeEvent = 'resize.select2.' + container.id; + var orientationEvent = 'orientationchange.select2.' + container.id; + + var $watchers = this.$container.parents().filter(Utils.hasScroll); + $watchers.off(scrollEvent); + + $(window).off(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent); + }; + + AttachBody.prototype._positionDropdown = function () { + var $window = $(window); + + var isCurrentlyAbove = this.$dropdown.hasClass('select2-dropdown--above'); + var isCurrentlyBelow = this.$dropdown.hasClass('select2-dropdown--below'); + + var newDirection = null; + + var offset = this.$container.offset(); + + offset.bottom = offset.top + this.$container.outerHeight(false); + + var container = { + height: this.$container.outerHeight(false) + }; + + container.top = offset.top; + container.bottom = offset.top + container.height; + + var dropdown = { + height: this.$dropdown.outerHeight(false) + }; + + var viewport = { + top: $window.scrollTop(), + bottom: $window.scrollTop() + $window.height() + }; + + var enoughRoomAbove = viewport.top < (offset.top - dropdown.height); + var enoughRoomBelow = viewport.bottom > (offset.bottom + dropdown.height); + + var css = { + left: offset.left, + top: container.bottom + }; + + // Determine what the parent element is to use for calculating the offset + var $offsetParent = this.$dropdownParent; + + // For statically positioned elements, we need to get the element + // that is determining the offset + if ($offsetParent.css('position') === 'static') { + $offsetParent = $offsetParent.offsetParent(); + } + + var parentOffset = $offsetParent.offset(); + + css.top -= parentOffset.top; + css.left -= parentOffset.left; + + if (!isCurrentlyAbove && !isCurrentlyBelow) { + newDirection = 'below'; + } + + if (!enoughRoomBelow && enoughRoomAbove && !isCurrentlyAbove) { + newDirection = 'above'; + } else if (!enoughRoomAbove && enoughRoomBelow && isCurrentlyAbove) { + newDirection = 'below'; + } + + if (newDirection == 'above' || + (isCurrentlyAbove && newDirection !== 'below')) { + css.top = container.top - parentOffset.top - dropdown.height; + } + + if (newDirection != null) { + this.$dropdown + .removeClass('select2-dropdown--below select2-dropdown--above') + .addClass('select2-dropdown--' + newDirection); + this.$container + .removeClass('select2-container--below select2-container--above') + .addClass('select2-container--' + newDirection); + } + + this.$dropdownContainer.css(css); + }; + + AttachBody.prototype._resizeDropdown = function () { + var css = { + width: this.$container.outerWidth(false) + 'px' + }; + + if (this.options.get('dropdownAutoWidth')) { + css.minWidth = css.width; + css.position = 'relative'; + css.width = 'auto'; + } + + this.$dropdown.css(css); + }; + + AttachBody.prototype._showDropdown = function (decorated) { + this.$dropdownContainer.appendTo(this.$dropdownParent); + + this._positionDropdown(); + this._resizeDropdown(); + }; + + return AttachBody; +}); + +S2.define('select2/dropdown/minimumResultsForSearch',[ + +], function () { + function countResults (data) { + var count = 0; + + for (var d = 0; d < data.length; d++) { + var item = data[d]; + + if (item.children) { + count += countResults(item.children); + } else { + count++; + } + } + + return count; + } + + function MinimumResultsForSearch (decorated, $element, options, dataAdapter) { + this.minimumResultsForSearch = options.get('minimumResultsForSearch'); + + if (this.minimumResultsForSearch < 0) { + this.minimumResultsForSearch = Infinity; + } + + decorated.call(this, $element, options, dataAdapter); + } + + MinimumResultsForSearch.prototype.showSearch = function (decorated, params) { + if (countResults(params.data.results) < this.minimumResultsForSearch) { + return false; + } + + return decorated.call(this, params); + }; + + return MinimumResultsForSearch; +}); + +S2.define('select2/dropdown/selectOnClose',[ + '../utils' +], function (Utils) { + function SelectOnClose () { } + + SelectOnClose.prototype.bind = function (decorated, container, $container) { + var self = this; + + decorated.call(this, container, $container); + + container.on('close', function (params) { + self._handleSelectOnClose(params); + }); + }; + + SelectOnClose.prototype._handleSelectOnClose = function (_, params) { + if (params && params.originalSelect2Event != null) { + var event = params.originalSelect2Event; + + // Don't select an item if the close event was triggered from a select or + // unselect event + if (event._type === 'select' || event._type === 'unselect') { + return; + } + } + + var $highlightedResults = this.getHighlightedResults(); + + // Only select highlighted results + if ($highlightedResults.length < 1) { + return; + } + + var data = Utils.GetData($highlightedResults[0], 'data'); + + // Don't re-select already selected resulte + if ( + (data.element != null && data.element.selected) || + (data.element == null && data.selected) + ) { + return; + } + + this.trigger('select', { + data: data + }); + }; + + return SelectOnClose; +}); + +S2.define('select2/dropdown/closeOnSelect',[ + +], function () { + function CloseOnSelect () { } + + CloseOnSelect.prototype.bind = function (decorated, container, $container) { + var self = this; + + decorated.call(this, container, $container); + + container.on('select', function (evt) { + self._selectTriggered(evt); + }); + + container.on('unselect', function (evt) { + self._selectTriggered(evt); + }); + }; + + CloseOnSelect.prototype._selectTriggered = function (_, evt) { + var originalEvent = evt.originalEvent; + + // Don't close if the control key is being held + if (originalEvent && (originalEvent.ctrlKey || originalEvent.metaKey)) { + return; + } + + this.trigger('close', { + originalEvent: originalEvent, + originalSelect2Event: evt + }); + }; + + return CloseOnSelect; +}); + +S2.define('select2/i18n/en',[],function () { + // English + return { + errorLoading: function () { + return 'The results could not be loaded.'; + }, + inputTooLong: function (args) { + var overChars = args.input.length - args.maximum; + + var message = 'Please delete ' + overChars + ' character'; + + if (overChars != 1) { + message += 's'; + } + + return message; + }, + inputTooShort: function (args) { + var remainingChars = args.minimum - args.input.length; + + var message = 'Please enter ' + remainingChars + ' or more characters'; + + return message; + }, + loadingMore: function () { + return 'Loading more results…'; + }, + maximumSelected: function (args) { + var message = 'You can only select ' + args.maximum + ' item'; + + if (args.maximum != 1) { + message += 's'; + } + + return message; + }, + noResults: function () { + return 'No results found'; + }, + searching: function () { + return 'Searching…'; + }, + removeAllItems: function () { + return 'Remove all items'; + } + }; +}); + +S2.define('select2/defaults',[ + 'jquery', + 'require', + + './results', + + './selection/single', + './selection/multiple', + './selection/placeholder', + './selection/allowClear', + './selection/search', + './selection/eventRelay', + + './utils', + './translation', + './diacritics', + + './data/select', + './data/array', + './data/ajax', + './data/tags', + './data/tokenizer', + './data/minimumInputLength', + './data/maximumInputLength', + './data/maximumSelectionLength', + + './dropdown', + './dropdown/search', + './dropdown/hidePlaceholder', + './dropdown/infiniteScroll', + './dropdown/attachBody', + './dropdown/minimumResultsForSearch', + './dropdown/selectOnClose', + './dropdown/closeOnSelect', + + './i18n/en' +], function ($, require, + + ResultsList, + + SingleSelection, MultipleSelection, Placeholder, AllowClear, + SelectionSearch, EventRelay, + + Utils, Translation, DIACRITICS, + + SelectData, ArrayData, AjaxData, Tags, Tokenizer, + MinimumInputLength, MaximumInputLength, MaximumSelectionLength, + + Dropdown, DropdownSearch, HidePlaceholder, InfiniteScroll, + AttachBody, MinimumResultsForSearch, SelectOnClose, CloseOnSelect, + + EnglishTranslation) { + function Defaults () { + this.reset(); + } + + Defaults.prototype.apply = function (options) { + options = $.extend(true, {}, this.defaults, options); + + if (options.dataAdapter == null) { + if (options.ajax != null) { + options.dataAdapter = AjaxData; + } else if (options.data != null) { + options.dataAdapter = ArrayData; + } else { + options.dataAdapter = SelectData; + } + + if (options.minimumInputLength > 0) { + options.dataAdapter = Utils.Decorate( + options.dataAdapter, + MinimumInputLength + ); + } + + if (options.maximumInputLength > 0) { + options.dataAdapter = Utils.Decorate( + options.dataAdapter, + MaximumInputLength + ); + } + + if (options.maximumSelectionLength > 0) { + options.dataAdapter = Utils.Decorate( + options.dataAdapter, + MaximumSelectionLength + ); + } + + if (options.tags) { + options.dataAdapter = Utils.Decorate(options.dataAdapter, Tags); + } + + if (options.tokenSeparators != null || options.tokenizer != null) { + options.dataAdapter = Utils.Decorate( + options.dataAdapter, + Tokenizer + ); + } + + if (options.query != null) { + var Query = require(options.amdBase + 'compat/query'); + + options.dataAdapter = Utils.Decorate( + options.dataAdapter, + Query + ); + } + + if (options.initSelection != null) { + var InitSelection = require(options.amdBase + 'compat/initSelection'); + + options.dataAdapter = Utils.Decorate( + options.dataAdapter, + InitSelection + ); + } + } + + if (options.resultsAdapter == null) { + options.resultsAdapter = ResultsList; + + if (options.ajax != null) { + options.resultsAdapter = Utils.Decorate( + options.resultsAdapter, + InfiniteScroll + ); + } + + if (options.placeholder != null) { + options.resultsAdapter = Utils.Decorate( + options.resultsAdapter, + HidePlaceholder + ); + } + + if (options.selectOnClose) { + options.resultsAdapter = Utils.Decorate( + options.resultsAdapter, + SelectOnClose + ); + } + } + + if (options.dropdownAdapter == null) { + if (options.multiple) { + options.dropdownAdapter = Dropdown; + } else { + var SearchableDropdown = Utils.Decorate(Dropdown, DropdownSearch); + + options.dropdownAdapter = SearchableDropdown; + } + + if (options.minimumResultsForSearch !== 0) { + options.dropdownAdapter = Utils.Decorate( + options.dropdownAdapter, + MinimumResultsForSearch + ); + } + + if (options.closeOnSelect) { + options.dropdownAdapter = Utils.Decorate( + options.dropdownAdapter, + CloseOnSelect + ); + } + + if ( + options.dropdownCssClass != null || + options.dropdownCss != null || + options.adaptDropdownCssClass != null + ) { + var DropdownCSS = require(options.amdBase + 'compat/dropdownCss'); + + options.dropdownAdapter = Utils.Decorate( + options.dropdownAdapter, + DropdownCSS + ); + } + + options.dropdownAdapter = Utils.Decorate( + options.dropdownAdapter, + AttachBody + ); + } + + if (options.selectionAdapter == null) { + if (options.multiple) { + options.selectionAdapter = MultipleSelection; + } else { + options.selectionAdapter = SingleSelection; + } + + // Add the placeholder mixin if a placeholder was specified + if (options.placeholder != null) { + options.selectionAdapter = Utils.Decorate( + options.selectionAdapter, + Placeholder + ); + } + + if (options.allowClear) { + options.selectionAdapter = Utils.Decorate( + options.selectionAdapter, + AllowClear + ); + } + + if (options.multiple) { + options.selectionAdapter = Utils.Decorate( + options.selectionAdapter, + SelectionSearch + ); + } + + if ( + options.containerCssClass != null || + options.containerCss != null || + options.adaptContainerCssClass != null + ) { + var ContainerCSS = require(options.amdBase + 'compat/containerCss'); + + options.selectionAdapter = Utils.Decorate( + options.selectionAdapter, + ContainerCSS + ); + } + + options.selectionAdapter = Utils.Decorate( + options.selectionAdapter, + EventRelay + ); + } + + // If the defaults were not previously applied from an element, it is + // possible for the language option to have not been resolved + options.language = this._resolveLanguage(options.language); + + // Always fall back to English since it will always be complete + options.language.push('en'); + + var uniqueLanguages = []; + + for (var l = 0; l < options.language.length; l++) { + var language = options.language[l]; + + if (uniqueLanguages.indexOf(language) === -1) { + uniqueLanguages.push(language); + } + } + + options.language = uniqueLanguages; + + options.translations = this._processTranslations( + options.language, + options.debug + ); + + return options; + }; + + Defaults.prototype.reset = function () { + function stripDiacritics (text) { + // Used 'uni range + named function' from http://jsperf.com/diacritics/18 + function match(a) { + return DIACRITICS[a] || a; + } + + return text.replace(/[^\u0000-\u007E]/g, match); + } + + function matcher (params, data) { + // Always return the object if there is nothing to compare + if ($.trim(params.term) === '') { + return data; + } + + // Do a recursive check for options with children + if (data.children && data.children.length > 0) { + // Clone the data object if there are children + // This is required as we modify the object to remove any non-matches + var match = $.extend(true, {}, data); + + // Check each child of the option + for (var c = data.children.length - 1; c >= 0; c--) { + var child = data.children[c]; + + var matches = matcher(params, child); + + // If there wasn't a match, remove the object in the array + if (matches == null) { + match.children.splice(c, 1); + } + } + + // If any children matched, return the new object + if (match.children.length > 0) { + return match; + } + + // If there were no matching children, check just the plain object + return matcher(params, match); + } + + var original = stripDiacritics(data.text).toUpperCase(); + var term = stripDiacritics(params.term).toUpperCase(); + + // Check if the text contains the term + if (original.indexOf(term) > -1) { + return data; + } + + // If it doesn't contain the term, don't return anything + return null; + } + + this.defaults = { + amdBase: './', + amdLanguageBase: './i18n/', + closeOnSelect: true, + debug: false, + dropdownAutoWidth: false, + escapeMarkup: Utils.escapeMarkup, + language: {}, + matcher: matcher, + minimumInputLength: 0, + maximumInputLength: 0, + maximumSelectionLength: 0, + minimumResultsForSearch: 0, + selectOnClose: false, + scrollAfterSelect: false, + sorter: function (data) { + return data; + }, + templateResult: function (result) { + return result.text; + }, + templateSelection: function (selection) { + return selection.text; + }, + theme: 'default', + width: 'resolve' + }; + }; + + Defaults.prototype.applyFromElement = function (options, $element) { + var optionLanguage = options.language; + var defaultLanguage = this.defaults.language; + var elementLanguage = $element.prop('lang'); + var parentLanguage = $element.closest('[lang]').prop('lang'); + + var languages = Array.prototype.concat.call( + this._resolveLanguage(elementLanguage), + this._resolveLanguage(optionLanguage), + this._resolveLanguage(defaultLanguage), + this._resolveLanguage(parentLanguage) + ); + + options.language = languages; + + return options; + }; + + Defaults.prototype._resolveLanguage = function (language) { + if (!language) { + return []; + } + + if ($.isEmptyObject(language)) { + return []; + } + + if ($.isPlainObject(language)) { + return [language]; + } + + var languages; + + if (!$.isArray(language)) { + languages = [language]; + } else { + languages = language; + } + + var resolvedLanguages = []; + + for (var l = 0; l < languages.length; l++) { + resolvedLanguages.push(languages[l]); + + if (typeof languages[l] === 'string' && languages[l].indexOf('-') > 0) { + // Extract the region information if it is included + var languageParts = languages[l].split('-'); + var baseLanguage = languageParts[0]; + + resolvedLanguages.push(baseLanguage); + } + } + + return resolvedLanguages; + }; + + Defaults.prototype._processTranslations = function (languages, debug) { + var translations = new Translation(); + + for (var l = 0; l < languages.length; l++) { + var languageData = new Translation(); + + var language = languages[l]; + + if (typeof language === 'string') { + try { + // Try to load it with the original name + languageData = Translation.loadPath(language); + } catch (e) { + try { + // If we couldn't load it, check if it wasn't the full path + language = this.defaults.amdLanguageBase + language; + languageData = Translation.loadPath(language); + } catch (ex) { + // The translation could not be loaded at all. Sometimes this is + // because of a configuration problem, other times this can be + // because of how Select2 helps load all possible translation files + if (debug && window.console && console.warn) { + console.warn( + 'Select2: The language file for "' + language + '" could ' + + 'not be automatically loaded. A fallback will be used instead.' + ); + } + } + } + } else if ($.isPlainObject(language)) { + languageData = new Translation(language); + } else { + languageData = language; + } + + translations.extend(languageData); + } + + return translations; + }; + + Defaults.prototype.set = function (key, value) { + var camelKey = $.camelCase(key); + + var data = {}; + data[camelKey] = value; + + var convertedData = Utils._convertData(data); + + $.extend(true, this.defaults, convertedData); + }; + + var defaults = new Defaults(); + + return defaults; +}); + +S2.define('select2/options',[ + 'require', + 'jquery', + './defaults', + './utils' +], function (require, $, Defaults, Utils) { + function Options (options, $element) { + this.options = options; + + if ($element != null) { + this.fromElement($element); + } + + if ($element != null) { + this.options = Defaults.applyFromElement(this.options, $element); + } + + this.options = Defaults.apply(this.options); + + if ($element && $element.is('input')) { + var InputCompat = require(this.get('amdBase') + 'compat/inputData'); + + this.options.dataAdapter = Utils.Decorate( + this.options.dataAdapter, + InputCompat + ); + } + } + + Options.prototype.fromElement = function ($e) { + var excludedData = ['select2']; + + if (this.options.multiple == null) { + this.options.multiple = $e.prop('multiple'); + } + + if (this.options.disabled == null) { + this.options.disabled = $e.prop('disabled'); + } + + if (this.options.dir == null) { + if ($e.prop('dir')) { + this.options.dir = $e.prop('dir'); + } else if ($e.closest('[dir]').prop('dir')) { + this.options.dir = $e.closest('[dir]').prop('dir'); + } else { + this.options.dir = 'ltr'; + } + } + + $e.prop('disabled', this.options.disabled); + $e.prop('multiple', this.options.multiple); + + if (Utils.GetData($e[0], 'select2Tags')) { + if (this.options.debug && window.console && console.warn) { + console.warn( + 'Select2: The `data-select2-tags` attribute has been changed to ' + + 'use the `data-data` and `data-tags="true"` attributes and will be ' + + 'removed in future versions of Select2.' + ); + } + + Utils.StoreData($e[0], 'data', Utils.GetData($e[0], 'select2Tags')); + Utils.StoreData($e[0], 'tags', true); + } + + if (Utils.GetData($e[0], 'ajaxUrl')) { + if (this.options.debug && window.console && console.warn) { + console.warn( + 'Select2: The `data-ajax-url` attribute has been changed to ' + + '`data-ajax--url` and support for the old attribute will be removed' + + ' in future versions of Select2.' + ); + } + + $e.attr('ajax--url', Utils.GetData($e[0], 'ajaxUrl')); + Utils.StoreData($e[0], 'ajax-Url', Utils.GetData($e[0], 'ajaxUrl')); + } + + var dataset = {}; + + function upperCaseLetter(_, letter) { + return letter.toUpperCase(); + } + + // Pre-load all of the attributes which are prefixed with `data-` + for (var attr = 0; attr < $e[0].attributes.length; attr++) { + var attributeName = $e[0].attributes[attr].name; + var prefix = 'data-'; + + if (attributeName.substr(0, prefix.length) == prefix) { + // Get the contents of the attribute after `data-` + var dataName = attributeName.substring(prefix.length); + + // Get the data contents from the consistent source + // This is more than likely the jQuery data helper + var dataValue = Utils.GetData($e[0], dataName); + + // camelCase the attribute name to match the spec + var camelDataName = dataName.replace(/-([a-z])/g, upperCaseLetter); + + // Store the data attribute contents into the dataset since + dataset[camelDataName] = dataValue; + } + } + + // Prefer the element's `dataset` attribute if it exists + // jQuery 1.x does not correctly handle data attributes with multiple dashes + if ($.fn.jquery && $.fn.jquery.substr(0, 2) == '1.' && $e[0].dataset) { + dataset = $.extend(true, {}, $e[0].dataset, dataset); + } + + // Prefer our internal data cache if it exists + var data = $.extend(true, {}, Utils.GetData($e[0]), dataset); + + data = Utils._convertData(data); + + for (var key in data) { + if ($.inArray(key, excludedData) > -1) { + continue; + } + + if ($.isPlainObject(this.options[key])) { + $.extend(this.options[key], data[key]); + } else { + this.options[key] = data[key]; + } + } + + return this; + }; + + Options.prototype.get = function (key) { + return this.options[key]; + }; + + Options.prototype.set = function (key, val) { + this.options[key] = val; + }; + + return Options; +}); + +S2.define('select2/core',[ + 'jquery', + './options', + './utils', + './keys' +], function ($, Options, Utils, KEYS) { + var Select2 = function ($element, options) { + if (Utils.GetData($element[0], 'select2') != null) { + Utils.GetData($element[0], 'select2').destroy(); + } + + this.$element = $element; + + this.id = this._generateId($element); + + options = options || {}; + + this.options = new Options(options, $element); + + Select2.__super__.constructor.call(this); + + // Set up the tabindex + + var tabindex = $element.attr('tabindex') || 0; + Utils.StoreData($element[0], 'old-tabindex', tabindex); + $element.attr('tabindex', '-1'); + + // Set up containers and adapters + + var DataAdapter = this.options.get('dataAdapter'); + this.dataAdapter = new DataAdapter($element, this.options); + + var $container = this.render(); + + this._placeContainer($container); + + var SelectionAdapter = this.options.get('selectionAdapter'); + this.selection = new SelectionAdapter($element, this.options); + this.$selection = this.selection.render(); + + this.selection.position(this.$selection, $container); + + var DropdownAdapter = this.options.get('dropdownAdapter'); + this.dropdown = new DropdownAdapter($element, this.options); + this.$dropdown = this.dropdown.render(); + + this.dropdown.position(this.$dropdown, $container); + + var ResultsAdapter = this.options.get('resultsAdapter'); + this.results = new ResultsAdapter($element, this.options, this.dataAdapter); + this.$results = this.results.render(); + + this.results.position(this.$results, this.$dropdown); + + // Bind events + + var self = this; + + // Bind the container to all of the adapters + this._bindAdapters(); + + // Register any DOM event handlers + this._registerDomEvents(); + + // Register any internal event handlers + this._registerDataEvents(); + this._registerSelectionEvents(); + this._registerDropdownEvents(); + this._registerResultsEvents(); + this._registerEvents(); + + // Set the initial state + this.dataAdapter.current(function (initialData) { + self.trigger('selection:update', { + data: initialData + }); + }); + + // Hide the original select + $element.addClass('select2-hidden-accessible'); + $element.attr('aria-hidden', 'true'); + + // Synchronize any monitored attributes + this._syncAttributes(); + + Utils.StoreData($element[0], 'select2', this); + + // Ensure backwards compatibility with $element.data('select2'). + $element.data('select2', this); + }; + + Utils.Extend(Select2, Utils.Observable); + + Select2.prototype._generateId = function ($element) { + var id = ''; + + if ($element.attr('id') != null) { + id = $element.attr('id'); + } else if ($element.attr('name') != null) { + id = $element.attr('name') + '-' + Utils.generateChars(2); + } else { + id = Utils.generateChars(4); + } + + id = id.replace(/(:|\.|\[|\]|,)/g, ''); + id = 'select2-' + id; + + return id; + }; + + Select2.prototype._placeContainer = function ($container) { + $container.insertAfter(this.$element); + + var width = this._resolveWidth(this.$element, this.options.get('width')); + + if (width != null) { + $container.css('width', width); + } + }; + + Select2.prototype._resolveWidth = function ($element, method) { + var WIDTH = /^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i; + + if (method == 'resolve') { + var styleWidth = this._resolveWidth($element, 'style'); + + if (styleWidth != null) { + return styleWidth; + } + + return this._resolveWidth($element, 'element'); + } + + if (method == 'element') { + var elementWidth = $element.outerWidth(false); + + if (elementWidth <= 0) { + return 'auto'; + } + + return elementWidth + 'px'; + } + + if (method == 'style') { + var style = $element.attr('style'); + + if (typeof(style) !== 'string') { + return null; + } + + var attrs = style.split(';'); + + for (var i = 0, l = attrs.length; i < l; i = i + 1) { + var attr = attrs[i].replace(/\s/g, ''); + var matches = attr.match(WIDTH); + + if (matches !== null && matches.length >= 1) { + return matches[1]; + } + } + + return null; + } + + if (method == 'computedstyle') { + var computedStyle = window.getComputedStyle($element[0]); + + return computedStyle.width; + } + + return method; + }; + + Select2.prototype._bindAdapters = function () { + this.dataAdapter.bind(this, this.$container); + this.selection.bind(this, this.$container); + + this.dropdown.bind(this, this.$container); + this.results.bind(this, this.$container); + }; + + Select2.prototype._registerDomEvents = function () { + var self = this; + + this.$element.on('change.select2', function () { + self.dataAdapter.current(function (data) { + self.trigger('selection:update', { + data: data + }); + }); + }); + + this.$element.on('focus.select2', function (evt) { + self.trigger('focus', evt); + }); + + this._syncA = Utils.bind(this._syncAttributes, this); + this._syncS = Utils.bind(this._syncSubtree, this); + + if (this.$element[0].attachEvent) { + this.$element[0].attachEvent('onpropertychange', this._syncA); + } + + var observer = window.MutationObserver || + window.WebKitMutationObserver || + window.MozMutationObserver + ; + + if (observer != null) { + this._observer = new observer(function (mutations) { + $.each(mutations, self._syncA); + $.each(mutations, self._syncS); + }); + this._observer.observe(this.$element[0], { + attributes: true, + childList: true, + subtree: false + }); + } else if (this.$element[0].addEventListener) { + this.$element[0].addEventListener( + 'DOMAttrModified', + self._syncA, + false + ); + this.$element[0].addEventListener( + 'DOMNodeInserted', + self._syncS, + false + ); + this.$element[0].addEventListener( + 'DOMNodeRemoved', + self._syncS, + false + ); + } + }; + + Select2.prototype._registerDataEvents = function () { + var self = this; + + this.dataAdapter.on('*', function (name, params) { + self.trigger(name, params); + }); + }; + + Select2.prototype._registerSelectionEvents = function () { + var self = this; + var nonRelayEvents = ['toggle', 'focus']; + + this.selection.on('toggle', function () { + self.toggleDropdown(); + }); + + this.selection.on('focus', function (params) { + self.focus(params); + }); + + this.selection.on('*', function (name, params) { + if ($.inArray(name, nonRelayEvents) !== -1) { + return; + } + + self.trigger(name, params); + }); + }; + + Select2.prototype._registerDropdownEvents = function () { + var self = this; + + this.dropdown.on('*', function (name, params) { + self.trigger(name, params); + }); + }; + + Select2.prototype._registerResultsEvents = function () { + var self = this; + + this.results.on('*', function (name, params) { + self.trigger(name, params); + }); + }; + + Select2.prototype._registerEvents = function () { + var self = this; + + this.on('open', function () { + self.$container.addClass('select2-container--open'); + }); + + this.on('close', function () { + self.$container.removeClass('select2-container--open'); + }); + + this.on('enable', function () { + self.$container.removeClass('select2-container--disabled'); + }); + + this.on('disable', function () { + self.$container.addClass('select2-container--disabled'); + }); + + this.on('blur', function () { + self.$container.removeClass('select2-container--focus'); + }); + + this.on('query', function (params) { + if (!self.isOpen()) { + self.trigger('open', {}); + } + + this.dataAdapter.query(params, function (data) { + self.trigger('results:all', { + data: data, + query: params + }); + }); + }); + + this.on('query:append', function (params) { + this.dataAdapter.query(params, function (data) { + self.trigger('results:append', { + data: data, + query: params + }); + }); + }); + + this.on('keypress', function (evt) { + var key = evt.which; + + if (self.isOpen()) { + if (key === KEYS.ESC || key === KEYS.TAB || + (key === KEYS.UP && evt.altKey)) { + self.close(); + + evt.preventDefault(); + } else if (key === KEYS.ENTER) { + self.trigger('results:select', {}); + + evt.preventDefault(); + } else if ((key === KEYS.SPACE && evt.ctrlKey)) { + self.trigger('results:toggle', {}); + + evt.preventDefault(); + } else if (key === KEYS.UP) { + self.trigger('results:previous', {}); + + evt.preventDefault(); + } else if (key === KEYS.DOWN) { + self.trigger('results:next', {}); + + evt.preventDefault(); + } + } else { + if (key === KEYS.ENTER || key === KEYS.SPACE || + (key === KEYS.DOWN && evt.altKey)) { + self.open(); + + evt.preventDefault(); + } + } + }); + }; + + Select2.prototype._syncAttributes = function () { + this.options.set('disabled', this.$element.prop('disabled')); + + if (this.options.get('disabled')) { + if (this.isOpen()) { + this.close(); + } + + this.trigger('disable', {}); + } else { + this.trigger('enable', {}); + } + }; + + Select2.prototype._syncSubtree = function (evt, mutations) { + var changed = false; + var self = this; + + // Ignore any mutation events raised for elements that aren't options or + // optgroups. This handles the case when the select element is destroyed + if ( + evt && evt.target && ( + evt.target.nodeName !== 'OPTION' && evt.target.nodeName !== 'OPTGROUP' + ) + ) { + return; + } + + if (!mutations) { + // If mutation events aren't supported, then we can only assume that the + // change affected the selections + changed = true; + } else if (mutations.addedNodes && mutations.addedNodes.length > 0) { + for (var n = 0; n < mutations.addedNodes.length; n++) { + var node = mutations.addedNodes[n]; + + if (node.selected) { + changed = true; + } + } + } else if (mutations.removedNodes && mutations.removedNodes.length > 0) { + changed = true; + } + + // Only re-pull the data if we think there is a change + if (changed) { + this.dataAdapter.current(function (currentData) { + self.trigger('selection:update', { + data: currentData + }); + }); + } + }; + + /** + * Override the trigger method to automatically trigger pre-events when + * there are events that can be prevented. + */ + Select2.prototype.trigger = function (name, args) { + var actualTrigger = Select2.__super__.trigger; + var preTriggerMap = { + 'open': 'opening', + 'close': 'closing', + 'select': 'selecting', + 'unselect': 'unselecting', + 'clear': 'clearing' + }; + + if (args === undefined) { + args = {}; + } + + if (name in preTriggerMap) { + var preTriggerName = preTriggerMap[name]; + var preTriggerArgs = { + prevented: false, + name: name, + args: args + }; + + actualTrigger.call(this, preTriggerName, preTriggerArgs); + + if (preTriggerArgs.prevented) { + args.prevented = true; + + return; + } + } + + actualTrigger.call(this, name, args); + }; + + Select2.prototype.toggleDropdown = function () { + if (this.options.get('disabled')) { + return; + } + + if (this.isOpen()) { + this.close(); + } else { + this.open(); + } + }; + + Select2.prototype.open = function () { + if (this.isOpen()) { + return; + } + + this.trigger('query', {}); + }; + + Select2.prototype.close = function () { + if (!this.isOpen()) { + return; + } + + this.trigger('close', {}); + }; + + Select2.prototype.isOpen = function () { + return this.$container.hasClass('select2-container--open'); + }; + + Select2.prototype.hasFocus = function () { + return this.$container.hasClass('select2-container--focus'); + }; + + Select2.prototype.focus = function (data) { + // No need to re-trigger focus events if we are already focused + if (this.hasFocus()) { + return; + } + + this.$container.addClass('select2-container--focus'); + this.trigger('focus', {}); + }; + + Select2.prototype.enable = function (args) { + if (this.options.get('debug') && window.console && console.warn) { + console.warn( + 'Select2: The `select2("enable")` method has been deprecated and will' + + ' be removed in later Select2 versions. Use $element.prop("disabled")' + + ' instead.' + ); + } + + if (args == null || args.length === 0) { + args = [true]; + } + + var disabled = !args[0]; + + this.$element.prop('disabled', disabled); + }; + + Select2.prototype.data = function () { + if (this.options.get('debug') && + arguments.length > 0 && window.console && console.warn) { + console.warn( + 'Select2: Data can no longer be set using `select2("data")`. You ' + + 'should consider setting the value instead using `$element.val()`.' + ); + } + + var data = []; + + this.dataAdapter.current(function (currentData) { + data = currentData; + }); + + return data; + }; + + Select2.prototype.val = function (args) { + if (this.options.get('debug') && window.console && console.warn) { + console.warn( + 'Select2: The `select2("val")` method has been deprecated and will be' + + ' removed in later Select2 versions. Use $element.val() instead.' + ); + } + + if (args == null || args.length === 0) { + return this.$element.val(); + } + + var newVal = args[0]; + + if ($.isArray(newVal)) { + newVal = $.map(newVal, function (obj) { + return obj.toString(); + }); + } + + this.$element.val(newVal).trigger('change'); + }; + + Select2.prototype.destroy = function () { + this.$container.remove(); + + if (this.$element[0].detachEvent) { + this.$element[0].detachEvent('onpropertychange', this._syncA); + } + + if (this._observer != null) { + this._observer.disconnect(); + this._observer = null; + } else if (this.$element[0].removeEventListener) { + this.$element[0] + .removeEventListener('DOMAttrModified', this._syncA, false); + this.$element[0] + .removeEventListener('DOMNodeInserted', this._syncS, false); + this.$element[0] + .removeEventListener('DOMNodeRemoved', this._syncS, false); + } + + this._syncA = null; + this._syncS = null; + + this.$element.off('.select2'); + this.$element.attr('tabindex', + Utils.GetData(this.$element[0], 'old-tabindex')); + + this.$element.removeClass('select2-hidden-accessible'); + this.$element.attr('aria-hidden', 'false'); + Utils.RemoveData(this.$element[0]); + this.$element.removeData('select2'); + + this.dataAdapter.destroy(); + this.selection.destroy(); + this.dropdown.destroy(); + this.results.destroy(); + + this.dataAdapter = null; + this.selection = null; + this.dropdown = null; + this.results = null; + }; + + Select2.prototype.render = function () { + var $container = $( + '' + + '' + + '' + + '' + ); + + $container.attr('dir', this.options.get('dir')); + + this.$container = $container; + + this.$container.addClass('select2-container--' + this.options.get('theme')); + + Utils.StoreData($container[0], 'element', this.$element); + + return $container; + }; + + return Select2; +}); + +S2.define('select2/compat/utils',[ + 'jquery' +], function ($) { + function syncCssClasses ($dest, $src, adapter) { + var classes, replacements = [], adapted; + + classes = $.trim($dest.attr('class')); + + if (classes) { + classes = '' + classes; // for IE which returns object + + $(classes.split(/\s+/)).each(function () { + // Save all Select2 classes + if (this.indexOf('select2-') === 0) { + replacements.push(this); + } + }); + } + + classes = $.trim($src.attr('class')); + + if (classes) { + classes = '' + classes; // for IE which returns object + + $(classes.split(/\s+/)).each(function () { + // Only adapt non-Select2 classes + if (this.indexOf('select2-') !== 0) { + adapted = adapter(this); + + if (adapted != null) { + replacements.push(adapted); + } + } + }); + } + + $dest.attr('class', replacements.join(' ')); + } + + return { + syncCssClasses: syncCssClasses + }; +}); + +S2.define('select2/compat/containerCss',[ + 'jquery', + './utils' +], function ($, CompatUtils) { + // No-op CSS adapter that discards all classes by default + function _containerAdapter (clazz) { + return null; + } + + function ContainerCSS () { } + + ContainerCSS.prototype.render = function (decorated) { + var $container = decorated.call(this); + + var containerCssClass = this.options.get('containerCssClass') || ''; + + if ($.isFunction(containerCssClass)) { + containerCssClass = containerCssClass(this.$element); + } + + var containerCssAdapter = this.options.get('adaptContainerCssClass'); + containerCssAdapter = containerCssAdapter || _containerAdapter; + + if (containerCssClass.indexOf(':all:') !== -1) { + containerCssClass = containerCssClass.replace(':all:', ''); + + var _cssAdapter = containerCssAdapter; + + containerCssAdapter = function (clazz) { + var adapted = _cssAdapter(clazz); + + if (adapted != null) { + // Append the old one along with the adapted one + return adapted + ' ' + clazz; + } + + return clazz; + }; + } + + var containerCss = this.options.get('containerCss') || {}; + + if ($.isFunction(containerCss)) { + containerCss = containerCss(this.$element); + } + + CompatUtils.syncCssClasses($container, this.$element, containerCssAdapter); + + $container.css(containerCss); + $container.addClass(containerCssClass); + + return $container; + }; + + return ContainerCSS; +}); + +S2.define('select2/compat/dropdownCss',[ + 'jquery', + './utils' +], function ($, CompatUtils) { + // No-op CSS adapter that discards all classes by default + function _dropdownAdapter (clazz) { + return null; + } + + function DropdownCSS () { } + + DropdownCSS.prototype.render = function (decorated) { + var $dropdown = decorated.call(this); + + var dropdownCssClass = this.options.get('dropdownCssClass') || ''; + + if ($.isFunction(dropdownCssClass)) { + dropdownCssClass = dropdownCssClass(this.$element); + } + + var dropdownCssAdapter = this.options.get('adaptDropdownCssClass'); + dropdownCssAdapter = dropdownCssAdapter || _dropdownAdapter; + + if (dropdownCssClass.indexOf(':all:') !== -1) { + dropdownCssClass = dropdownCssClass.replace(':all:', ''); + + var _cssAdapter = dropdownCssAdapter; + + dropdownCssAdapter = function (clazz) { + var adapted = _cssAdapter(clazz); + + if (adapted != null) { + // Append the old one along with the adapted one + return adapted + ' ' + clazz; + } + + return clazz; + }; + } + + var dropdownCss = this.options.get('dropdownCss') || {}; + + if ($.isFunction(dropdownCss)) { + dropdownCss = dropdownCss(this.$element); + } + + CompatUtils.syncCssClasses($dropdown, this.$element, dropdownCssAdapter); + + $dropdown.css(dropdownCss); + $dropdown.addClass(dropdownCssClass); + + return $dropdown; + }; + + return DropdownCSS; +}); + +S2.define('select2/compat/initSelection',[ + 'jquery' +], function ($) { + function InitSelection (decorated, $element, options) { + if (options.get('debug') && window.console && console.warn) { + console.warn( + 'Select2: The `initSelection` option has been deprecated in favor' + + ' of a custom data adapter that overrides the `current` method. ' + + 'This method is now called multiple times instead of a single ' + + 'time when the instance is initialized. Support will be removed ' + + 'for the `initSelection` option in future versions of Select2' + ); + } + + this.initSelection = options.get('initSelection'); + this._isInitialized = false; + + decorated.call(this, $element, options); + } + + InitSelection.prototype.current = function (decorated, callback) { + var self = this; + + if (this._isInitialized) { + decorated.call(this, callback); + + return; + } + + this.initSelection.call(null, this.$element, function (data) { + self._isInitialized = true; + + if (!$.isArray(data)) { + data = [data]; + } + + callback(data); + }); + }; + + return InitSelection; +}); + +S2.define('select2/compat/inputData',[ + 'jquery', + '../utils' +], function ($, Utils) { + function InputData (decorated, $element, options) { + this._currentData = []; + this._valueSeparator = options.get('valueSeparator') || ','; + + if ($element.prop('type') === 'hidden') { + if (options.get('debug') && console && console.warn) { + console.warn( + 'Select2: Using a hidden input with Select2 is no longer ' + + 'supported and may stop working in the future. It is recommended ' + + 'to use a `');this.$searchContainer=t,this.$search=t.find("input");var n=e.call(this);return this._transferTabIndex(),n},e.prototype.bind=function(e,t,n){var i=this,r=t.id+"-results";e.call(this,t,n),t.on("open",function(){i.$search.attr("aria-controls",r),i.$search.trigger("focus")}),t.on("close",function(){i.$search.val(""),i.$search.removeAttr("aria-controls"),i.$search.removeAttr("aria-activedescendant"),i.$search.trigger("focus")}),t.on("enable",function(){i.$search.prop("disabled",!1),i._transferTabIndex()}),t.on("disable",function(){i.$search.prop("disabled",!0)}),t.on("focus",function(e){i.$search.trigger("focus")}),t.on("results:focus",function(e){e.data._resultId?i.$search.attr("aria-activedescendant",e.data._resultId):i.$search.removeAttr("aria-activedescendant")}),this.$selection.on("focusin",".select2-search--inline",function(e){i.trigger("focus",e)}),this.$selection.on("focusout",".select2-search--inline",function(e){i._handleBlur(e)}),this.$selection.on("keydown",".select2-search--inline",function(e){if(e.stopPropagation(),i.trigger("keypress",e),i._keyUpPrevented=e.isDefaultPrevented(),e.which===l.BACKSPACE&&""===i.$search.val()){var t=i.$searchContainer.prev(".select2-selection__choice");if(0this.maximumInputLength?this.trigger("results:message",{message:"inputTooLong",args:{maximum:this.maximumInputLength,input:t.term,params:t}}):e.call(this,t,n)},e}),e.define("select2/data/maximumSelectionLength",[],function(){function e(e,t,n){this.maximumSelectionLength=n.get("maximumSelectionLength"),e.call(this,t,n)}return e.prototype.bind=function(e,t,n){var i=this;e.call(this,t,n),t.on("select",function(){i._checkIfMaximumSelected()})},e.prototype.query=function(e,t,n){var i=this;this._checkIfMaximumSelected(function(){e.call(i,t,n)})},e.prototype._checkIfMaximumSelected=function(e,n){var i=this;this.current(function(e){var t=null!=e?e.length:0;0=i.maximumSelectionLength?i.trigger("results:message",{message:"maximumSelected",args:{maximum:i.maximumSelectionLength}}):n&&n()})},e}),e.define("select2/dropdown",["jquery","./utils"],function(t,e){function n(e,t){this.$element=e,this.options=t,n.__super__.constructor.call(this)}return e.Extend(n,e.Observable),n.prototype.render=function(){var e=t('');return e.attr("dir",this.options.get("dir")),this.$dropdown=e},n.prototype.bind=function(){},n.prototype.position=function(e,t){},n.prototype.destroy=function(){this.$dropdown.remove()},n}),e.define("select2/dropdown/search",["jquery","../utils"],function(o,e){function t(){}return t.prototype.render=function(e){var t=e.call(this),n=o('');return this.$searchContainer=n,this.$search=n.find("input"),t.prepend(n),t},t.prototype.bind=function(e,t,n){var i=this,r=t.id+"-results";e.call(this,t,n),this.$search.on("keydown",function(e){i.trigger("keypress",e),i._keyUpPrevented=e.isDefaultPrevented()}),this.$search.on("input",function(e){o(this).off("keyup")}),this.$search.on("keyup input",function(e){i.handleSearch(e)}),t.on("open",function(){i.$search.attr("tabindex",0),i.$search.attr("aria-controls",r),i.$search.trigger("focus"),window.setTimeout(function(){i.$search.trigger("focus")},0)}),t.on("close",function(){i.$search.attr("tabindex",-1),i.$search.removeAttr("aria-controls"),i.$search.removeAttr("aria-activedescendant"),i.$search.val(""),i.$search.trigger("blur")}),t.on("focus",function(){t.isOpen()||i.$search.trigger("focus")}),t.on("results:all",function(e){null!=e.query.term&&""!==e.query.term||(i.showSearch(e)?i.$searchContainer.removeClass("select2-search--hide"):i.$searchContainer.addClass("select2-search--hide"))}),t.on("results:focus",function(e){e.data._resultId?i.$search.attr("aria-activedescendant",e.data._resultId):i.$search.removeAttr("aria-activedescendant")})},t.prototype.handleSearch=function(e){if(!this._keyUpPrevented){var t=this.$search.val();this.trigger("query",{term:t})}this._keyUpPrevented=!1},t.prototype.showSearch=function(e,t){return!0},t}),e.define("select2/dropdown/hidePlaceholder",[],function(){function e(e,t,n,i){this.placeholder=this.normalizePlaceholder(n.get("placeholder")),e.call(this,t,n,i)}return e.prototype.append=function(e,t){t.results=this.removePlaceholder(t.results),e.call(this,t)},e.prototype.normalizePlaceholder=function(e,t){return"string"==typeof t&&(t={id:"",text:t}),t},e.prototype.removePlaceholder=function(e,t){for(var n=t.slice(0),i=t.length-1;0<=i;i--){var r=t[i];this.placeholder.id===r.id&&n.splice(i,1)}return n},e}),e.define("select2/dropdown/infiniteScroll",["jquery"],function(n){function e(e,t,n,i){this.lastParams={},e.call(this,t,n,i),this.$loadingMore=this.createLoadingMore(),this.loading=!1}return e.prototype.append=function(e,t){this.$loadingMore.remove(),this.loading=!1,e.call(this,t),this.showLoadingMore(t)&&(this.$results.append(this.$loadingMore),this.loadMoreIfNeeded())},e.prototype.bind=function(e,t,n){var i=this;e.call(this,t,n),t.on("query",function(e){i.lastParams=e,i.loading=!0}),t.on("query:append",function(e){i.lastParams=e,i.loading=!0}),this.$results.on("scroll",this.loadMoreIfNeeded.bind(this))},e.prototype.loadMoreIfNeeded=function(){var e=n.contains(document.documentElement,this.$loadingMore[0]);if(!this.loading&&e){var t=this.$results.offset().top+this.$results.outerHeight(!1);this.$loadingMore.offset().top+this.$loadingMore.outerHeight(!1)<=t+50&&this.loadMore()}},e.prototype.loadMore=function(){this.loading=!0;var e=n.extend({},{page:1},this.lastParams);e.page++,this.trigger("query:append",e)},e.prototype.showLoadingMore=function(e,t){return t.pagination&&t.pagination.more},e.prototype.createLoadingMore=function(){var e=n('
          • '),t=this.options.get("translations").get("loadingMore");return e.html(t(this.lastParams)),e},e}),e.define("select2/dropdown/attachBody",["jquery","../utils"],function(f,a){function e(e,t,n){this.$dropdownParent=f(n.get("dropdownParent")||document.body),e.call(this,t,n)}return e.prototype.bind=function(e,t,n){var i=this;e.call(this,t,n),t.on("open",function(){i._showDropdown(),i._attachPositioningHandler(t),i._bindContainerResultHandlers(t)}),t.on("close",function(){i._hideDropdown(),i._detachPositioningHandler(t)}),this.$dropdownContainer.on("mousedown",function(e){e.stopPropagation()})},e.prototype.destroy=function(e){e.call(this),this.$dropdownContainer.remove()},e.prototype.position=function(e,t,n){t.attr("class",n.attr("class")),t.removeClass("select2"),t.addClass("select2-container--open"),t.css({position:"absolute",top:-999999}),this.$container=n},e.prototype.render=function(e){var t=f(""),n=e.call(this);return t.append(n),this.$dropdownContainer=t},e.prototype._hideDropdown=function(e){this.$dropdownContainer.detach()},e.prototype._bindContainerResultHandlers=function(e,t){if(!this._containerResultsHandlersBound){var n=this;t.on("results:all",function(){n._positionDropdown(),n._resizeDropdown()}),t.on("results:append",function(){n._positionDropdown(),n._resizeDropdown()}),t.on("results:message",function(){n._positionDropdown(),n._resizeDropdown()}),t.on("select",function(){n._positionDropdown(),n._resizeDropdown()}),t.on("unselect",function(){n._positionDropdown(),n._resizeDropdown()}),this._containerResultsHandlersBound=!0}},e.prototype._attachPositioningHandler=function(e,t){var n=this,i="scroll.select2."+t.id,r="resize.select2."+t.id,o="orientationchange.select2."+t.id,s=this.$container.parents().filter(a.hasScroll);s.each(function(){a.StoreData(this,"select2-scroll-position",{x:f(this).scrollLeft(),y:f(this).scrollTop()})}),s.on(i,function(e){var t=a.GetData(this,"select2-scroll-position");f(this).scrollTop(t.y)}),f(window).on(i+" "+r+" "+o,function(e){n._positionDropdown(),n._resizeDropdown()})},e.prototype._detachPositioningHandler=function(e,t){var n="scroll.select2."+t.id,i="resize.select2."+t.id,r="orientationchange.select2."+t.id;this.$container.parents().filter(a.hasScroll).off(n),f(window).off(n+" "+i+" "+r)},e.prototype._positionDropdown=function(){var e=f(window),t=this.$dropdown.hasClass("select2-dropdown--above"),n=this.$dropdown.hasClass("select2-dropdown--below"),i=null,r=this.$container.offset();r.bottom=r.top+this.$container.outerHeight(!1);var o={height:this.$container.outerHeight(!1)};o.top=r.top,o.bottom=r.top+o.height;var s=this.$dropdown.outerHeight(!1),a=e.scrollTop(),l=e.scrollTop()+e.height(),c=ar.bottom+s,d={left:r.left,top:o.bottom},p=this.$dropdownParent;"static"===p.css("position")&&(p=p.offsetParent());var h=p.offset();d.top-=h.top,d.left-=h.left,t||n||(i="below"),u||!c||t?!c&&u&&t&&(i="below"):i="above",("above"==i||t&&"below"!==i)&&(d.top=o.top-h.top-s),null!=i&&(this.$dropdown.removeClass("select2-dropdown--below select2-dropdown--above").addClass("select2-dropdown--"+i),this.$container.removeClass("select2-container--below select2-container--above").addClass("select2-container--"+i)),this.$dropdownContainer.css(d)},e.prototype._resizeDropdown=function(){var e={width:this.$container.outerWidth(!1)+"px"};this.options.get("dropdownAutoWidth")&&(e.minWidth=e.width,e.position="relative",e.width="auto"),this.$dropdown.css(e)},e.prototype._showDropdown=function(e){this.$dropdownContainer.appendTo(this.$dropdownParent),this._positionDropdown(),this._resizeDropdown()},e}),e.define("select2/dropdown/minimumResultsForSearch",[],function(){function e(e,t,n,i){this.minimumResultsForSearch=n.get("minimumResultsForSearch"),this.minimumResultsForSearch<0&&(this.minimumResultsForSearch=1/0),e.call(this,t,n,i)}return e.prototype.showSearch=function(e,t){return!(function e(t){for(var n=0,i=0;i');return e.attr("dir",this.options.get("dir")),this.$container=e,this.$container.addClass("select2-container--"+this.options.get("theme")),u.StoreData(e[0],"element",this.$element),e},d}),e.define("select2/compat/utils",["jquery"],function(s){return{syncCssClasses:function(e,t,n){var i,r,o=[];(i=s.trim(e.attr("class")))&&s((i=""+i).split(/\s+/)).each(function(){0===this.indexOf("select2-")&&o.push(this)}),(i=s.trim(t.attr("class")))&&s((i=""+i).split(/\s+/)).each(function(){0!==this.indexOf("select2-")&&null!=(r=n(this))&&o.push(r)}),e.attr("class",o.join(" "))}}}),e.define("select2/compat/containerCss",["jquery","./utils"],function(s,a){function l(e){return null}function e(){}return e.prototype.render=function(e){var t=e.call(this),n=this.options.get("containerCssClass")||"";s.isFunction(n)&&(n=n(this.$element));var i=this.options.get("adaptContainerCssClass");if(i=i||l,-1!==n.indexOf(":all:")){n=n.replace(":all:","");var r=i;i=function(e){var t=r(e);return null!=t?t+" "+e:e}}var o=this.options.get("containerCss")||{};return s.isFunction(o)&&(o=o(this.$element)),a.syncCssClasses(t,this.$element,i),t.css(o),t.addClass(n),t},e}),e.define("select2/compat/dropdownCss",["jquery","./utils"],function(s,a){function l(e){return null}function e(){}return e.prototype.render=function(e){var t=e.call(this),n=this.options.get("dropdownCssClass")||"";s.isFunction(n)&&(n=n(this.$element));var i=this.options.get("adaptDropdownCssClass");if(i=i||l,-1!==n.indexOf(":all:")){n=n.replace(":all:","");var r=i;i=function(e){var t=r(e);return null!=t?t+" "+e:e}}var o=this.options.get("dropdownCss")||{};return s.isFunction(o)&&(o=o(this.$element)),a.syncCssClasses(t,this.$element,i),t.css(o),t.addClass(n),t},e}),e.define("select2/compat/initSelection",["jquery"],function(i){function e(e,t,n){n.get("debug")&&window.console&&console.warn&&console.warn("Select2: The `initSelection` option has been deprecated in favor of a custom data adapter that overrides the `current` method. This method is now called multiple times instead of a single time when the instance is initialized. Support will be removed for the `initSelection` option in future versions of Select2"),this.initSelection=n.get("initSelection"),this._isInitialized=!1,e.call(this,t,n)}return e.prototype.current=function(e,t){var n=this;this._isInitialized?e.call(this,t):this.initSelection.call(null,this.$element,function(e){n._isInitialized=!0,i.isArray(e)||(e=[e]),t(e)})},e}),e.define("select2/compat/inputData",["jquery","../utils"],function(s,i){function e(e,t,n){this._currentData=[],this._valueSeparator=n.get("valueSeparator")||",","hidden"===t.prop("type")&&n.get("debug")&&console&&console.warn&&console.warn("Select2: Using a hidden input with Select2 is no longer supported and may stop working in the future. It is recommended to use a `' + + '' + ); + + this.$searchContainer = $search; + this.$search = $search.find('input'); + + var $rendered = decorated.call(this); + + this._transferTabIndex(); + + return $rendered; + }; + + Search.prototype.bind = function (decorated, container, $container) { + var self = this; + + var resultsId = container.id + '-results'; + + decorated.call(this, container, $container); + + container.on('open', function () { + self.$search.attr('aria-controls', resultsId); + self.$search.trigger('focus'); + }); + + container.on('close', function () { + self.$search.val(''); + self.$search.removeAttr('aria-controls'); + self.$search.removeAttr('aria-activedescendant'); + self.$search.trigger('focus'); + }); + + container.on('enable', function () { + self.$search.prop('disabled', false); + + self._transferTabIndex(); + }); + + container.on('disable', function () { + self.$search.prop('disabled', true); + }); + + container.on('focus', function (evt) { + self.$search.trigger('focus'); + }); + + container.on('results:focus', function (params) { + if (params.data._resultId) { + self.$search.attr('aria-activedescendant', params.data._resultId); + } else { + self.$search.removeAttr('aria-activedescendant'); + } + }); + + this.$selection.on('focusin', '.select2-search--inline', function (evt) { + self.trigger('focus', evt); + }); + + this.$selection.on('focusout', '.select2-search--inline', function (evt) { + self._handleBlur(evt); + }); + + this.$selection.on('keydown', '.select2-search--inline', function (evt) { + evt.stopPropagation(); + + self.trigger('keypress', evt); + + self._keyUpPrevented = evt.isDefaultPrevented(); + + var key = evt.which; + + if (key === KEYS.BACKSPACE && self.$search.val() === '') { + var $previousChoice = self.$searchContainer + .prev('.select2-selection__choice'); + + if ($previousChoice.length > 0) { + var item = Utils.GetData($previousChoice[0], 'data'); + + self.searchRemoveChoice(item); + + evt.preventDefault(); + } + } + }); + + this.$selection.on('click', '.select2-search--inline', function (evt) { + if (self.$search.val()) { + evt.stopPropagation(); + } + }); + + // Try to detect the IE version should the `documentMode` property that + // is stored on the document. This is only implemented in IE and is + // slightly cleaner than doing a user agent check. + // This property is not available in Edge, but Edge also doesn't have + // this bug. + var msie = document.documentMode; + var disableInputEvents = msie && msie <= 11; + + // Workaround for browsers which do not support the `input` event + // This will prevent double-triggering of events for browsers which support + // both the `keyup` and `input` events. + this.$selection.on( + 'input.searchcheck', + '.select2-search--inline', + function (evt) { + // IE will trigger the `input` event when a placeholder is used on a + // search box. To get around this issue, we are forced to ignore all + // `input` events in IE and keep using `keyup`. + if (disableInputEvents) { + self.$selection.off('input.search input.searchcheck'); + return; + } + + // Unbind the duplicated `keyup` event + self.$selection.off('keyup.search'); + } + ); + + this.$selection.on( + 'keyup.search input.search', + '.select2-search--inline', + function (evt) { + // IE will trigger the `input` event when a placeholder is used on a + // search box. To get around this issue, we are forced to ignore all + // `input` events in IE and keep using `keyup`. + if (disableInputEvents && evt.type === 'input') { + self.$selection.off('input.search input.searchcheck'); + return; + } + + var key = evt.which; + + // We can freely ignore events from modifier keys + if (key == KEYS.SHIFT || key == KEYS.CTRL || key == KEYS.ALT) { + return; + } + + // Tabbing will be handled during the `keydown` phase + if (key == KEYS.TAB) { + return; + } + + self.handleSearch(evt); + } + ); + }; + + /** + * This method will transfer the tabindex attribute from the rendered + * selection to the search box. This allows for the search box to be used as + * the primary focus instead of the selection container. + * + * @private + */ + Search.prototype._transferTabIndex = function (decorated) { + this.$search.attr('tabindex', this.$selection.attr('tabindex')); + this.$selection.attr('tabindex', '-1'); + }; + + Search.prototype.createPlaceholder = function (decorated, placeholder) { + this.$search.attr('placeholder', placeholder.text); + }; + + Search.prototype.update = function (decorated, data) { + var searchHadFocus = this.$search[0] == document.activeElement; + + this.$search.attr('placeholder', ''); + + decorated.call(this, data); + + this.$selection.find('.select2-selection__rendered') + .append(this.$searchContainer); + + this.resizeSearch(); + if (searchHadFocus) { + this.$search.trigger('focus'); + } + }; + + Search.prototype.handleSearch = function () { + this.resizeSearch(); + + if (!this._keyUpPrevented) { + var input = this.$search.val(); + + this.trigger('query', { + term: input + }); + } + + this._keyUpPrevented = false; + }; + + Search.prototype.searchRemoveChoice = function (decorated, item) { + this.trigger('unselect', { + data: item + }); + + this.$search.val(item.text); + this.handleSearch(); + }; + + Search.prototype.resizeSearch = function () { + this.$search.css('width', '25px'); + + var width = ''; + + if (this.$search.attr('placeholder') !== '') { + width = this.$selection.find('.select2-selection__rendered').width(); + } else { + var minimumWidth = this.$search.val().length + 1; + + width = (minimumWidth * 0.75) + 'em'; + } + + this.$search.css('width', width); + }; + + return Search; +}); + +S2.define('select2/selection/eventRelay',[ + 'jquery' +], function ($) { + function EventRelay () { } + + EventRelay.prototype.bind = function (decorated, container, $container) { + var self = this; + var relayEvents = [ + 'open', 'opening', + 'close', 'closing', + 'select', 'selecting', + 'unselect', 'unselecting', + 'clear', 'clearing' + ]; + + var preventableEvents = [ + 'opening', 'closing', 'selecting', 'unselecting', 'clearing' + ]; + + decorated.call(this, container, $container); + + container.on('*', function (name, params) { + // Ignore events that should not be relayed + if ($.inArray(name, relayEvents) === -1) { + return; + } + + // The parameters should always be an object + params = params || {}; + + // Generate the jQuery event for the Select2 event + var evt = $.Event('select2:' + name, { + params: params + }); + + self.$element.trigger(evt); + + // Only handle preventable events if it was one + if ($.inArray(name, preventableEvents) === -1) { + return; + } + + params.prevented = evt.isDefaultPrevented(); + }); + }; + + return EventRelay; +}); + +S2.define('select2/translation',[ + 'jquery', + 'require' +], function ($, require) { + function Translation (dict) { + this.dict = dict || {}; + } + + Translation.prototype.all = function () { + return this.dict; + }; + + Translation.prototype.get = function (key) { + return this.dict[key]; + }; + + Translation.prototype.extend = function (translation) { + this.dict = $.extend({}, translation.all(), this.dict); + }; + + // Static functions + + Translation._cache = {}; + + Translation.loadPath = function (path) { + if (!(path in Translation._cache)) { + var translations = require(path); + + Translation._cache[path] = translations; + } + + return new Translation(Translation._cache[path]); + }; + + return Translation; +}); + +S2.define('select2/diacritics',[ + +], function () { + var diacritics = { + '\u24B6': 'A', + '\uFF21': 'A', + '\u00C0': 'A', + '\u00C1': 'A', + '\u00C2': 'A', + '\u1EA6': 'A', + '\u1EA4': 'A', + '\u1EAA': 'A', + '\u1EA8': 'A', + '\u00C3': 'A', + '\u0100': 'A', + '\u0102': 'A', + '\u1EB0': 'A', + '\u1EAE': 'A', + '\u1EB4': 'A', + '\u1EB2': 'A', + '\u0226': 'A', + '\u01E0': 'A', + '\u00C4': 'A', + '\u01DE': 'A', + '\u1EA2': 'A', + '\u00C5': 'A', + '\u01FA': 'A', + '\u01CD': 'A', + '\u0200': 'A', + '\u0202': 'A', + '\u1EA0': 'A', + '\u1EAC': 'A', + '\u1EB6': 'A', + '\u1E00': 'A', + '\u0104': 'A', + '\u023A': 'A', + '\u2C6F': 'A', + '\uA732': 'AA', + '\u00C6': 'AE', + '\u01FC': 'AE', + '\u01E2': 'AE', + '\uA734': 'AO', + '\uA736': 'AU', + '\uA738': 'AV', + '\uA73A': 'AV', + '\uA73C': 'AY', + '\u24B7': 'B', + '\uFF22': 'B', + '\u1E02': 'B', + '\u1E04': 'B', + '\u1E06': 'B', + '\u0243': 'B', + '\u0182': 'B', + '\u0181': 'B', + '\u24B8': 'C', + '\uFF23': 'C', + '\u0106': 'C', + '\u0108': 'C', + '\u010A': 'C', + '\u010C': 'C', + '\u00C7': 'C', + '\u1E08': 'C', + '\u0187': 'C', + '\u023B': 'C', + '\uA73E': 'C', + '\u24B9': 'D', + '\uFF24': 'D', + '\u1E0A': 'D', + '\u010E': 'D', + '\u1E0C': 'D', + '\u1E10': 'D', + '\u1E12': 'D', + '\u1E0E': 'D', + '\u0110': 'D', + '\u018B': 'D', + '\u018A': 'D', + '\u0189': 'D', + '\uA779': 'D', + '\u01F1': 'DZ', + '\u01C4': 'DZ', + '\u01F2': 'Dz', + '\u01C5': 'Dz', + '\u24BA': 'E', + '\uFF25': 'E', + '\u00C8': 'E', + '\u00C9': 'E', + '\u00CA': 'E', + '\u1EC0': 'E', + '\u1EBE': 'E', + '\u1EC4': 'E', + '\u1EC2': 'E', + '\u1EBC': 'E', + '\u0112': 'E', + '\u1E14': 'E', + '\u1E16': 'E', + '\u0114': 'E', + '\u0116': 'E', + '\u00CB': 'E', + '\u1EBA': 'E', + '\u011A': 'E', + '\u0204': 'E', + '\u0206': 'E', + '\u1EB8': 'E', + '\u1EC6': 'E', + '\u0228': 'E', + '\u1E1C': 'E', + '\u0118': 'E', + '\u1E18': 'E', + '\u1E1A': 'E', + '\u0190': 'E', + '\u018E': 'E', + '\u24BB': 'F', + '\uFF26': 'F', + '\u1E1E': 'F', + '\u0191': 'F', + '\uA77B': 'F', + '\u24BC': 'G', + '\uFF27': 'G', + '\u01F4': 'G', + '\u011C': 'G', + '\u1E20': 'G', + '\u011E': 'G', + '\u0120': 'G', + '\u01E6': 'G', + '\u0122': 'G', + '\u01E4': 'G', + '\u0193': 'G', + '\uA7A0': 'G', + '\uA77D': 'G', + '\uA77E': 'G', + '\u24BD': 'H', + '\uFF28': 'H', + '\u0124': 'H', + '\u1E22': 'H', + '\u1E26': 'H', + '\u021E': 'H', + '\u1E24': 'H', + '\u1E28': 'H', + '\u1E2A': 'H', + '\u0126': 'H', + '\u2C67': 'H', + '\u2C75': 'H', + '\uA78D': 'H', + '\u24BE': 'I', + '\uFF29': 'I', + '\u00CC': 'I', + '\u00CD': 'I', + '\u00CE': 'I', + '\u0128': 'I', + '\u012A': 'I', + '\u012C': 'I', + '\u0130': 'I', + '\u00CF': 'I', + '\u1E2E': 'I', + '\u1EC8': 'I', + '\u01CF': 'I', + '\u0208': 'I', + '\u020A': 'I', + '\u1ECA': 'I', + '\u012E': 'I', + '\u1E2C': 'I', + '\u0197': 'I', + '\u24BF': 'J', + '\uFF2A': 'J', + '\u0134': 'J', + '\u0248': 'J', + '\u24C0': 'K', + '\uFF2B': 'K', + '\u1E30': 'K', + '\u01E8': 'K', + '\u1E32': 'K', + '\u0136': 'K', + '\u1E34': 'K', + '\u0198': 'K', + '\u2C69': 'K', + '\uA740': 'K', + '\uA742': 'K', + '\uA744': 'K', + '\uA7A2': 'K', + '\u24C1': 'L', + '\uFF2C': 'L', + '\u013F': 'L', + '\u0139': 'L', + '\u013D': 'L', + '\u1E36': 'L', + '\u1E38': 'L', + '\u013B': 'L', + '\u1E3C': 'L', + '\u1E3A': 'L', + '\u0141': 'L', + '\u023D': 'L', + '\u2C62': 'L', + '\u2C60': 'L', + '\uA748': 'L', + '\uA746': 'L', + '\uA780': 'L', + '\u01C7': 'LJ', + '\u01C8': 'Lj', + '\u24C2': 'M', + '\uFF2D': 'M', + '\u1E3E': 'M', + '\u1E40': 'M', + '\u1E42': 'M', + '\u2C6E': 'M', + '\u019C': 'M', + '\u24C3': 'N', + '\uFF2E': 'N', + '\u01F8': 'N', + '\u0143': 'N', + '\u00D1': 'N', + '\u1E44': 'N', + '\u0147': 'N', + '\u1E46': 'N', + '\u0145': 'N', + '\u1E4A': 'N', + '\u1E48': 'N', + '\u0220': 'N', + '\u019D': 'N', + '\uA790': 'N', + '\uA7A4': 'N', + '\u01CA': 'NJ', + '\u01CB': 'Nj', + '\u24C4': 'O', + '\uFF2F': 'O', + '\u00D2': 'O', + '\u00D3': 'O', + '\u00D4': 'O', + '\u1ED2': 'O', + '\u1ED0': 'O', + '\u1ED6': 'O', + '\u1ED4': 'O', + '\u00D5': 'O', + '\u1E4C': 'O', + '\u022C': 'O', + '\u1E4E': 'O', + '\u014C': 'O', + '\u1E50': 'O', + '\u1E52': 'O', + '\u014E': 'O', + '\u022E': 'O', + '\u0230': 'O', + '\u00D6': 'O', + '\u022A': 'O', + '\u1ECE': 'O', + '\u0150': 'O', + '\u01D1': 'O', + '\u020C': 'O', + '\u020E': 'O', + '\u01A0': 'O', + '\u1EDC': 'O', + '\u1EDA': 'O', + '\u1EE0': 'O', + '\u1EDE': 'O', + '\u1EE2': 'O', + '\u1ECC': 'O', + '\u1ED8': 'O', + '\u01EA': 'O', + '\u01EC': 'O', + '\u00D8': 'O', + '\u01FE': 'O', + '\u0186': 'O', + '\u019F': 'O', + '\uA74A': 'O', + '\uA74C': 'O', + '\u0152': 'OE', + '\u01A2': 'OI', + '\uA74E': 'OO', + '\u0222': 'OU', + '\u24C5': 'P', + '\uFF30': 'P', + '\u1E54': 'P', + '\u1E56': 'P', + '\u01A4': 'P', + '\u2C63': 'P', + '\uA750': 'P', + '\uA752': 'P', + '\uA754': 'P', + '\u24C6': 'Q', + '\uFF31': 'Q', + '\uA756': 'Q', + '\uA758': 'Q', + '\u024A': 'Q', + '\u24C7': 'R', + '\uFF32': 'R', + '\u0154': 'R', + '\u1E58': 'R', + '\u0158': 'R', + '\u0210': 'R', + '\u0212': 'R', + '\u1E5A': 'R', + '\u1E5C': 'R', + '\u0156': 'R', + '\u1E5E': 'R', + '\u024C': 'R', + '\u2C64': 'R', + '\uA75A': 'R', + '\uA7A6': 'R', + '\uA782': 'R', + '\u24C8': 'S', + '\uFF33': 'S', + '\u1E9E': 'S', + '\u015A': 'S', + '\u1E64': 'S', + '\u015C': 'S', + '\u1E60': 'S', + '\u0160': 'S', + '\u1E66': 'S', + '\u1E62': 'S', + '\u1E68': 'S', + '\u0218': 'S', + '\u015E': 'S', + '\u2C7E': 'S', + '\uA7A8': 'S', + '\uA784': 'S', + '\u24C9': 'T', + '\uFF34': 'T', + '\u1E6A': 'T', + '\u0164': 'T', + '\u1E6C': 'T', + '\u021A': 'T', + '\u0162': 'T', + '\u1E70': 'T', + '\u1E6E': 'T', + '\u0166': 'T', + '\u01AC': 'T', + '\u01AE': 'T', + '\u023E': 'T', + '\uA786': 'T', + '\uA728': 'TZ', + '\u24CA': 'U', + '\uFF35': 'U', + '\u00D9': 'U', + '\u00DA': 'U', + '\u00DB': 'U', + '\u0168': 'U', + '\u1E78': 'U', + '\u016A': 'U', + '\u1E7A': 'U', + '\u016C': 'U', + '\u00DC': 'U', + '\u01DB': 'U', + '\u01D7': 'U', + '\u01D5': 'U', + '\u01D9': 'U', + '\u1EE6': 'U', + '\u016E': 'U', + '\u0170': 'U', + '\u01D3': 'U', + '\u0214': 'U', + '\u0216': 'U', + '\u01AF': 'U', + '\u1EEA': 'U', + '\u1EE8': 'U', + '\u1EEE': 'U', + '\u1EEC': 'U', + '\u1EF0': 'U', + '\u1EE4': 'U', + '\u1E72': 'U', + '\u0172': 'U', + '\u1E76': 'U', + '\u1E74': 'U', + '\u0244': 'U', + '\u24CB': 'V', + '\uFF36': 'V', + '\u1E7C': 'V', + '\u1E7E': 'V', + '\u01B2': 'V', + '\uA75E': 'V', + '\u0245': 'V', + '\uA760': 'VY', + '\u24CC': 'W', + '\uFF37': 'W', + '\u1E80': 'W', + '\u1E82': 'W', + '\u0174': 'W', + '\u1E86': 'W', + '\u1E84': 'W', + '\u1E88': 'W', + '\u2C72': 'W', + '\u24CD': 'X', + '\uFF38': 'X', + '\u1E8A': 'X', + '\u1E8C': 'X', + '\u24CE': 'Y', + '\uFF39': 'Y', + '\u1EF2': 'Y', + '\u00DD': 'Y', + '\u0176': 'Y', + '\u1EF8': 'Y', + '\u0232': 'Y', + '\u1E8E': 'Y', + '\u0178': 'Y', + '\u1EF6': 'Y', + '\u1EF4': 'Y', + '\u01B3': 'Y', + '\u024E': 'Y', + '\u1EFE': 'Y', + '\u24CF': 'Z', + '\uFF3A': 'Z', + '\u0179': 'Z', + '\u1E90': 'Z', + '\u017B': 'Z', + '\u017D': 'Z', + '\u1E92': 'Z', + '\u1E94': 'Z', + '\u01B5': 'Z', + '\u0224': 'Z', + '\u2C7F': 'Z', + '\u2C6B': 'Z', + '\uA762': 'Z', + '\u24D0': 'a', + '\uFF41': 'a', + '\u1E9A': 'a', + '\u00E0': 'a', + '\u00E1': 'a', + '\u00E2': 'a', + '\u1EA7': 'a', + '\u1EA5': 'a', + '\u1EAB': 'a', + '\u1EA9': 'a', + '\u00E3': 'a', + '\u0101': 'a', + '\u0103': 'a', + '\u1EB1': 'a', + '\u1EAF': 'a', + '\u1EB5': 'a', + '\u1EB3': 'a', + '\u0227': 'a', + '\u01E1': 'a', + '\u00E4': 'a', + '\u01DF': 'a', + '\u1EA3': 'a', + '\u00E5': 'a', + '\u01FB': 'a', + '\u01CE': 'a', + '\u0201': 'a', + '\u0203': 'a', + '\u1EA1': 'a', + '\u1EAD': 'a', + '\u1EB7': 'a', + '\u1E01': 'a', + '\u0105': 'a', + '\u2C65': 'a', + '\u0250': 'a', + '\uA733': 'aa', + '\u00E6': 'ae', + '\u01FD': 'ae', + '\u01E3': 'ae', + '\uA735': 'ao', + '\uA737': 'au', + '\uA739': 'av', + '\uA73B': 'av', + '\uA73D': 'ay', + '\u24D1': 'b', + '\uFF42': 'b', + '\u1E03': 'b', + '\u1E05': 'b', + '\u1E07': 'b', + '\u0180': 'b', + '\u0183': 'b', + '\u0253': 'b', + '\u24D2': 'c', + '\uFF43': 'c', + '\u0107': 'c', + '\u0109': 'c', + '\u010B': 'c', + '\u010D': 'c', + '\u00E7': 'c', + '\u1E09': 'c', + '\u0188': 'c', + '\u023C': 'c', + '\uA73F': 'c', + '\u2184': 'c', + '\u24D3': 'd', + '\uFF44': 'd', + '\u1E0B': 'd', + '\u010F': 'd', + '\u1E0D': 'd', + '\u1E11': 'd', + '\u1E13': 'd', + '\u1E0F': 'd', + '\u0111': 'd', + '\u018C': 'd', + '\u0256': 'd', + '\u0257': 'd', + '\uA77A': 'd', + '\u01F3': 'dz', + '\u01C6': 'dz', + '\u24D4': 'e', + '\uFF45': 'e', + '\u00E8': 'e', + '\u00E9': 'e', + '\u00EA': 'e', + '\u1EC1': 'e', + '\u1EBF': 'e', + '\u1EC5': 'e', + '\u1EC3': 'e', + '\u1EBD': 'e', + '\u0113': 'e', + '\u1E15': 'e', + '\u1E17': 'e', + '\u0115': 'e', + '\u0117': 'e', + '\u00EB': 'e', + '\u1EBB': 'e', + '\u011B': 'e', + '\u0205': 'e', + '\u0207': 'e', + '\u1EB9': 'e', + '\u1EC7': 'e', + '\u0229': 'e', + '\u1E1D': 'e', + '\u0119': 'e', + '\u1E19': 'e', + '\u1E1B': 'e', + '\u0247': 'e', + '\u025B': 'e', + '\u01DD': 'e', + '\u24D5': 'f', + '\uFF46': 'f', + '\u1E1F': 'f', + '\u0192': 'f', + '\uA77C': 'f', + '\u24D6': 'g', + '\uFF47': 'g', + '\u01F5': 'g', + '\u011D': 'g', + '\u1E21': 'g', + '\u011F': 'g', + '\u0121': 'g', + '\u01E7': 'g', + '\u0123': 'g', + '\u01E5': 'g', + '\u0260': 'g', + '\uA7A1': 'g', + '\u1D79': 'g', + '\uA77F': 'g', + '\u24D7': 'h', + '\uFF48': 'h', + '\u0125': 'h', + '\u1E23': 'h', + '\u1E27': 'h', + '\u021F': 'h', + '\u1E25': 'h', + '\u1E29': 'h', + '\u1E2B': 'h', + '\u1E96': 'h', + '\u0127': 'h', + '\u2C68': 'h', + '\u2C76': 'h', + '\u0265': 'h', + '\u0195': 'hv', + '\u24D8': 'i', + '\uFF49': 'i', + '\u00EC': 'i', + '\u00ED': 'i', + '\u00EE': 'i', + '\u0129': 'i', + '\u012B': 'i', + '\u012D': 'i', + '\u00EF': 'i', + '\u1E2F': 'i', + '\u1EC9': 'i', + '\u01D0': 'i', + '\u0209': 'i', + '\u020B': 'i', + '\u1ECB': 'i', + '\u012F': 'i', + '\u1E2D': 'i', + '\u0268': 'i', + '\u0131': 'i', + '\u24D9': 'j', + '\uFF4A': 'j', + '\u0135': 'j', + '\u01F0': 'j', + '\u0249': 'j', + '\u24DA': 'k', + '\uFF4B': 'k', + '\u1E31': 'k', + '\u01E9': 'k', + '\u1E33': 'k', + '\u0137': 'k', + '\u1E35': 'k', + '\u0199': 'k', + '\u2C6A': 'k', + '\uA741': 'k', + '\uA743': 'k', + '\uA745': 'k', + '\uA7A3': 'k', + '\u24DB': 'l', + '\uFF4C': 'l', + '\u0140': 'l', + '\u013A': 'l', + '\u013E': 'l', + '\u1E37': 'l', + '\u1E39': 'l', + '\u013C': 'l', + '\u1E3D': 'l', + '\u1E3B': 'l', + '\u017F': 'l', + '\u0142': 'l', + '\u019A': 'l', + '\u026B': 'l', + '\u2C61': 'l', + '\uA749': 'l', + '\uA781': 'l', + '\uA747': 'l', + '\u01C9': 'lj', + '\u24DC': 'm', + '\uFF4D': 'm', + '\u1E3F': 'm', + '\u1E41': 'm', + '\u1E43': 'm', + '\u0271': 'm', + '\u026F': 'm', + '\u24DD': 'n', + '\uFF4E': 'n', + '\u01F9': 'n', + '\u0144': 'n', + '\u00F1': 'n', + '\u1E45': 'n', + '\u0148': 'n', + '\u1E47': 'n', + '\u0146': 'n', + '\u1E4B': 'n', + '\u1E49': 'n', + '\u019E': 'n', + '\u0272': 'n', + '\u0149': 'n', + '\uA791': 'n', + '\uA7A5': 'n', + '\u01CC': 'nj', + '\u24DE': 'o', + '\uFF4F': 'o', + '\u00F2': 'o', + '\u00F3': 'o', + '\u00F4': 'o', + '\u1ED3': 'o', + '\u1ED1': 'o', + '\u1ED7': 'o', + '\u1ED5': 'o', + '\u00F5': 'o', + '\u1E4D': 'o', + '\u022D': 'o', + '\u1E4F': 'o', + '\u014D': 'o', + '\u1E51': 'o', + '\u1E53': 'o', + '\u014F': 'o', + '\u022F': 'o', + '\u0231': 'o', + '\u00F6': 'o', + '\u022B': 'o', + '\u1ECF': 'o', + '\u0151': 'o', + '\u01D2': 'o', + '\u020D': 'o', + '\u020F': 'o', + '\u01A1': 'o', + '\u1EDD': 'o', + '\u1EDB': 'o', + '\u1EE1': 'o', + '\u1EDF': 'o', + '\u1EE3': 'o', + '\u1ECD': 'o', + '\u1ED9': 'o', + '\u01EB': 'o', + '\u01ED': 'o', + '\u00F8': 'o', + '\u01FF': 'o', + '\u0254': 'o', + '\uA74B': 'o', + '\uA74D': 'o', + '\u0275': 'o', + '\u0153': 'oe', + '\u01A3': 'oi', + '\u0223': 'ou', + '\uA74F': 'oo', + '\u24DF': 'p', + '\uFF50': 'p', + '\u1E55': 'p', + '\u1E57': 'p', + '\u01A5': 'p', + '\u1D7D': 'p', + '\uA751': 'p', + '\uA753': 'p', + '\uA755': 'p', + '\u24E0': 'q', + '\uFF51': 'q', + '\u024B': 'q', + '\uA757': 'q', + '\uA759': 'q', + '\u24E1': 'r', + '\uFF52': 'r', + '\u0155': 'r', + '\u1E59': 'r', + '\u0159': 'r', + '\u0211': 'r', + '\u0213': 'r', + '\u1E5B': 'r', + '\u1E5D': 'r', + '\u0157': 'r', + '\u1E5F': 'r', + '\u024D': 'r', + '\u027D': 'r', + '\uA75B': 'r', + '\uA7A7': 'r', + '\uA783': 'r', + '\u24E2': 's', + '\uFF53': 's', + '\u00DF': 's', + '\u015B': 's', + '\u1E65': 's', + '\u015D': 's', + '\u1E61': 's', + '\u0161': 's', + '\u1E67': 's', + '\u1E63': 's', + '\u1E69': 's', + '\u0219': 's', + '\u015F': 's', + '\u023F': 's', + '\uA7A9': 's', + '\uA785': 's', + '\u1E9B': 's', + '\u24E3': 't', + '\uFF54': 't', + '\u1E6B': 't', + '\u1E97': 't', + '\u0165': 't', + '\u1E6D': 't', + '\u021B': 't', + '\u0163': 't', + '\u1E71': 't', + '\u1E6F': 't', + '\u0167': 't', + '\u01AD': 't', + '\u0288': 't', + '\u2C66': 't', + '\uA787': 't', + '\uA729': 'tz', + '\u24E4': 'u', + '\uFF55': 'u', + '\u00F9': 'u', + '\u00FA': 'u', + '\u00FB': 'u', + '\u0169': 'u', + '\u1E79': 'u', + '\u016B': 'u', + '\u1E7B': 'u', + '\u016D': 'u', + '\u00FC': 'u', + '\u01DC': 'u', + '\u01D8': 'u', + '\u01D6': 'u', + '\u01DA': 'u', + '\u1EE7': 'u', + '\u016F': 'u', + '\u0171': 'u', + '\u01D4': 'u', + '\u0215': 'u', + '\u0217': 'u', + '\u01B0': 'u', + '\u1EEB': 'u', + '\u1EE9': 'u', + '\u1EEF': 'u', + '\u1EED': 'u', + '\u1EF1': 'u', + '\u1EE5': 'u', + '\u1E73': 'u', + '\u0173': 'u', + '\u1E77': 'u', + '\u1E75': 'u', + '\u0289': 'u', + '\u24E5': 'v', + '\uFF56': 'v', + '\u1E7D': 'v', + '\u1E7F': 'v', + '\u028B': 'v', + '\uA75F': 'v', + '\u028C': 'v', + '\uA761': 'vy', + '\u24E6': 'w', + '\uFF57': 'w', + '\u1E81': 'w', + '\u1E83': 'w', + '\u0175': 'w', + '\u1E87': 'w', + '\u1E85': 'w', + '\u1E98': 'w', + '\u1E89': 'w', + '\u2C73': 'w', + '\u24E7': 'x', + '\uFF58': 'x', + '\u1E8B': 'x', + '\u1E8D': 'x', + '\u24E8': 'y', + '\uFF59': 'y', + '\u1EF3': 'y', + '\u00FD': 'y', + '\u0177': 'y', + '\u1EF9': 'y', + '\u0233': 'y', + '\u1E8F': 'y', + '\u00FF': 'y', + '\u1EF7': 'y', + '\u1E99': 'y', + '\u1EF5': 'y', + '\u01B4': 'y', + '\u024F': 'y', + '\u1EFF': 'y', + '\u24E9': 'z', + '\uFF5A': 'z', + '\u017A': 'z', + '\u1E91': 'z', + '\u017C': 'z', + '\u017E': 'z', + '\u1E93': 'z', + '\u1E95': 'z', + '\u01B6': 'z', + '\u0225': 'z', + '\u0240': 'z', + '\u2C6C': 'z', + '\uA763': 'z', + '\u0386': '\u0391', + '\u0388': '\u0395', + '\u0389': '\u0397', + '\u038A': '\u0399', + '\u03AA': '\u0399', + '\u038C': '\u039F', + '\u038E': '\u03A5', + '\u03AB': '\u03A5', + '\u038F': '\u03A9', + '\u03AC': '\u03B1', + '\u03AD': '\u03B5', + '\u03AE': '\u03B7', + '\u03AF': '\u03B9', + '\u03CA': '\u03B9', + '\u0390': '\u03B9', + '\u03CC': '\u03BF', + '\u03CD': '\u03C5', + '\u03CB': '\u03C5', + '\u03B0': '\u03C5', + '\u03CE': '\u03C9', + '\u03C2': '\u03C3', + '\u2019': '\'' + }; + + return diacritics; +}); + +S2.define('select2/data/base',[ + '../utils' +], function (Utils) { + function BaseAdapter ($element, options) { + BaseAdapter.__super__.constructor.call(this); + } + + Utils.Extend(BaseAdapter, Utils.Observable); + + BaseAdapter.prototype.current = function (callback) { + throw new Error('The `current` method must be defined in child classes.'); + }; + + BaseAdapter.prototype.query = function (params, callback) { + throw new Error('The `query` method must be defined in child classes.'); + }; + + BaseAdapter.prototype.bind = function (container, $container) { + // Can be implemented in subclasses + }; + + BaseAdapter.prototype.destroy = function () { + // Can be implemented in subclasses + }; + + BaseAdapter.prototype.generateResultId = function (container, data) { + var id = container.id + '-result-'; + + id += Utils.generateChars(4); + + if (data.id != null) { + id += '-' + data.id.toString(); + } else { + id += '-' + Utils.generateChars(4); + } + return id; + }; + + return BaseAdapter; +}); + +S2.define('select2/data/select',[ + './base', + '../utils', + 'jquery' +], function (BaseAdapter, Utils, $) { + function SelectAdapter ($element, options) { + this.$element = $element; + this.options = options; + + SelectAdapter.__super__.constructor.call(this); + } + + Utils.Extend(SelectAdapter, BaseAdapter); + + SelectAdapter.prototype.current = function (callback) { + var data = []; + var self = this; + + this.$element.find(':selected').each(function () { + var $option = $(this); + + var option = self.item($option); + + data.push(option); + }); + + callback(data); + }; + + SelectAdapter.prototype.select = function (data) { + var self = this; + + data.selected = true; + + // If data.element is a DOM node, use it instead + if ($(data.element).is('option')) { + data.element.selected = true; + + this.$element.trigger('change'); + + return; + } + + if (this.$element.prop('multiple')) { + this.current(function (currentData) { + var val = []; + + data = [data]; + data.push.apply(data, currentData); + + for (var d = 0; d < data.length; d++) { + var id = data[d].id; + + if ($.inArray(id, val) === -1) { + val.push(id); + } + } + + self.$element.val(val); + self.$element.trigger('change'); + }); + } else { + var val = data.id; + + this.$element.val(val); + this.$element.trigger('change'); + } + }; + + SelectAdapter.prototype.unselect = function (data) { + var self = this; + + if (!this.$element.prop('multiple')) { + return; + } + + data.selected = false; + + if ($(data.element).is('option')) { + data.element.selected = false; + + this.$element.trigger('change'); + + return; + } + + this.current(function (currentData) { + var val = []; + + for (var d = 0; d < currentData.length; d++) { + var id = currentData[d].id; + + if (id !== data.id && $.inArray(id, val) === -1) { + val.push(id); + } + } + + self.$element.val(val); + + self.$element.trigger('change'); + }); + }; + + SelectAdapter.prototype.bind = function (container, $container) { + var self = this; + + this.container = container; + + container.on('select', function (params) { + self.select(params.data); + }); + + container.on('unselect', function (params) { + self.unselect(params.data); + }); + }; + + SelectAdapter.prototype.destroy = function () { + // Remove anything added to child elements + this.$element.find('*').each(function () { + // Remove any custom data set by Select2 + Utils.RemoveData(this); + }); + }; + + SelectAdapter.prototype.query = function (params, callback) { + var data = []; + var self = this; + + var $options = this.$element.children(); + + $options.each(function () { + var $option = $(this); + + if (!$option.is('option') && !$option.is('optgroup')) { + return; + } + + var option = self.item($option); + + var matches = self.matches(params, option); + + if (matches !== null) { + data.push(matches); + } + }); + + callback({ + results: data + }); + }; + + SelectAdapter.prototype.addOptions = function ($options) { + Utils.appendMany(this.$element, $options); + }; + + SelectAdapter.prototype.option = function (data) { + var option; + + if (data.children) { + option = document.createElement('optgroup'); + option.label = data.text; + } else { + option = document.createElement('option'); + + if (option.textContent !== undefined) { + option.textContent = data.text; + } else { + option.innerText = data.text; + } + } + + if (data.id !== undefined) { + option.value = data.id; + } + + if (data.disabled) { + option.disabled = true; + } + + if (data.selected) { + option.selected = true; + } + + if (data.title) { + option.title = data.title; + } + + var $option = $(option); + + var normalizedData = this._normalizeItem(data); + normalizedData.element = option; + + // Override the option's data with the combined data + Utils.StoreData(option, 'data', normalizedData); + + return $option; + }; + + SelectAdapter.prototype.item = function ($option) { + var data = {}; + + data = Utils.GetData($option[0], 'data'); + + if (data != null) { + return data; + } + + if ($option.is('option')) { + data = { + id: $option.val(), + text: $option.text(), + disabled: $option.prop('disabled'), + selected: $option.prop('selected'), + title: $option.prop('title') + }; + } else if ($option.is('optgroup')) { + data = { + text: $option.prop('label'), + children: [], + title: $option.prop('title') + }; + + var $children = $option.children('option'); + var children = []; + + for (var c = 0; c < $children.length; c++) { + var $child = $($children[c]); + + var child = this.item($child); + + children.push(child); + } + + data.children = children; + } + + data = this._normalizeItem(data); + data.element = $option[0]; + + Utils.StoreData($option[0], 'data', data); + + return data; + }; + + SelectAdapter.prototype._normalizeItem = function (item) { + if (item !== Object(item)) { + item = { + id: item, + text: item + }; + } + + item = $.extend({}, { + text: '' + }, item); + + var defaults = { + selected: false, + disabled: false + }; + + if (item.id != null) { + item.id = item.id.toString(); + } + + if (item.text != null) { + item.text = item.text.toString(); + } + + if (item._resultId == null && item.id && this.container != null) { + item._resultId = this.generateResultId(this.container, item); + } + + return $.extend({}, defaults, item); + }; + + SelectAdapter.prototype.matches = function (params, data) { + var matcher = this.options.get('matcher'); + + return matcher(params, data); + }; + + return SelectAdapter; +}); + +S2.define('select2/data/array',[ + './select', + '../utils', + 'jquery' +], function (SelectAdapter, Utils, $) { + function ArrayAdapter ($element, options) { + this._dataToConvert = options.get('data') || []; + + ArrayAdapter.__super__.constructor.call(this, $element, options); + } + + Utils.Extend(ArrayAdapter, SelectAdapter); + + ArrayAdapter.prototype.bind = function (container, $container) { + ArrayAdapter.__super__.bind.call(this, container, $container); + + this.addOptions(this.convertToOptions(this._dataToConvert)); + }; + + ArrayAdapter.prototype.select = function (data) { + var $option = this.$element.find('option').filter(function (i, elm) { + return elm.value == data.id.toString(); + }); + + if ($option.length === 0) { + $option = this.option(data); + + this.addOptions($option); + } + + ArrayAdapter.__super__.select.call(this, data); + }; + + ArrayAdapter.prototype.convertToOptions = function (data) { + var self = this; + + var $existing = this.$element.find('option'); + var existingIds = $existing.map(function () { + return self.item($(this)).id; + }).get(); + + var $options = []; + + // Filter out all items except for the one passed in the argument + function onlyItem (item) { + return function () { + return $(this).val() == item.id; + }; + } + + for (var d = 0; d < data.length; d++) { + var item = this._normalizeItem(data[d]); + + // Skip items which were pre-loaded, only merge the data + if ($.inArray(item.id, existingIds) >= 0) { + var $existingOption = $existing.filter(onlyItem(item)); + + var existingData = this.item($existingOption); + var newData = $.extend(true, {}, item, existingData); + + var $newOption = this.option(newData); + + $existingOption.replaceWith($newOption); + + continue; + } + + var $option = this.option(item); + + if (item.children) { + var $children = this.convertToOptions(item.children); + + Utils.appendMany($option, $children); + } + + $options.push($option); + } + + return $options; + }; + + return ArrayAdapter; +}); + +S2.define('select2/data/ajax',[ + './array', + '../utils', + 'jquery' +], function (ArrayAdapter, Utils, $) { + function AjaxAdapter ($element, options) { + this.ajaxOptions = this._applyDefaults(options.get('ajax')); + + if (this.ajaxOptions.processResults != null) { + this.processResults = this.ajaxOptions.processResults; + } + + AjaxAdapter.__super__.constructor.call(this, $element, options); + } + + Utils.Extend(AjaxAdapter, ArrayAdapter); + + AjaxAdapter.prototype._applyDefaults = function (options) { + var defaults = { + data: function (params) { + return $.extend({}, params, { + q: params.term + }); + }, + transport: function (params, success, failure) { + var $request = $.ajax(params); + + $request.then(success); + $request.fail(failure); + + return $request; + } + }; + + return $.extend({}, defaults, options, true); + }; + + AjaxAdapter.prototype.processResults = function (results) { + return results; + }; + + AjaxAdapter.prototype.query = function (params, callback) { + var matches = []; + var self = this; + + if (this._request != null) { + // JSONP requests cannot always be aborted + if ($.isFunction(this._request.abort)) { + this._request.abort(); + } + + this._request = null; + } + + var options = $.extend({ + type: 'GET' + }, this.ajaxOptions); + + if (typeof options.url === 'function') { + options.url = options.url.call(this.$element, params); + } + + if (typeof options.data === 'function') { + options.data = options.data.call(this.$element, params); + } + + function request () { + var $request = options.transport(options, function (data) { + var results = self.processResults(data, params); + + if (self.options.get('debug') && window.console && console.error) { + // Check to make sure that the response included a `results` key. + if (!results || !results.results || !$.isArray(results.results)) { + console.error( + 'Select2: The AJAX results did not return an array in the ' + + '`results` key of the response.' + ); + } + } + + callback(results); + }, function () { + // Attempt to detect if a request was aborted + // Only works if the transport exposes a status property + if ('status' in $request && + ($request.status === 0 || $request.status === '0')) { + return; + } + + self.trigger('results:message', { + message: 'errorLoading' + }); + }); + + self._request = $request; + } + + if (this.ajaxOptions.delay && params.term != null) { + if (this._queryTimeout) { + window.clearTimeout(this._queryTimeout); + } + + this._queryTimeout = window.setTimeout(request, this.ajaxOptions.delay); + } else { + request(); + } + }; + + return AjaxAdapter; +}); + +S2.define('select2/data/tags',[ + 'jquery' +], function ($) { + function Tags (decorated, $element, options) { + var tags = options.get('tags'); + + var createTag = options.get('createTag'); + + if (createTag !== undefined) { + this.createTag = createTag; + } + + var insertTag = options.get('insertTag'); + + if (insertTag !== undefined) { + this.insertTag = insertTag; + } + + decorated.call(this, $element, options); + + if ($.isArray(tags)) { + for (var t = 0; t < tags.length; t++) { + var tag = tags[t]; + var item = this._normalizeItem(tag); + + var $option = this.option(item); + + this.$element.append($option); + } + } + } + + Tags.prototype.query = function (decorated, params, callback) { + var self = this; + + this._removeOldTags(); + + if (params.term == null || params.page != null) { + decorated.call(this, params, callback); + return; + } + + function wrapper (obj, child) { + var data = obj.results; + + for (var i = 0; i < data.length; i++) { + var option = data[i]; + + var checkChildren = ( + option.children != null && + !wrapper({ + results: option.children + }, true) + ); + + var optionText = (option.text || '').toUpperCase(); + var paramsTerm = (params.term || '').toUpperCase(); + + var checkText = optionText === paramsTerm; + + if (checkText || checkChildren) { + if (child) { + return false; + } + + obj.data = data; + callback(obj); + + return; + } + } + + if (child) { + return true; + } + + var tag = self.createTag(params); + + if (tag != null) { + var $option = self.option(tag); + $option.attr('data-select2-tag', true); + + self.addOptions([$option]); + + self.insertTag(data, tag); + } + + obj.results = data; + + callback(obj); + } + + decorated.call(this, params, wrapper); + }; + + Tags.prototype.createTag = function (decorated, params) { + var term = $.trim(params.term); + + if (term === '') { + return null; + } + + return { + id: term, + text: term + }; + }; + + Tags.prototype.insertTag = function (_, data, tag) { + data.unshift(tag); + }; + + Tags.prototype._removeOldTags = function (_) { + var $options = this.$element.find('option[data-select2-tag]'); + + $options.each(function () { + if (this.selected) { + return; + } + + $(this).remove(); + }); + }; + + return Tags; +}); + +S2.define('select2/data/tokenizer',[ + 'jquery' +], function ($) { + function Tokenizer (decorated, $element, options) { + var tokenizer = options.get('tokenizer'); + + if (tokenizer !== undefined) { + this.tokenizer = tokenizer; + } + + decorated.call(this, $element, options); + } + + Tokenizer.prototype.bind = function (decorated, container, $container) { + decorated.call(this, container, $container); + + this.$search = container.dropdown.$search || container.selection.$search || + $container.find('.select2-search__field'); + }; + + Tokenizer.prototype.query = function (decorated, params, callback) { + var self = this; + + function createAndSelect (data) { + // Normalize the data object so we can use it for checks + var item = self._normalizeItem(data); + + // Check if the data object already exists as a tag + // Select it if it doesn't + var $existingOptions = self.$element.find('option').filter(function () { + return $(this).val() === item.id; + }); + + // If an existing option wasn't found for it, create the option + if (!$existingOptions.length) { + var $option = self.option(item); + $option.attr('data-select2-tag', true); + + self._removeOldTags(); + self.addOptions([$option]); + } + + // Select the item, now that we know there is an option for it + select(item); + } + + function select (data) { + self.trigger('select', { + data: data + }); + } + + params.term = params.term || ''; + + var tokenData = this.tokenizer(params, this.options, createAndSelect); + + if (tokenData.term !== params.term) { + // Replace the search term if we have the search box + if (this.$search.length) { + this.$search.val(tokenData.term); + this.$search.trigger('focus'); + } + + params.term = tokenData.term; + } + + decorated.call(this, params, callback); + }; + + Tokenizer.prototype.tokenizer = function (_, params, options, callback) { + var separators = options.get('tokenSeparators') || []; + var term = params.term; + var i = 0; + + var createTag = this.createTag || function (params) { + return { + id: params.term, + text: params.term + }; + }; + + while (i < term.length) { + var termChar = term[i]; + + if ($.inArray(termChar, separators) === -1) { + i++; + + continue; + } + + var part = term.substr(0, i); + var partParams = $.extend({}, params, { + term: part + }); + + var data = createTag(partParams); + + if (data == null) { + i++; + continue; + } + + callback(data); + + // Reset the term to not include the tokenized portion + term = term.substr(i + 1) || ''; + i = 0; + } + + return { + term: term + }; + }; + + return Tokenizer; +}); + +S2.define('select2/data/minimumInputLength',[ + +], function () { + function MinimumInputLength (decorated, $e, options) { + this.minimumInputLength = options.get('minimumInputLength'); + + decorated.call(this, $e, options); + } + + MinimumInputLength.prototype.query = function (decorated, params, callback) { + params.term = params.term || ''; + + if (params.term.length < this.minimumInputLength) { + this.trigger('results:message', { + message: 'inputTooShort', + args: { + minimum: this.minimumInputLength, + input: params.term, + params: params + } + }); + + return; + } + + decorated.call(this, params, callback); + }; + + return MinimumInputLength; +}); + +S2.define('select2/data/maximumInputLength',[ + +], function () { + function MaximumInputLength (decorated, $e, options) { + this.maximumInputLength = options.get('maximumInputLength'); + + decorated.call(this, $e, options); + } + + MaximumInputLength.prototype.query = function (decorated, params, callback) { + params.term = params.term || ''; + + if (this.maximumInputLength > 0 && + params.term.length > this.maximumInputLength) { + this.trigger('results:message', { + message: 'inputTooLong', + args: { + maximum: this.maximumInputLength, + input: params.term, + params: params + } + }); + + return; + } + + decorated.call(this, params, callback); + }; + + return MaximumInputLength; +}); + +S2.define('select2/data/maximumSelectionLength',[ + +], function (){ + function MaximumSelectionLength (decorated, $e, options) { + this.maximumSelectionLength = options.get('maximumSelectionLength'); + + decorated.call(this, $e, options); + } + + MaximumSelectionLength.prototype.bind = + function (decorated, container, $container) { + var self = this; + + decorated.call(this, container, $container); + + container.on('select', function () { + self._checkIfMaximumSelected(); + }); + }; + + MaximumSelectionLength.prototype.query = + function (decorated, params, callback) { + var self = this; + + this._checkIfMaximumSelected(function () { + decorated.call(self, params, callback); + }); + }; + + MaximumSelectionLength.prototype._checkIfMaximumSelected = + function (_, successCallback) { + var self = this; + + this.current(function (currentData) { + var count = currentData != null ? currentData.length : 0; + if (self.maximumSelectionLength > 0 && + count >= self.maximumSelectionLength) { + self.trigger('results:message', { + message: 'maximumSelected', + args: { + maximum: self.maximumSelectionLength + } + }); + return; + } + + if (successCallback) { + successCallback(); + } + }); + }; + + return MaximumSelectionLength; +}); + +S2.define('select2/dropdown',[ + 'jquery', + './utils' +], function ($, Utils) { + function Dropdown ($element, options) { + this.$element = $element; + this.options = options; + + Dropdown.__super__.constructor.call(this); + } + + Utils.Extend(Dropdown, Utils.Observable); + + Dropdown.prototype.render = function () { + var $dropdown = $( + '' + + '' + + '' + ); + + $dropdown.attr('dir', this.options.get('dir')); + + this.$dropdown = $dropdown; + + return $dropdown; + }; + + Dropdown.prototype.bind = function () { + // Should be implemented in subclasses + }; + + Dropdown.prototype.position = function ($dropdown, $container) { + // Should be implemented in subclasses + }; + + Dropdown.prototype.destroy = function () { + // Remove the dropdown from the DOM + this.$dropdown.remove(); + }; + + return Dropdown; +}); + +S2.define('select2/dropdown/search',[ + 'jquery', + '../utils' +], function ($, Utils) { + function Search () { } + + Search.prototype.render = function (decorated) { + var $rendered = decorated.call(this); + + var $search = $( + '' + + '' + + '' + ); + + this.$searchContainer = $search; + this.$search = $search.find('input'); + + $rendered.prepend($search); + + return $rendered; + }; + + Search.prototype.bind = function (decorated, container, $container) { + var self = this; + + var resultsId = container.id + '-results'; + + decorated.call(this, container, $container); + + this.$search.on('keydown', function (evt) { + self.trigger('keypress', evt); + + self._keyUpPrevented = evt.isDefaultPrevented(); + }); + + // Workaround for browsers which do not support the `input` event + // This will prevent double-triggering of events for browsers which support + // both the `keyup` and `input` events. + this.$search.on('input', function (evt) { + // Unbind the duplicated `keyup` event + $(this).off('keyup'); + }); + + this.$search.on('keyup input', function (evt) { + self.handleSearch(evt); + }); + + container.on('open', function () { + self.$search.attr('tabindex', 0); + self.$search.attr('aria-controls', resultsId); + + self.$search.trigger('focus'); + + window.setTimeout(function () { + self.$search.trigger('focus'); + }, 0); + }); + + container.on('close', function () { + self.$search.attr('tabindex', -1); + self.$search.removeAttr('aria-controls'); + self.$search.removeAttr('aria-activedescendant'); + + self.$search.val(''); + self.$search.trigger('blur'); + }); + + container.on('focus', function () { + if (!container.isOpen()) { + self.$search.trigger('focus'); + } + }); + + container.on('results:all', function (params) { + if (params.query.term == null || params.query.term === '') { + var showSearch = self.showSearch(params); + + if (showSearch) { + self.$searchContainer.removeClass('select2-search--hide'); + } else { + self.$searchContainer.addClass('select2-search--hide'); + } + } + }); + + container.on('results:focus', function (params) { + if (params.data._resultId) { + self.$search.attr('aria-activedescendant', params.data._resultId); + } else { + self.$search.removeAttr('aria-activedescendant'); + } + }); + }; + + Search.prototype.handleSearch = function (evt) { + if (!this._keyUpPrevented) { + var input = this.$search.val(); + + this.trigger('query', { + term: input + }); + } + + this._keyUpPrevented = false; + }; + + Search.prototype.showSearch = function (_, params) { + return true; + }; + + return Search; +}); + +S2.define('select2/dropdown/hidePlaceholder',[ + +], function () { + function HidePlaceholder (decorated, $element, options, dataAdapter) { + this.placeholder = this.normalizePlaceholder(options.get('placeholder')); + + decorated.call(this, $element, options, dataAdapter); + } + + HidePlaceholder.prototype.append = function (decorated, data) { + data.results = this.removePlaceholder(data.results); + + decorated.call(this, data); + }; + + HidePlaceholder.prototype.normalizePlaceholder = function (_, placeholder) { + if (typeof placeholder === 'string') { + placeholder = { + id: '', + text: placeholder + }; + } + + return placeholder; + }; + + HidePlaceholder.prototype.removePlaceholder = function (_, data) { + var modifiedData = data.slice(0); + + for (var d = data.length - 1; d >= 0; d--) { + var item = data[d]; + + if (this.placeholder.id === item.id) { + modifiedData.splice(d, 1); + } + } + + return modifiedData; + }; + + return HidePlaceholder; +}); + +S2.define('select2/dropdown/infiniteScroll',[ + 'jquery' +], function ($) { + function InfiniteScroll (decorated, $element, options, dataAdapter) { + this.lastParams = {}; + + decorated.call(this, $element, options, dataAdapter); + + this.$loadingMore = this.createLoadingMore(); + this.loading = false; + } + + InfiniteScroll.prototype.append = function (decorated, data) { + this.$loadingMore.remove(); + this.loading = false; + + decorated.call(this, data); + + if (this.showLoadingMore(data)) { + this.$results.append(this.$loadingMore); + this.loadMoreIfNeeded(); + } + }; + + InfiniteScroll.prototype.bind = function (decorated, container, $container) { + var self = this; + + decorated.call(this, container, $container); + + container.on('query', function (params) { + self.lastParams = params; + self.loading = true; + }); + + container.on('query:append', function (params) { + self.lastParams = params; + self.loading = true; + }); + + this.$results.on('scroll', this.loadMoreIfNeeded.bind(this)); + }; + + InfiniteScroll.prototype.loadMoreIfNeeded = function () { + var isLoadMoreVisible = $.contains( + document.documentElement, + this.$loadingMore[0] + ); + + if (this.loading || !isLoadMoreVisible) { + return; + } + + var currentOffset = this.$results.offset().top + + this.$results.outerHeight(false); + var loadingMoreOffset = this.$loadingMore.offset().top + + this.$loadingMore.outerHeight(false); + + if (currentOffset + 50 >= loadingMoreOffset) { + this.loadMore(); + } + }; + + InfiniteScroll.prototype.loadMore = function () { + this.loading = true; + + var params = $.extend({}, {page: 1}, this.lastParams); + + params.page++; + + this.trigger('query:append', params); + }; + + InfiniteScroll.prototype.showLoadingMore = function (_, data) { + return data.pagination && data.pagination.more; + }; + + InfiniteScroll.prototype.createLoadingMore = function () { + var $option = $( + '
          • ' + ); + + var message = this.options.get('translations').get('loadingMore'); + + $option.html(message(this.lastParams)); + + return $option; + }; + + return InfiniteScroll; +}); + +S2.define('select2/dropdown/attachBody',[ + 'jquery', + '../utils' +], function ($, Utils) { + function AttachBody (decorated, $element, options) { + this.$dropdownParent = $(options.get('dropdownParent') || document.body); + + decorated.call(this, $element, options); + } + + AttachBody.prototype.bind = function (decorated, container, $container) { + var self = this; + + decorated.call(this, container, $container); + + container.on('open', function () { + self._showDropdown(); + self._attachPositioningHandler(container); + + // Must bind after the results handlers to ensure correct sizing + self._bindContainerResultHandlers(container); + }); + + container.on('close', function () { + self._hideDropdown(); + self._detachPositioningHandler(container); + }); + + this.$dropdownContainer.on('mousedown', function (evt) { + evt.stopPropagation(); + }); + }; + + AttachBody.prototype.destroy = function (decorated) { + decorated.call(this); + + this.$dropdownContainer.remove(); + }; + + AttachBody.prototype.position = function (decorated, $dropdown, $container) { + // Clone all of the container classes + $dropdown.attr('class', $container.attr('class')); + + $dropdown.removeClass('select2'); + $dropdown.addClass('select2-container--open'); + + $dropdown.css({ + position: 'absolute', + top: -999999 + }); + + this.$container = $container; + }; + + AttachBody.prototype.render = function (decorated) { + var $container = $(''); + + var $dropdown = decorated.call(this); + $container.append($dropdown); + + this.$dropdownContainer = $container; + + return $container; + }; + + AttachBody.prototype._hideDropdown = function (decorated) { + this.$dropdownContainer.detach(); + }; + + AttachBody.prototype._bindContainerResultHandlers = + function (decorated, container) { + + // These should only be bound once + if (this._containerResultsHandlersBound) { + return; + } + + var self = this; + + container.on('results:all', function () { + self._positionDropdown(); + self._resizeDropdown(); + }); + + container.on('results:append', function () { + self._positionDropdown(); + self._resizeDropdown(); + }); + + container.on('results:message', function () { + self._positionDropdown(); + self._resizeDropdown(); + }); + + container.on('select', function () { + self._positionDropdown(); + self._resizeDropdown(); + }); + + container.on('unselect', function () { + self._positionDropdown(); + self._resizeDropdown(); + }); + + this._containerResultsHandlersBound = true; + }; + + AttachBody.prototype._attachPositioningHandler = + function (decorated, container) { + var self = this; + + var scrollEvent = 'scroll.select2.' + container.id; + var resizeEvent = 'resize.select2.' + container.id; + var orientationEvent = 'orientationchange.select2.' + container.id; + + var $watchers = this.$container.parents().filter(Utils.hasScroll); + $watchers.each(function () { + Utils.StoreData(this, 'select2-scroll-position', { + x: $(this).scrollLeft(), + y: $(this).scrollTop() + }); + }); + + $watchers.on(scrollEvent, function (ev) { + var position = Utils.GetData(this, 'select2-scroll-position'); + $(this).scrollTop(position.y); + }); + + $(window).on(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent, + function (e) { + self._positionDropdown(); + self._resizeDropdown(); + }); + }; + + AttachBody.prototype._detachPositioningHandler = + function (decorated, container) { + var scrollEvent = 'scroll.select2.' + container.id; + var resizeEvent = 'resize.select2.' + container.id; + var orientationEvent = 'orientationchange.select2.' + container.id; + + var $watchers = this.$container.parents().filter(Utils.hasScroll); + $watchers.off(scrollEvent); + + $(window).off(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent); + }; + + AttachBody.prototype._positionDropdown = function () { + var $window = $(window); + + var isCurrentlyAbove = this.$dropdown.hasClass('select2-dropdown--above'); + var isCurrentlyBelow = this.$dropdown.hasClass('select2-dropdown--below'); + + var newDirection = null; + + var offset = this.$container.offset(); + + offset.bottom = offset.top + this.$container.outerHeight(false); + + var container = { + height: this.$container.outerHeight(false) + }; + + container.top = offset.top; + container.bottom = offset.top + container.height; + + var dropdown = { + height: this.$dropdown.outerHeight(false) + }; + + var viewport = { + top: $window.scrollTop(), + bottom: $window.scrollTop() + $window.height() + }; + + var enoughRoomAbove = viewport.top < (offset.top - dropdown.height); + var enoughRoomBelow = viewport.bottom > (offset.bottom + dropdown.height); + + var css = { + left: offset.left, + top: container.bottom + }; + + // Determine what the parent element is to use for calculating the offset + var $offsetParent = this.$dropdownParent; + + // For statically positioned elements, we need to get the element + // that is determining the offset + if ($offsetParent.css('position') === 'static') { + $offsetParent = $offsetParent.offsetParent(); + } + + var parentOffset = $offsetParent.offset(); + + css.top -= parentOffset.top; + css.left -= parentOffset.left; + + if (!isCurrentlyAbove && !isCurrentlyBelow) { + newDirection = 'below'; + } + + if (!enoughRoomBelow && enoughRoomAbove && !isCurrentlyAbove) { + newDirection = 'above'; + } else if (!enoughRoomAbove && enoughRoomBelow && isCurrentlyAbove) { + newDirection = 'below'; + } + + if (newDirection == 'above' || + (isCurrentlyAbove && newDirection !== 'below')) { + css.top = container.top - parentOffset.top - dropdown.height; + } + + if (newDirection != null) { + this.$dropdown + .removeClass('select2-dropdown--below select2-dropdown--above') + .addClass('select2-dropdown--' + newDirection); + this.$container + .removeClass('select2-container--below select2-container--above') + .addClass('select2-container--' + newDirection); + } + + this.$dropdownContainer.css(css); + }; + + AttachBody.prototype._resizeDropdown = function () { + var css = { + width: this.$container.outerWidth(false) + 'px' + }; + + if (this.options.get('dropdownAutoWidth')) { + css.minWidth = css.width; + css.position = 'relative'; + css.width = 'auto'; + } + + this.$dropdown.css(css); + }; + + AttachBody.prototype._showDropdown = function (decorated) { + this.$dropdownContainer.appendTo(this.$dropdownParent); + + this._positionDropdown(); + this._resizeDropdown(); + }; + + return AttachBody; +}); + +S2.define('select2/dropdown/minimumResultsForSearch',[ + +], function () { + function countResults (data) { + var count = 0; + + for (var d = 0; d < data.length; d++) { + var item = data[d]; + + if (item.children) { + count += countResults(item.children); + } else { + count++; + } + } + + return count; + } + + function MinimumResultsForSearch (decorated, $element, options, dataAdapter) { + this.minimumResultsForSearch = options.get('minimumResultsForSearch'); + + if (this.minimumResultsForSearch < 0) { + this.minimumResultsForSearch = Infinity; + } + + decorated.call(this, $element, options, dataAdapter); + } + + MinimumResultsForSearch.prototype.showSearch = function (decorated, params) { + if (countResults(params.data.results) < this.minimumResultsForSearch) { + return false; + } + + return decorated.call(this, params); + }; + + return MinimumResultsForSearch; +}); + +S2.define('select2/dropdown/selectOnClose',[ + '../utils' +], function (Utils) { + function SelectOnClose () { } + + SelectOnClose.prototype.bind = function (decorated, container, $container) { + var self = this; + + decorated.call(this, container, $container); + + container.on('close', function (params) { + self._handleSelectOnClose(params); + }); + }; + + SelectOnClose.prototype._handleSelectOnClose = function (_, params) { + if (params && params.originalSelect2Event != null) { + var event = params.originalSelect2Event; + + // Don't select an item if the close event was triggered from a select or + // unselect event + if (event._type === 'select' || event._type === 'unselect') { + return; + } + } + + var $highlightedResults = this.getHighlightedResults(); + + // Only select highlighted results + if ($highlightedResults.length < 1) { + return; + } + + var data = Utils.GetData($highlightedResults[0], 'data'); + + // Don't re-select already selected resulte + if ( + (data.element != null && data.element.selected) || + (data.element == null && data.selected) + ) { + return; + } + + this.trigger('select', { + data: data + }); + }; + + return SelectOnClose; +}); + +S2.define('select2/dropdown/closeOnSelect',[ + +], function () { + function CloseOnSelect () { } + + CloseOnSelect.prototype.bind = function (decorated, container, $container) { + var self = this; + + decorated.call(this, container, $container); + + container.on('select', function (evt) { + self._selectTriggered(evt); + }); + + container.on('unselect', function (evt) { + self._selectTriggered(evt); + }); + }; + + CloseOnSelect.prototype._selectTriggered = function (_, evt) { + var originalEvent = evt.originalEvent; + + // Don't close if the control key is being held + if (originalEvent && (originalEvent.ctrlKey || originalEvent.metaKey)) { + return; + } + + this.trigger('close', { + originalEvent: originalEvent, + originalSelect2Event: evt + }); + }; + + return CloseOnSelect; +}); + +S2.define('select2/i18n/en',[],function () { + // English + return { + errorLoading: function () { + return 'The results could not be loaded.'; + }, + inputTooLong: function (args) { + var overChars = args.input.length - args.maximum; + + var message = 'Please delete ' + overChars + ' character'; + + if (overChars != 1) { + message += 's'; + } + + return message; + }, + inputTooShort: function (args) { + var remainingChars = args.minimum - args.input.length; + + var message = 'Please enter ' + remainingChars + ' or more characters'; + + return message; + }, + loadingMore: function () { + return 'Loading more results…'; + }, + maximumSelected: function (args) { + var message = 'You can only select ' + args.maximum + ' item'; + + if (args.maximum != 1) { + message += 's'; + } + + return message; + }, + noResults: function () { + return 'No results found'; + }, + searching: function () { + return 'Searching…'; + }, + removeAllItems: function () { + return 'Remove all items'; + } + }; +}); + +S2.define('select2/defaults',[ + 'jquery', + 'require', + + './results', + + './selection/single', + './selection/multiple', + './selection/placeholder', + './selection/allowClear', + './selection/search', + './selection/eventRelay', + + './utils', + './translation', + './diacritics', + + './data/select', + './data/array', + './data/ajax', + './data/tags', + './data/tokenizer', + './data/minimumInputLength', + './data/maximumInputLength', + './data/maximumSelectionLength', + + './dropdown', + './dropdown/search', + './dropdown/hidePlaceholder', + './dropdown/infiniteScroll', + './dropdown/attachBody', + './dropdown/minimumResultsForSearch', + './dropdown/selectOnClose', + './dropdown/closeOnSelect', + + './i18n/en' +], function ($, require, + + ResultsList, + + SingleSelection, MultipleSelection, Placeholder, AllowClear, + SelectionSearch, EventRelay, + + Utils, Translation, DIACRITICS, + + SelectData, ArrayData, AjaxData, Tags, Tokenizer, + MinimumInputLength, MaximumInputLength, MaximumSelectionLength, + + Dropdown, DropdownSearch, HidePlaceholder, InfiniteScroll, + AttachBody, MinimumResultsForSearch, SelectOnClose, CloseOnSelect, + + EnglishTranslation) { + function Defaults () { + this.reset(); + } + + Defaults.prototype.apply = function (options) { + options = $.extend(true, {}, this.defaults, options); + + if (options.dataAdapter == null) { + if (options.ajax != null) { + options.dataAdapter = AjaxData; + } else if (options.data != null) { + options.dataAdapter = ArrayData; + } else { + options.dataAdapter = SelectData; + } + + if (options.minimumInputLength > 0) { + options.dataAdapter = Utils.Decorate( + options.dataAdapter, + MinimumInputLength + ); + } + + if (options.maximumInputLength > 0) { + options.dataAdapter = Utils.Decorate( + options.dataAdapter, + MaximumInputLength + ); + } + + if (options.maximumSelectionLength > 0) { + options.dataAdapter = Utils.Decorate( + options.dataAdapter, + MaximumSelectionLength + ); + } + + if (options.tags) { + options.dataAdapter = Utils.Decorate(options.dataAdapter, Tags); + } + + if (options.tokenSeparators != null || options.tokenizer != null) { + options.dataAdapter = Utils.Decorate( + options.dataAdapter, + Tokenizer + ); + } + + if (options.query != null) { + var Query = require(options.amdBase + 'compat/query'); + + options.dataAdapter = Utils.Decorate( + options.dataAdapter, + Query + ); + } + + if (options.initSelection != null) { + var InitSelection = require(options.amdBase + 'compat/initSelection'); + + options.dataAdapter = Utils.Decorate( + options.dataAdapter, + InitSelection + ); + } + } + + if (options.resultsAdapter == null) { + options.resultsAdapter = ResultsList; + + if (options.ajax != null) { + options.resultsAdapter = Utils.Decorate( + options.resultsAdapter, + InfiniteScroll + ); + } + + if (options.placeholder != null) { + options.resultsAdapter = Utils.Decorate( + options.resultsAdapter, + HidePlaceholder + ); + } + + if (options.selectOnClose) { + options.resultsAdapter = Utils.Decorate( + options.resultsAdapter, + SelectOnClose + ); + } + } + + if (options.dropdownAdapter == null) { + if (options.multiple) { + options.dropdownAdapter = Dropdown; + } else { + var SearchableDropdown = Utils.Decorate(Dropdown, DropdownSearch); + + options.dropdownAdapter = SearchableDropdown; + } + + if (options.minimumResultsForSearch !== 0) { + options.dropdownAdapter = Utils.Decorate( + options.dropdownAdapter, + MinimumResultsForSearch + ); + } + + if (options.closeOnSelect) { + options.dropdownAdapter = Utils.Decorate( + options.dropdownAdapter, + CloseOnSelect + ); + } + + if ( + options.dropdownCssClass != null || + options.dropdownCss != null || + options.adaptDropdownCssClass != null + ) { + var DropdownCSS = require(options.amdBase + 'compat/dropdownCss'); + + options.dropdownAdapter = Utils.Decorate( + options.dropdownAdapter, + DropdownCSS + ); + } + + options.dropdownAdapter = Utils.Decorate( + options.dropdownAdapter, + AttachBody + ); + } + + if (options.selectionAdapter == null) { + if (options.multiple) { + options.selectionAdapter = MultipleSelection; + } else { + options.selectionAdapter = SingleSelection; + } + + // Add the placeholder mixin if a placeholder was specified + if (options.placeholder != null) { + options.selectionAdapter = Utils.Decorate( + options.selectionAdapter, + Placeholder + ); + } + + if (options.allowClear) { + options.selectionAdapter = Utils.Decorate( + options.selectionAdapter, + AllowClear + ); + } + + if (options.multiple) { + options.selectionAdapter = Utils.Decorate( + options.selectionAdapter, + SelectionSearch + ); + } + + if ( + options.containerCssClass != null || + options.containerCss != null || + options.adaptContainerCssClass != null + ) { + var ContainerCSS = require(options.amdBase + 'compat/containerCss'); + + options.selectionAdapter = Utils.Decorate( + options.selectionAdapter, + ContainerCSS + ); + } + + options.selectionAdapter = Utils.Decorate( + options.selectionAdapter, + EventRelay + ); + } + + // If the defaults were not previously applied from an element, it is + // possible for the language option to have not been resolved + options.language = this._resolveLanguage(options.language); + + // Always fall back to English since it will always be complete + options.language.push('en'); + + var uniqueLanguages = []; + + for (var l = 0; l < options.language.length; l++) { + var language = options.language[l]; + + if (uniqueLanguages.indexOf(language) === -1) { + uniqueLanguages.push(language); + } + } + + options.language = uniqueLanguages; + + options.translations = this._processTranslations( + options.language, + options.debug + ); + + return options; + }; + + Defaults.prototype.reset = function () { + function stripDiacritics (text) { + // Used 'uni range + named function' from http://jsperf.com/diacritics/18 + function match(a) { + return DIACRITICS[a] || a; + } + + return text.replace(/[^\u0000-\u007E]/g, match); + } + + function matcher (params, data) { + // Always return the object if there is nothing to compare + if ($.trim(params.term) === '') { + return data; + } + + // Do a recursive check for options with children + if (data.children && data.children.length > 0) { + // Clone the data object if there are children + // This is required as we modify the object to remove any non-matches + var match = $.extend(true, {}, data); + + // Check each child of the option + for (var c = data.children.length - 1; c >= 0; c--) { + var child = data.children[c]; + + var matches = matcher(params, child); + + // If there wasn't a match, remove the object in the array + if (matches == null) { + match.children.splice(c, 1); + } + } + + // If any children matched, return the new object + if (match.children.length > 0) { + return match; + } + + // If there were no matching children, check just the plain object + return matcher(params, match); + } + + var original = stripDiacritics(data.text).toUpperCase(); + var term = stripDiacritics(params.term).toUpperCase(); + + // Check if the text contains the term + if (original.indexOf(term) > -1) { + return data; + } + + // If it doesn't contain the term, don't return anything + return null; + } + + this.defaults = { + amdBase: './', + amdLanguageBase: './i18n/', + closeOnSelect: true, + debug: false, + dropdownAutoWidth: false, + escapeMarkup: Utils.escapeMarkup, + language: {}, + matcher: matcher, + minimumInputLength: 0, + maximumInputLength: 0, + maximumSelectionLength: 0, + minimumResultsForSearch: 0, + selectOnClose: false, + scrollAfterSelect: false, + sorter: function (data) { + return data; + }, + templateResult: function (result) { + return result.text; + }, + templateSelection: function (selection) { + return selection.text; + }, + theme: 'default', + width: 'resolve' + }; + }; + + Defaults.prototype.applyFromElement = function (options, $element) { + var optionLanguage = options.language; + var defaultLanguage = this.defaults.language; + var elementLanguage = $element.prop('lang'); + var parentLanguage = $element.closest('[lang]').prop('lang'); + + var languages = Array.prototype.concat.call( + this._resolveLanguage(elementLanguage), + this._resolveLanguage(optionLanguage), + this._resolveLanguage(defaultLanguage), + this._resolveLanguage(parentLanguage) + ); + + options.language = languages; + + return options; + }; + + Defaults.prototype._resolveLanguage = function (language) { + if (!language) { + return []; + } + + if ($.isEmptyObject(language)) { + return []; + } + + if ($.isPlainObject(language)) { + return [language]; + } + + var languages; + + if (!$.isArray(language)) { + languages = [language]; + } else { + languages = language; + } + + var resolvedLanguages = []; + + for (var l = 0; l < languages.length; l++) { + resolvedLanguages.push(languages[l]); + + if (typeof languages[l] === 'string' && languages[l].indexOf('-') > 0) { + // Extract the region information if it is included + var languageParts = languages[l].split('-'); + var baseLanguage = languageParts[0]; + + resolvedLanguages.push(baseLanguage); + } + } + + return resolvedLanguages; + }; + + Defaults.prototype._processTranslations = function (languages, debug) { + var translations = new Translation(); + + for (var l = 0; l < languages.length; l++) { + var languageData = new Translation(); + + var language = languages[l]; + + if (typeof language === 'string') { + try { + // Try to load it with the original name + languageData = Translation.loadPath(language); + } catch (e) { + try { + // If we couldn't load it, check if it wasn't the full path + language = this.defaults.amdLanguageBase + language; + languageData = Translation.loadPath(language); + } catch (ex) { + // The translation could not be loaded at all. Sometimes this is + // because of a configuration problem, other times this can be + // because of how Select2 helps load all possible translation files + if (debug && window.console && console.warn) { + console.warn( + 'Select2: The language file for "' + language + '" could ' + + 'not be automatically loaded. A fallback will be used instead.' + ); + } + } + } + } else if ($.isPlainObject(language)) { + languageData = new Translation(language); + } else { + languageData = language; + } + + translations.extend(languageData); + } + + return translations; + }; + + Defaults.prototype.set = function (key, value) { + var camelKey = $.camelCase(key); + + var data = {}; + data[camelKey] = value; + + var convertedData = Utils._convertData(data); + + $.extend(true, this.defaults, convertedData); + }; + + var defaults = new Defaults(); + + return defaults; +}); + +S2.define('select2/options',[ + 'require', + 'jquery', + './defaults', + './utils' +], function (require, $, Defaults, Utils) { + function Options (options, $element) { + this.options = options; + + if ($element != null) { + this.fromElement($element); + } + + if ($element != null) { + this.options = Defaults.applyFromElement(this.options, $element); + } + + this.options = Defaults.apply(this.options); + + if ($element && $element.is('input')) { + var InputCompat = require(this.get('amdBase') + 'compat/inputData'); + + this.options.dataAdapter = Utils.Decorate( + this.options.dataAdapter, + InputCompat + ); + } + } + + Options.prototype.fromElement = function ($e) { + var excludedData = ['select2']; + + if (this.options.multiple == null) { + this.options.multiple = $e.prop('multiple'); + } + + if (this.options.disabled == null) { + this.options.disabled = $e.prop('disabled'); + } + + if (this.options.dir == null) { + if ($e.prop('dir')) { + this.options.dir = $e.prop('dir'); + } else if ($e.closest('[dir]').prop('dir')) { + this.options.dir = $e.closest('[dir]').prop('dir'); + } else { + this.options.dir = 'ltr'; + } + } + + $e.prop('disabled', this.options.disabled); + $e.prop('multiple', this.options.multiple); + + if (Utils.GetData($e[0], 'select2Tags')) { + if (this.options.debug && window.console && console.warn) { + console.warn( + 'Select2: The `data-select2-tags` attribute has been changed to ' + + 'use the `data-data` and `data-tags="true"` attributes and will be ' + + 'removed in future versions of Select2.' + ); + } + + Utils.StoreData($e[0], 'data', Utils.GetData($e[0], 'select2Tags')); + Utils.StoreData($e[0], 'tags', true); + } + + if (Utils.GetData($e[0], 'ajaxUrl')) { + if (this.options.debug && window.console && console.warn) { + console.warn( + 'Select2: The `data-ajax-url` attribute has been changed to ' + + '`data-ajax--url` and support for the old attribute will be removed' + + ' in future versions of Select2.' + ); + } + + $e.attr('ajax--url', Utils.GetData($e[0], 'ajaxUrl')); + Utils.StoreData($e[0], 'ajax-Url', Utils.GetData($e[0], 'ajaxUrl')); + } + + var dataset = {}; + + function upperCaseLetter(_, letter) { + return letter.toUpperCase(); + } + + // Pre-load all of the attributes which are prefixed with `data-` + for (var attr = 0; attr < $e[0].attributes.length; attr++) { + var attributeName = $e[0].attributes[attr].name; + var prefix = 'data-'; + + if (attributeName.substr(0, prefix.length) == prefix) { + // Get the contents of the attribute after `data-` + var dataName = attributeName.substring(prefix.length); + + // Get the data contents from the consistent source + // This is more than likely the jQuery data helper + var dataValue = Utils.GetData($e[0], dataName); + + // camelCase the attribute name to match the spec + var camelDataName = dataName.replace(/-([a-z])/g, upperCaseLetter); + + // Store the data attribute contents into the dataset since + dataset[camelDataName] = dataValue; + } + } + + // Prefer the element's `dataset` attribute if it exists + // jQuery 1.x does not correctly handle data attributes with multiple dashes + if ($.fn.jquery && $.fn.jquery.substr(0, 2) == '1.' && $e[0].dataset) { + dataset = $.extend(true, {}, $e[0].dataset, dataset); + } + + // Prefer our internal data cache if it exists + var data = $.extend(true, {}, Utils.GetData($e[0]), dataset); + + data = Utils._convertData(data); + + for (var key in data) { + if ($.inArray(key, excludedData) > -1) { + continue; + } + + if ($.isPlainObject(this.options[key])) { + $.extend(this.options[key], data[key]); + } else { + this.options[key] = data[key]; + } + } + + return this; + }; + + Options.prototype.get = function (key) { + return this.options[key]; + }; + + Options.prototype.set = function (key, val) { + this.options[key] = val; + }; + + return Options; +}); + +S2.define('select2/core',[ + 'jquery', + './options', + './utils', + './keys' +], function ($, Options, Utils, KEYS) { + var Select2 = function ($element, options) { + if (Utils.GetData($element[0], 'select2') != null) { + Utils.GetData($element[0], 'select2').destroy(); + } + + this.$element = $element; + + this.id = this._generateId($element); + + options = options || {}; + + this.options = new Options(options, $element); + + Select2.__super__.constructor.call(this); + + // Set up the tabindex + + var tabindex = $element.attr('tabindex') || 0; + Utils.StoreData($element[0], 'old-tabindex', tabindex); + $element.attr('tabindex', '-1'); + + // Set up containers and adapters + + var DataAdapter = this.options.get('dataAdapter'); + this.dataAdapter = new DataAdapter($element, this.options); + + var $container = this.render(); + + this._placeContainer($container); + + var SelectionAdapter = this.options.get('selectionAdapter'); + this.selection = new SelectionAdapter($element, this.options); + this.$selection = this.selection.render(); + + this.selection.position(this.$selection, $container); + + var DropdownAdapter = this.options.get('dropdownAdapter'); + this.dropdown = new DropdownAdapter($element, this.options); + this.$dropdown = this.dropdown.render(); + + this.dropdown.position(this.$dropdown, $container); + + var ResultsAdapter = this.options.get('resultsAdapter'); + this.results = new ResultsAdapter($element, this.options, this.dataAdapter); + this.$results = this.results.render(); + + this.results.position(this.$results, this.$dropdown); + + // Bind events + + var self = this; + + // Bind the container to all of the adapters + this._bindAdapters(); + + // Register any DOM event handlers + this._registerDomEvents(); + + // Register any internal event handlers + this._registerDataEvents(); + this._registerSelectionEvents(); + this._registerDropdownEvents(); + this._registerResultsEvents(); + this._registerEvents(); + + // Set the initial state + this.dataAdapter.current(function (initialData) { + self.trigger('selection:update', { + data: initialData + }); + }); + + // Hide the original select + $element.addClass('select2-hidden-accessible'); + $element.attr('aria-hidden', 'true'); + + // Synchronize any monitored attributes + this._syncAttributes(); + + Utils.StoreData($element[0], 'select2', this); + + // Ensure backwards compatibility with $element.data('select2'). + $element.data('select2', this); + }; + + Utils.Extend(Select2, Utils.Observable); + + Select2.prototype._generateId = function ($element) { + var id = ''; + + if ($element.attr('id') != null) { + id = $element.attr('id'); + } else if ($element.attr('name') != null) { + id = $element.attr('name') + '-' + Utils.generateChars(2); + } else { + id = Utils.generateChars(4); + } + + id = id.replace(/(:|\.|\[|\]|,)/g, ''); + id = 'select2-' + id; + + return id; + }; + + Select2.prototype._placeContainer = function ($container) { + $container.insertAfter(this.$element); + + var width = this._resolveWidth(this.$element, this.options.get('width')); + + if (width != null) { + $container.css('width', width); + } + }; + + Select2.prototype._resolveWidth = function ($element, method) { + var WIDTH = /^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i; + + if (method == 'resolve') { + var styleWidth = this._resolveWidth($element, 'style'); + + if (styleWidth != null) { + return styleWidth; + } + + return this._resolveWidth($element, 'element'); + } + + if (method == 'element') { + var elementWidth = $element.outerWidth(false); + + if (elementWidth <= 0) { + return 'auto'; + } + + return elementWidth + 'px'; + } + + if (method == 'style') { + var style = $element.attr('style'); + + if (typeof(style) !== 'string') { + return null; + } + + var attrs = style.split(';'); + + for (var i = 0, l = attrs.length; i < l; i = i + 1) { + var attr = attrs[i].replace(/\s/g, ''); + var matches = attr.match(WIDTH); + + if (matches !== null && matches.length >= 1) { + return matches[1]; + } + } + + return null; + } + + if (method == 'computedstyle') { + var computedStyle = window.getComputedStyle($element[0]); + + return computedStyle.width; + } + + return method; + }; + + Select2.prototype._bindAdapters = function () { + this.dataAdapter.bind(this, this.$container); + this.selection.bind(this, this.$container); + + this.dropdown.bind(this, this.$container); + this.results.bind(this, this.$container); + }; + + Select2.prototype._registerDomEvents = function () { + var self = this; + + this.$element.on('change.select2', function () { + self.dataAdapter.current(function (data) { + self.trigger('selection:update', { + data: data + }); + }); + }); + + this.$element.on('focus.select2', function (evt) { + self.trigger('focus', evt); + }); + + this._syncA = Utils.bind(this._syncAttributes, this); + this._syncS = Utils.bind(this._syncSubtree, this); + + if (this.$element[0].attachEvent) { + this.$element[0].attachEvent('onpropertychange', this._syncA); + } + + var observer = window.MutationObserver || + window.WebKitMutationObserver || + window.MozMutationObserver + ; + + if (observer != null) { + this._observer = new observer(function (mutations) { + $.each(mutations, self._syncA); + $.each(mutations, self._syncS); + }); + this._observer.observe(this.$element[0], { + attributes: true, + childList: true, + subtree: false + }); + } else if (this.$element[0].addEventListener) { + this.$element[0].addEventListener( + 'DOMAttrModified', + self._syncA, + false + ); + this.$element[0].addEventListener( + 'DOMNodeInserted', + self._syncS, + false + ); + this.$element[0].addEventListener( + 'DOMNodeRemoved', + self._syncS, + false + ); + } + }; + + Select2.prototype._registerDataEvents = function () { + var self = this; + + this.dataAdapter.on('*', function (name, params) { + self.trigger(name, params); + }); + }; + + Select2.prototype._registerSelectionEvents = function () { + var self = this; + var nonRelayEvents = ['toggle', 'focus']; + + this.selection.on('toggle', function () { + self.toggleDropdown(); + }); + + this.selection.on('focus', function (params) { + self.focus(params); + }); + + this.selection.on('*', function (name, params) { + if ($.inArray(name, nonRelayEvents) !== -1) { + return; + } + + self.trigger(name, params); + }); + }; + + Select2.prototype._registerDropdownEvents = function () { + var self = this; + + this.dropdown.on('*', function (name, params) { + self.trigger(name, params); + }); + }; + + Select2.prototype._registerResultsEvents = function () { + var self = this; + + this.results.on('*', function (name, params) { + self.trigger(name, params); + }); + }; + + Select2.prototype._registerEvents = function () { + var self = this; + + this.on('open', function () { + self.$container.addClass('select2-container--open'); + }); + + this.on('close', function () { + self.$container.removeClass('select2-container--open'); + }); + + this.on('enable', function () { + self.$container.removeClass('select2-container--disabled'); + }); + + this.on('disable', function () { + self.$container.addClass('select2-container--disabled'); + }); + + this.on('blur', function () { + self.$container.removeClass('select2-container--focus'); + }); + + this.on('query', function (params) { + if (!self.isOpen()) { + self.trigger('open', {}); + } + + this.dataAdapter.query(params, function (data) { + self.trigger('results:all', { + data: data, + query: params + }); + }); + }); + + this.on('query:append', function (params) { + this.dataAdapter.query(params, function (data) { + self.trigger('results:append', { + data: data, + query: params + }); + }); + }); + + this.on('keypress', function (evt) { + var key = evt.which; + + if (self.isOpen()) { + if (key === KEYS.ESC || key === KEYS.TAB || + (key === KEYS.UP && evt.altKey)) { + self.close(); + + evt.preventDefault(); + } else if (key === KEYS.ENTER) { + self.trigger('results:select', {}); + + evt.preventDefault(); + } else if ((key === KEYS.SPACE && evt.ctrlKey)) { + self.trigger('results:toggle', {}); + + evt.preventDefault(); + } else if (key === KEYS.UP) { + self.trigger('results:previous', {}); + + evt.preventDefault(); + } else if (key === KEYS.DOWN) { + self.trigger('results:next', {}); + + evt.preventDefault(); + } + } else { + if (key === KEYS.ENTER || key === KEYS.SPACE || + (key === KEYS.DOWN && evt.altKey)) { + self.open(); + + evt.preventDefault(); + } + } + }); + }; + + Select2.prototype._syncAttributes = function () { + this.options.set('disabled', this.$element.prop('disabled')); + + if (this.options.get('disabled')) { + if (this.isOpen()) { + this.close(); + } + + this.trigger('disable', {}); + } else { + this.trigger('enable', {}); + } + }; + + Select2.prototype._syncSubtree = function (evt, mutations) { + var changed = false; + var self = this; + + // Ignore any mutation events raised for elements that aren't options or + // optgroups. This handles the case when the select element is destroyed + if ( + evt && evt.target && ( + evt.target.nodeName !== 'OPTION' && evt.target.nodeName !== 'OPTGROUP' + ) + ) { + return; + } + + if (!mutations) { + // If mutation events aren't supported, then we can only assume that the + // change affected the selections + changed = true; + } else if (mutations.addedNodes && mutations.addedNodes.length > 0) { + for (var n = 0; n < mutations.addedNodes.length; n++) { + var node = mutations.addedNodes[n]; + + if (node.selected) { + changed = true; + } + } + } else if (mutations.removedNodes && mutations.removedNodes.length > 0) { + changed = true; + } + + // Only re-pull the data if we think there is a change + if (changed) { + this.dataAdapter.current(function (currentData) { + self.trigger('selection:update', { + data: currentData + }); + }); + } + }; + + /** + * Override the trigger method to automatically trigger pre-events when + * there are events that can be prevented. + */ + Select2.prototype.trigger = function (name, args) { + var actualTrigger = Select2.__super__.trigger; + var preTriggerMap = { + 'open': 'opening', + 'close': 'closing', + 'select': 'selecting', + 'unselect': 'unselecting', + 'clear': 'clearing' + }; + + if (args === undefined) { + args = {}; + } + + if (name in preTriggerMap) { + var preTriggerName = preTriggerMap[name]; + var preTriggerArgs = { + prevented: false, + name: name, + args: args + }; + + actualTrigger.call(this, preTriggerName, preTriggerArgs); + + if (preTriggerArgs.prevented) { + args.prevented = true; + + return; + } + } + + actualTrigger.call(this, name, args); + }; + + Select2.prototype.toggleDropdown = function () { + if (this.options.get('disabled')) { + return; + } + + if (this.isOpen()) { + this.close(); + } else { + this.open(); + } + }; + + Select2.prototype.open = function () { + if (this.isOpen()) { + return; + } + + this.trigger('query', {}); + }; + + Select2.prototype.close = function () { + if (!this.isOpen()) { + return; + } + + this.trigger('close', {}); + }; + + Select2.prototype.isOpen = function () { + return this.$container.hasClass('select2-container--open'); + }; + + Select2.prototype.hasFocus = function () { + return this.$container.hasClass('select2-container--focus'); + }; + + Select2.prototype.focus = function (data) { + // No need to re-trigger focus events if we are already focused + if (this.hasFocus()) { + return; + } + + this.$container.addClass('select2-container--focus'); + this.trigger('focus', {}); + }; + + Select2.prototype.enable = function (args) { + if (this.options.get('debug') && window.console && console.warn) { + console.warn( + 'Select2: The `select2("enable")` method has been deprecated and will' + + ' be removed in later Select2 versions. Use $element.prop("disabled")' + + ' instead.' + ); + } + + if (args == null || args.length === 0) { + args = [true]; + } + + var disabled = !args[0]; + + this.$element.prop('disabled', disabled); + }; + + Select2.prototype.data = function () { + if (this.options.get('debug') && + arguments.length > 0 && window.console && console.warn) { + console.warn( + 'Select2: Data can no longer be set using `select2("data")`. You ' + + 'should consider setting the value instead using `$element.val()`.' + ); + } + + var data = []; + + this.dataAdapter.current(function (currentData) { + data = currentData; + }); + + return data; + }; + + Select2.prototype.val = function (args) { + if (this.options.get('debug') && window.console && console.warn) { + console.warn( + 'Select2: The `select2("val")` method has been deprecated and will be' + + ' removed in later Select2 versions. Use $element.val() instead.' + ); + } + + if (args == null || args.length === 0) { + return this.$element.val(); + } + + var newVal = args[0]; + + if ($.isArray(newVal)) { + newVal = $.map(newVal, function (obj) { + return obj.toString(); + }); + } + + this.$element.val(newVal).trigger('change'); + }; + + Select2.prototype.destroy = function () { + this.$container.remove(); + + if (this.$element[0].detachEvent) { + this.$element[0].detachEvent('onpropertychange', this._syncA); + } + + if (this._observer != null) { + this._observer.disconnect(); + this._observer = null; + } else if (this.$element[0].removeEventListener) { + this.$element[0] + .removeEventListener('DOMAttrModified', this._syncA, false); + this.$element[0] + .removeEventListener('DOMNodeInserted', this._syncS, false); + this.$element[0] + .removeEventListener('DOMNodeRemoved', this._syncS, false); + } + + this._syncA = null; + this._syncS = null; + + this.$element.off('.select2'); + this.$element.attr('tabindex', + Utils.GetData(this.$element[0], 'old-tabindex')); + + this.$element.removeClass('select2-hidden-accessible'); + this.$element.attr('aria-hidden', 'false'); + Utils.RemoveData(this.$element[0]); + this.$element.removeData('select2'); + + this.dataAdapter.destroy(); + this.selection.destroy(); + this.dropdown.destroy(); + this.results.destroy(); + + this.dataAdapter = null; + this.selection = null; + this.dropdown = null; + this.results = null; + }; + + Select2.prototype.render = function () { + var $container = $( + '' + + '' + + '' + + '' + ); + + $container.attr('dir', this.options.get('dir')); + + this.$container = $container; + + this.$container.addClass('select2-container--' + this.options.get('theme')); + + Utils.StoreData($container[0], 'element', this.$element); + + return $container; + }; + + return Select2; +}); + +S2.define('jquery-mousewheel',[ + 'jquery' +], function ($) { + // Used to shim jQuery.mousewheel for non-full builds. + return $; +}); + +S2.define('jquery.select2',[ + 'jquery', + 'jquery-mousewheel', + + './select2/core', + './select2/defaults', + './select2/utils' +], function ($, _, Select2, Defaults, Utils) { + if ($.fn.select2 == null) { + // All methods that should return the element + var thisMethods = ['open', 'close', 'destroy']; + + $.fn.select2 = function (options) { + options = options || {}; + + if (typeof options === 'object') { + this.each(function () { + var instanceOptions = $.extend(true, {}, options); + + var instance = new Select2($(this), instanceOptions); + }); + + return this; + } else if (typeof options === 'string') { + var ret; + var args = Array.prototype.slice.call(arguments, 1); + + this.each(function () { + var instance = Utils.GetData(this, 'select2'); + + if (instance == null && window.console && console.error) { + console.error( + 'The select2(\'' + options + '\') method was called on an ' + + 'element that is not using Select2.' + ); + } + + ret = instance[options].apply(instance, args); + }); + + // Check if we should be returning `this` + if ($.inArray(options, thisMethods) > -1) { + return this; + } + + return ret; + } else { + throw new Error('Invalid arguments for Select2: ' + options); + } + }; + } + + if ($.fn.select2.defaults == null) { + $.fn.select2.defaults = Defaults; + } + + return Select2; +}); + + // Return the AMD loader configuration so it can be used outside of this file + return { + define: S2.define, + require: S2.require + }; +}()); + + // Autoload the jQuery bindings + // We know that all of the modules exist above this, so we're safe + var select2 = S2.require('jquery.select2'); + + // Hold the AMD module references on the jQuery function that was just loaded + // This allows Select2 to use the internal loader outside of this file, such + // as in the language files. + jQuery.fn.select2.amd = S2; + + // Return the Select2 instance for anyone who is importing it. + return select2; +})); diff --git a/ext/phpbbstudio/ass/adm/style/js/select2.min.js b/ext/phpbbstudio/ass/adm/style/js/select2.min.js new file mode 100644 index 0000000..31db3e2 --- /dev/null +++ b/ext/phpbbstudio/ass/adm/style/js/select2.min.js @@ -0,0 +1,2 @@ +/*! Select2 4.0.10 | https://github.com/select2/select2/blob/master/LICENSE.md */ +!function(n){"function"==typeof define&&define.amd?define(["jquery"],n):"object"==typeof module&&module.exports?module.exports=function(e,t){return void 0===t&&(t="undefined"!=typeof window?require("jquery"):require("jquery")(e)),n(t),t}:n(jQuery)}(function(u){var e=function(){if(u&&u.fn&&u.fn.select2&&u.fn.select2.amd)var e=u.fn.select2.amd;var t,n,r,h,o,s,f,g,m,v,y,_,i,a,w;function b(e,t){return i.call(e,t)}function l(e,t){var n,r,i,o,s,a,l,c,u,d,p,h=t&&t.split("/"),f=y.map,g=f&&f["*"]||{};if(e){for(s=(e=e.split("/")).length-1,y.nodeIdCompat&&w.test(e[s])&&(e[s]=e[s].replace(w,"")),"."===e[0].charAt(0)&&h&&(e=h.slice(0,h.length-1).concat(e)),u=0;u":">",'"':""","'":"'","/":"/"};return"string"!=typeof e?e:String(e).replace(/[&<>"'\/\\]/g,function(e){return t[e]})},i.appendMany=function(e,t){if("1.7"===o.fn.jquery.substr(0,3)){var n=o();o.map(t,function(e){n=n.add(e)}),t=n}e.append(t)},i.__cache={};var n=0;return i.GetUniqueElementId=function(e){var t=e.getAttribute("data-select2-id");return null==t&&(e.id?(t=e.id,e.setAttribute("data-select2-id",t)):(e.setAttribute("data-select2-id",++n),t=n.toString())),t},i.StoreData=function(e,t,n){var r=i.GetUniqueElementId(e);i.__cache[r]||(i.__cache[r]={}),i.__cache[r][t]=n},i.GetData=function(e,t){var n=i.GetUniqueElementId(e);return t?i.__cache[n]&&null!=i.__cache[n][t]?i.__cache[n][t]:o(e).data(t):i.__cache[n]},i.RemoveData=function(e){var t=i.GetUniqueElementId(e);null!=i.__cache[t]&&delete i.__cache[t],e.removeAttribute("data-select2-id")},i}),e.define("select2/results",["jquery","./utils"],function(h,f){function r(e,t,n){this.$element=e,this.data=n,this.options=t,r.__super__.constructor.call(this)}return f.Extend(r,f.Observable),r.prototype.render=function(){var e=h('
              ');return this.options.get("multiple")&&e.attr("aria-multiselectable","true"),this.$results=e},r.prototype.clear=function(){this.$results.empty()},r.prototype.displayMessage=function(e){var t=this.options.get("escapeMarkup");this.clear(),this.hideLoading();var n=h(''),r=this.options.get("translations").get(e.message);n.append(t(r(e.args))),n[0].className+=" select2-results__message",this.$results.append(n)},r.prototype.hideMessages=function(){this.$results.find(".select2-results__message").remove()},r.prototype.append=function(e){this.hideLoading();var t=[];if(null!=e.results&&0!==e.results.length){e.results=this.sort(e.results);for(var n=0;n",{class:"select2-results__options select2-results__options--nested"});p.append(l),s.append(a),s.append(p)}else this.template(e,t);return f.StoreData(t,"data",e),t},r.prototype.bind=function(t,e){var l=this,n=t.id+"-results";this.$results.attr("id",n),t.on("results:all",function(e){l.clear(),l.append(e.data),t.isOpen()&&(l.setClasses(),l.highlightFirstItem())}),t.on("results:append",function(e){l.append(e.data),t.isOpen()&&l.setClasses()}),t.on("query",function(e){l.hideMessages(),l.showLoading(e)}),t.on("select",function(){t.isOpen()&&(l.setClasses(),l.options.get("scrollAfterSelect")&&l.highlightFirstItem())}),t.on("unselect",function(){t.isOpen()&&(l.setClasses(),l.options.get("scrollAfterSelect")&&l.highlightFirstItem())}),t.on("open",function(){l.$results.attr("aria-expanded","true"),l.$results.attr("aria-hidden","false"),l.setClasses(),l.ensureHighlightVisible()}),t.on("close",function(){l.$results.attr("aria-expanded","false"),l.$results.attr("aria-hidden","true"),l.$results.removeAttr("aria-activedescendant")}),t.on("results:toggle",function(){var e=l.getHighlightedResults();0!==e.length&&e.trigger("mouseup")}),t.on("results:select",function(){var e=l.getHighlightedResults();if(0!==e.length){var t=f.GetData(e[0],"data");"true"==e.attr("aria-selected")?l.trigger("close",{}):l.trigger("select",{data:t})}}),t.on("results:previous",function(){var e=l.getHighlightedResults(),t=l.$results.find("[aria-selected]"),n=t.index(e);if(!(n<=0)){var r=n-1;0===e.length&&(r=0);var i=t.eq(r);i.trigger("mouseenter");var o=l.$results.offset().top,s=i.offset().top,a=l.$results.scrollTop()+(s-o);0===r?l.$results.scrollTop(0):s-o<0&&l.$results.scrollTop(a)}}),t.on("results:next",function(){var e=l.getHighlightedResults(),t=l.$results.find("[aria-selected]"),n=t.index(e)+1;if(!(n>=t.length)){var r=t.eq(n);r.trigger("mouseenter");var i=l.$results.offset().top+l.$results.outerHeight(!1),o=r.offset().top+r.outerHeight(!1),s=l.$results.scrollTop()+o-i;0===n?l.$results.scrollTop(0):ithis.$results.outerHeight()||o<0)&&this.$results.scrollTop(i)}},r.prototype.template=function(e,t){var n=this.options.get("templateResult"),r=this.options.get("escapeMarkup"),i=n(e,t);null==i?t.style.display="none":"string"==typeof i?t.innerHTML=r(i):h(t).append(i)},r}),e.define("select2/keys",[],function(){return{BACKSPACE:8,TAB:9,ENTER:13,SHIFT:16,CTRL:17,ALT:18,ESC:27,SPACE:32,PAGE_UP:33,PAGE_DOWN:34,END:35,HOME:36,LEFT:37,UP:38,RIGHT:39,DOWN:40,DELETE:46}}),e.define("select2/selection/base",["jquery","../utils","../keys"],function(n,r,i){function o(e,t){this.$element=e,this.options=t,o.__super__.constructor.call(this)}return r.Extend(o,r.Observable),o.prototype.render=function(){var e=n('');return this._tabindex=0,null!=r.GetData(this.$element[0],"old-tabindex")?this._tabindex=r.GetData(this.$element[0],"old-tabindex"):null!=this.$element.attr("tabindex")&&(this._tabindex=this.$element.attr("tabindex")),e.attr("title",this.$element.attr("title")),e.attr("tabindex",this._tabindex),e.attr("aria-disabled","false"),this.$selection=e},o.prototype.bind=function(e,t){var n=this,r=e.id+"-results";this.container=e,this.$selection.on("focus",function(e){n.trigger("focus",e)}),this.$selection.on("blur",function(e){n._handleBlur(e)}),this.$selection.on("keydown",function(e){n.trigger("keypress",e),e.which===i.SPACE&&e.preventDefault()}),e.on("results:focus",function(e){n.$selection.attr("aria-activedescendant",e.data._resultId)}),e.on("selection:update",function(e){n.update(e.data)}),e.on("open",function(){n.$selection.attr("aria-expanded","true"),n.$selection.attr("aria-owns",r),n._attachCloseHandler(e)}),e.on("close",function(){n.$selection.attr("aria-expanded","false"),n.$selection.removeAttr("aria-activedescendant"),n.$selection.removeAttr("aria-owns"),n.$selection.trigger("focus"),n._detachCloseHandler(e)}),e.on("enable",function(){n.$selection.attr("tabindex",n._tabindex),n.$selection.attr("aria-disabled","false")}),e.on("disable",function(){n.$selection.attr("tabindex","-1"),n.$selection.attr("aria-disabled","true")})},o.prototype._handleBlur=function(e){var t=this;window.setTimeout(function(){document.activeElement==t.$selection[0]||n.contains(t.$selection[0],document.activeElement)||t.trigger("blur",e)},1)},o.prototype._attachCloseHandler=function(e){n(document.body).on("mousedown.select2."+e.id,function(e){var t=n(e.target).closest(".select2");n(".select2.select2-container--open").each(function(){this!=t[0]&&r.GetData(this,"element").select2("close")})})},o.prototype._detachCloseHandler=function(e){n(document.body).off("mousedown.select2."+e.id)},o.prototype.position=function(e,t){t.find(".selection").append(e)},o.prototype.destroy=function(){this._detachCloseHandler(this.container)},o.prototype.update=function(e){throw new Error("The `update` method must be defined in child classes.")},o}),e.define("select2/selection/single",["jquery","./base","../utils","../keys"],function(e,t,n,r){function i(){i.__super__.constructor.apply(this,arguments)}return n.Extend(i,t),i.prototype.render=function(){var e=i.__super__.render.call(this);return e.addClass("select2-selection--single"),e.html(''),e},i.prototype.bind=function(t,e){var n=this;i.__super__.bind.apply(this,arguments);var r=t.id+"-container";this.$selection.find(".select2-selection__rendered").attr("id",r).attr("role","textbox").attr("aria-readonly","true"),this.$selection.attr("aria-labelledby",r),this.$selection.on("mousedown",function(e){1===e.which&&n.trigger("toggle",{originalEvent:e})}),this.$selection.on("focus",function(e){}),this.$selection.on("blur",function(e){}),t.on("focus",function(e){t.isOpen()||n.$selection.trigger("focus")})},i.prototype.clear=function(){var e=this.$selection.find(".select2-selection__rendered");e.empty(),e.removeAttr("title")},i.prototype.display=function(e,t){var n=this.options.get("templateSelection");return this.options.get("escapeMarkup")(n(e,t))},i.prototype.selectionContainer=function(){return e("")},i.prototype.update=function(e){if(0!==e.length){var t=e[0],n=this.$selection.find(".select2-selection__rendered"),r=this.display(t,n);n.empty().append(r);var i=t.title||t.text;i?n.attr("title",i):n.removeAttr("title")}else this.clear()},i}),e.define("select2/selection/multiple",["jquery","./base","../utils"],function(i,e,l){function n(e,t){n.__super__.constructor.apply(this,arguments)}return l.Extend(n,e),n.prototype.render=function(){var e=n.__super__.render.call(this);return e.addClass("select2-selection--multiple"),e.html('
                '),e},n.prototype.bind=function(e,t){var r=this;n.__super__.bind.apply(this,arguments),this.$selection.on("click",function(e){r.trigger("toggle",{originalEvent:e})}),this.$selection.on("click",".select2-selection__choice__remove",function(e){if(!r.options.get("disabled")){var t=i(this).parent(),n=l.GetData(t[0],"data");r.trigger("unselect",{originalEvent:e,data:n})}})},n.prototype.clear=function(){var e=this.$selection.find(".select2-selection__rendered");e.empty(),e.removeAttr("title")},n.prototype.display=function(e,t){var n=this.options.get("templateSelection");return this.options.get("escapeMarkup")(n(e,t))},n.prototype.selectionContainer=function(){return i('
              • ×
              • ')},n.prototype.update=function(e){if(this.clear(),0!==e.length){for(var t=[],n=0;n×');a.StoreData(r[0],"data",t),this.$selection.find(".select2-selection__rendered").prepend(r)}},e}),e.define("select2/selection/search",["jquery","../utils","../keys"],function(r,a,l){function e(e,t,n){e.call(this,t,n)}return e.prototype.render=function(e){var t=r('');this.$searchContainer=t,this.$search=t.find("input");var n=e.call(this);return this._transferTabIndex(),n},e.prototype.bind=function(e,t,n){var r=this,i=t.id+"-results";e.call(this,t,n),t.on("open",function(){r.$search.attr("aria-controls",i),r.$search.trigger("focus")}),t.on("close",function(){r.$search.val(""),r.$search.removeAttr("aria-controls"),r.$search.removeAttr("aria-activedescendant"),r.$search.trigger("focus")}),t.on("enable",function(){r.$search.prop("disabled",!1),r._transferTabIndex()}),t.on("disable",function(){r.$search.prop("disabled",!0)}),t.on("focus",function(e){r.$search.trigger("focus")}),t.on("results:focus",function(e){e.data._resultId?r.$search.attr("aria-activedescendant",e.data._resultId):r.$search.removeAttr("aria-activedescendant")}),this.$selection.on("focusin",".select2-search--inline",function(e){r.trigger("focus",e)}),this.$selection.on("focusout",".select2-search--inline",function(e){r._handleBlur(e)}),this.$selection.on("keydown",".select2-search--inline",function(e){if(e.stopPropagation(),r.trigger("keypress",e),r._keyUpPrevented=e.isDefaultPrevented(),e.which===l.BACKSPACE&&""===r.$search.val()){var t=r.$searchContainer.prev(".select2-selection__choice");if(0this.maximumInputLength?this.trigger("results:message",{message:"inputTooLong",args:{maximum:this.maximumInputLength,input:t.term,params:t}}):e.call(this,t,n)},e}),e.define("select2/data/maximumSelectionLength",[],function(){function e(e,t,n){this.maximumSelectionLength=n.get("maximumSelectionLength"),e.call(this,t,n)}return e.prototype.bind=function(e,t,n){var r=this;e.call(this,t,n),t.on("select",function(){r._checkIfMaximumSelected()})},e.prototype.query=function(e,t,n){var r=this;this._checkIfMaximumSelected(function(){e.call(r,t,n)})},e.prototype._checkIfMaximumSelected=function(e,n){var r=this;this.current(function(e){var t=null!=e?e.length:0;0=r.maximumSelectionLength?r.trigger("results:message",{message:"maximumSelected",args:{maximum:r.maximumSelectionLength}}):n&&n()})},e}),e.define("select2/dropdown",["jquery","./utils"],function(t,e){function n(e,t){this.$element=e,this.options=t,n.__super__.constructor.call(this)}return e.Extend(n,e.Observable),n.prototype.render=function(){var e=t('');return e.attr("dir",this.options.get("dir")),this.$dropdown=e},n.prototype.bind=function(){},n.prototype.position=function(e,t){},n.prototype.destroy=function(){this.$dropdown.remove()},n}),e.define("select2/dropdown/search",["jquery","../utils"],function(o,e){function t(){}return t.prototype.render=function(e){var t=e.call(this),n=o('');return this.$searchContainer=n,this.$search=n.find("input"),t.prepend(n),t},t.prototype.bind=function(e,t,n){var r=this,i=t.id+"-results";e.call(this,t,n),this.$search.on("keydown",function(e){r.trigger("keypress",e),r._keyUpPrevented=e.isDefaultPrevented()}),this.$search.on("input",function(e){o(this).off("keyup")}),this.$search.on("keyup input",function(e){r.handleSearch(e)}),t.on("open",function(){r.$search.attr("tabindex",0),r.$search.attr("aria-controls",i),r.$search.trigger("focus"),window.setTimeout(function(){r.$search.trigger("focus")},0)}),t.on("close",function(){r.$search.attr("tabindex",-1),r.$search.removeAttr("aria-controls"),r.$search.removeAttr("aria-activedescendant"),r.$search.val(""),r.$search.trigger("blur")}),t.on("focus",function(){t.isOpen()||r.$search.trigger("focus")}),t.on("results:all",function(e){null!=e.query.term&&""!==e.query.term||(r.showSearch(e)?r.$searchContainer.removeClass("select2-search--hide"):r.$searchContainer.addClass("select2-search--hide"))}),t.on("results:focus",function(e){e.data._resultId?r.$search.attr("aria-activedescendant",e.data._resultId):r.$search.removeAttr("aria-activedescendant")})},t.prototype.handleSearch=function(e){if(!this._keyUpPrevented){var t=this.$search.val();this.trigger("query",{term:t})}this._keyUpPrevented=!1},t.prototype.showSearch=function(e,t){return!0},t}),e.define("select2/dropdown/hidePlaceholder",[],function(){function e(e,t,n,r){this.placeholder=this.normalizePlaceholder(n.get("placeholder")),e.call(this,t,n,r)}return e.prototype.append=function(e,t){t.results=this.removePlaceholder(t.results),e.call(this,t)},e.prototype.normalizePlaceholder=function(e,t){return"string"==typeof t&&(t={id:"",text:t}),t},e.prototype.removePlaceholder=function(e,t){for(var n=t.slice(0),r=t.length-1;0<=r;r--){var i=t[r];this.placeholder.id===i.id&&n.splice(r,1)}return n},e}),e.define("select2/dropdown/infiniteScroll",["jquery"],function(n){function e(e,t,n,r){this.lastParams={},e.call(this,t,n,r),this.$loadingMore=this.createLoadingMore(),this.loading=!1}return e.prototype.append=function(e,t){this.$loadingMore.remove(),this.loading=!1,e.call(this,t),this.showLoadingMore(t)&&(this.$results.append(this.$loadingMore),this.loadMoreIfNeeded())},e.prototype.bind=function(e,t,n){var r=this;e.call(this,t,n),t.on("query",function(e){r.lastParams=e,r.loading=!0}),t.on("query:append",function(e){r.lastParams=e,r.loading=!0}),this.$results.on("scroll",this.loadMoreIfNeeded.bind(this))},e.prototype.loadMoreIfNeeded=function(){var e=n.contains(document.documentElement,this.$loadingMore[0]);if(!this.loading&&e){var t=this.$results.offset().top+this.$results.outerHeight(!1);this.$loadingMore.offset().top+this.$loadingMore.outerHeight(!1)<=t+50&&this.loadMore()}},e.prototype.loadMore=function(){this.loading=!0;var e=n.extend({},{page:1},this.lastParams);e.page++,this.trigger("query:append",e)},e.prototype.showLoadingMore=function(e,t){return t.pagination&&t.pagination.more},e.prototype.createLoadingMore=function(){var e=n('
              • '),t=this.options.get("translations").get("loadingMore");return e.html(t(this.lastParams)),e},e}),e.define("select2/dropdown/attachBody",["jquery","../utils"],function(f,a){function e(e,t,n){this.$dropdownParent=f(n.get("dropdownParent")||document.body),e.call(this,t,n)}return e.prototype.bind=function(e,t,n){var r=this;e.call(this,t,n),t.on("open",function(){r._showDropdown(),r._attachPositioningHandler(t),r._bindContainerResultHandlers(t)}),t.on("close",function(){r._hideDropdown(),r._detachPositioningHandler(t)}),this.$dropdownContainer.on("mousedown",function(e){e.stopPropagation()})},e.prototype.destroy=function(e){e.call(this),this.$dropdownContainer.remove()},e.prototype.position=function(e,t,n){t.attr("class",n.attr("class")),t.removeClass("select2"),t.addClass("select2-container--open"),t.css({position:"absolute",top:-999999}),this.$container=n},e.prototype.render=function(e){var t=f(""),n=e.call(this);return t.append(n),this.$dropdownContainer=t},e.prototype._hideDropdown=function(e){this.$dropdownContainer.detach()},e.prototype._bindContainerResultHandlers=function(e,t){if(!this._containerResultsHandlersBound){var n=this;t.on("results:all",function(){n._positionDropdown(),n._resizeDropdown()}),t.on("results:append",function(){n._positionDropdown(),n._resizeDropdown()}),t.on("results:message",function(){n._positionDropdown(),n._resizeDropdown()}),t.on("select",function(){n._positionDropdown(),n._resizeDropdown()}),t.on("unselect",function(){n._positionDropdown(),n._resizeDropdown()}),this._containerResultsHandlersBound=!0}},e.prototype._attachPositioningHandler=function(e,t){var n=this,r="scroll.select2."+t.id,i="resize.select2."+t.id,o="orientationchange.select2."+t.id,s=this.$container.parents().filter(a.hasScroll);s.each(function(){a.StoreData(this,"select2-scroll-position",{x:f(this).scrollLeft(),y:f(this).scrollTop()})}),s.on(r,function(e){var t=a.GetData(this,"select2-scroll-position");f(this).scrollTop(t.y)}),f(window).on(r+" "+i+" "+o,function(e){n._positionDropdown(),n._resizeDropdown()})},e.prototype._detachPositioningHandler=function(e,t){var n="scroll.select2."+t.id,r="resize.select2."+t.id,i="orientationchange.select2."+t.id;this.$container.parents().filter(a.hasScroll).off(n),f(window).off(n+" "+r+" "+i)},e.prototype._positionDropdown=function(){var e=f(window),t=this.$dropdown.hasClass("select2-dropdown--above"),n=this.$dropdown.hasClass("select2-dropdown--below"),r=null,i=this.$container.offset();i.bottom=i.top+this.$container.outerHeight(!1);var o={height:this.$container.outerHeight(!1)};o.top=i.top,o.bottom=i.top+o.height;var s=this.$dropdown.outerHeight(!1),a=e.scrollTop(),l=e.scrollTop()+e.height(),c=ai.bottom+s,d={left:i.left,top:o.bottom},p=this.$dropdownParent;"static"===p.css("position")&&(p=p.offsetParent());var h=p.offset();d.top-=h.top,d.left-=h.left,t||n||(r="below"),u||!c||t?!c&&u&&t&&(r="below"):r="above",("above"==r||t&&"below"!==r)&&(d.top=o.top-h.top-s),null!=r&&(this.$dropdown.removeClass("select2-dropdown--below select2-dropdown--above").addClass("select2-dropdown--"+r),this.$container.removeClass("select2-container--below select2-container--above").addClass("select2-container--"+r)),this.$dropdownContainer.css(d)},e.prototype._resizeDropdown=function(){var e={width:this.$container.outerWidth(!1)+"px"};this.options.get("dropdownAutoWidth")&&(e.minWidth=e.width,e.position="relative",e.width="auto"),this.$dropdown.css(e)},e.prototype._showDropdown=function(e){this.$dropdownContainer.appendTo(this.$dropdownParent),this._positionDropdown(),this._resizeDropdown()},e}),e.define("select2/dropdown/minimumResultsForSearch",[],function(){function e(e,t,n,r){this.minimumResultsForSearch=n.get("minimumResultsForSearch"),this.minimumResultsForSearch<0&&(this.minimumResultsForSearch=1/0),e.call(this,t,n,r)}return e.prototype.showSearch=function(e,t){return!(function e(t){for(var n=0,r=0;r');return e.attr("dir",this.options.get("dir")),this.$container=e,this.$container.addClass("select2-container--"+this.options.get("theme")),u.StoreData(e[0],"element",this.$element),e},d}),e.define("jquery-mousewheel",["jquery"],function(e){return e}),e.define("jquery.select2",["jquery","jquery-mousewheel","./select2/core","./select2/defaults","./select2/utils"],function(i,e,o,t,s){if(null==i.fn.select2){var a=["open","close","destroy"];i.fn.select2=function(t){if("object"==typeof(t=t||{}))return this.each(function(){var e=i.extend(!0,{},t);new o(i(this),e)}),this;if("string"!=typeof t)throw new Error("Invalid arguments for Select2: "+t);var n,r=Array.prototype.slice.call(arguments,1);return this.each(function(){var e=s.GetData(this,"select2");null==e&&window.console&&console.error&&console.error("The select2('"+t+"') method was called on an element that is not using Select2."),n=e[t].apply(e,r)}),-1=5.5.0", + "composer/installers": "~1.0" + }, + "extra": { + "display-name": "phpBB Studio - Advanced Shop System", + "soft-require": { + "phpbb/phpbb": ">=3.2.7,<4.0.0@dev" + }, + "version-check": { + "host": "3d-i.github.io", + "directory": "/site/vchecks", + "filename": "advshop.json" + } + } +} diff --git a/ext/phpbbstudio/ass/config/routing.yml b/ext/phpbbstudio/ass/config/routing.yml new file mode 100644 index 0000000..c44da67 --- /dev/null +++ b/ext/phpbbstudio/ass/config/routing.yml @@ -0,0 +1,54 @@ +phpbbstudio_ass_shop: + path: /aps/shop + defaults: + _controller: phpbbstudio.ass.controller.shop:shop + +phpbbstudio_ass_category: + path: /aps/shop/{category_slug} + defaults: + _controller: phpbbstudio.ass.controller.shop:category + page: 1 + +phpbbstudio_ass_category_pagination: + path: /aps/shop/{category_slug}/page-{page} + defaults: + _controller: phpbbstudio.ass.controller.shop:category + +phpbbstudio_ass_item: + path: /aps/shop/{category_slug}/{item_slug} + defaults: + _controller: phpbbstudio.ass.controller.shop:item + +phpbbstudio_ass_purchase: + path: /aps/shop/purchase/{category_slug}/{item_slug} + defaults: + _controller: phpbbstudio.ass.controller.inventory:purchase + purchase: true + +phpbbstudio_ass_gift: + path: /aps/shop/gift/{category_slug}/{item_slug} + defaults: + _controller: phpbbstudio.ass.controller.inventory:purchase + purchase: false + +phpbbstudio_ass_history: + path: /aps/inventory/history + defaults: + _controller: phpbbstudio.ass.controller.inventory:history + page: 1 + +phpbbstudio_ass_history_pagination: + path: /aps/inventory/history/page-{page} + defaults: + _controller: phpbbstudio.ass.controller.inventory:history + +phpbbstudio_ass_inventory: + path: /aps/inventory/{category_slug}/{item_slug}/{index}/{action} + defaults: + _controller: phpbbstudio.ass.controller.inventory:inventory + category_slug: '' + item_slug: '' + action: '' + index: 1 + requirements: + index: '\d+' diff --git a/ext/phpbbstudio/ass/config/services.yml b/ext/phpbbstudio/ass/config/services.yml new file mode 100644 index 0000000..5f45447 --- /dev/null +++ b/ext/phpbbstudio/ass/config/services.yml @@ -0,0 +1,11 @@ +parameters: + phpbbstudio.aps.extended: "phpBB Studio - Advanced Shop System" + +imports: + - { resource: services_controllers.yml } + - { resource: services_helpers.yml } + - { resource: services_items.yml } + - { resource: services_listeners.yml } + - { resource: services_notifications.yml } + - { resource: services_objects.yml } + - { resource: tables.yml } diff --git a/ext/phpbbstudio/ass/config/services_controllers.yml b/ext/phpbbstudio/ass/config/services_controllers.yml new file mode 100644 index 0000000..9e094cc --- /dev/null +++ b/ext/phpbbstudio/ass/config/services_controllers.yml @@ -0,0 +1,139 @@ +services: + phpbbstudio.ass.controller.acp.files: + class: phpbbstudio\ass\controller\acp_files_controller + arguments: + - '@cache.driver' + - '@config' + - '@phpbbstudio.ass.files' + - '@language' + - '@request' + - '@template' + - '@user' + + phpbbstudio.ass.controller.acp.inventory: + class: phpbbstudio\ass\controller\acp_inventory_controller + arguments: + - '@dbal.conn' + - '@group_helper' + - '@language' + - '@log' + - '@phpbbstudio.ass.notification' + - '@phpbbstudio.ass.operator.category' + - '@phpbbstudio.ass.operator.item' + - '@request' + - '@template' + - '@user' + - '%tables.groups%' + - '%tables.users%' + - '%tables.user_group%' + - '%phpbbstudio.ass.tables.inventory%' + - '%core.root_path%' + - '%core.php_ext%' + + + phpbbstudio.ass.controller.acp.items: + class: phpbbstudio\ass\controller\acp_items_controller + arguments: + - '@cache.driver' + - '@phpbbstudio.ass.items.manager' + - '@language' + - '@log' + - '@phpbbstudio.ass.operator.category' + - '@phpbbstudio.ass.operator.inventory' + - '@phpbbstudio.ass.operator.item' + - '@request' + - '@template' + - '@phpbbstudio.ass.time' + - '@user' + - '%core.adm_relative_path%' + - '%core.root_path%' + - '%core.php_ext%' + + phpbbstudio.ass.controller.acp.logs: + class: phpbbstudio\ass\controller\acp_logs_controller + arguments: + - '@config' + - '@phpbbstudio.ass.items.manager' + - '@language' + - '@phpbbstudio.ass.log' + - '@log' + - '@phpbbstudio.ass.operator.category' + - '@phpbbstudio.ass.operator.item' + - '@pagination' + - '@request' + - '@template' + - '@user' + + phpbbstudio.ass.controller.acp.overview: + class: phpbbstudio\ass\controller\acp_overview_controller + arguments: + - '@config' + - '@config_text' + - '@dbal.conn' + - '@language' + - '@phpbbstudio.ass.operator.item' + - '@text_formatter.parser' + - '@text_formatter.renderer' + - '@request' + - '@template' + - '@user_loader' + - '@text_formatter.utils' + - '%phpbbstudio.ass.tables.categories%' + - '%phpbbstudio.ass.tables.items%' + - '%phpbbstudio.ass.tables.logs%' + + phpbbstudio.ass.controller.acp.settings: + class: phpbbstudio\ass\controller\acp_settings_controller + arguments: + - '@phpbbstudio.aps.functions' + - '@config' + - '@config_text' + - '@language' + - '@log' + - '@text_formatter.parser' + - '@request' + - '@template' + - '@user' + - '@text_formatter.utils' + - '%core.root_path%' + - '%core.php_ext%' + + phpbbstudio.ass.controller.inventory: + class: phpbbstudio\ass\controller\inventory_controller + arguments: + - '@phpbbstudio.aps.distributor' + - '@phpbbstudio.aps.functions' + - '@auth' + - '@config' + - '@phpbbstudio.ass.controller' + - '@controller.helper' + - '@phpbbstudio.ass.items.manager' + - '@language' + - '@phpbbstudio.ass.log' + - '@log' + - '@phpbbstudio.ass.operator.category' + - '@phpbbstudio.ass.operator.inventory' + - '@phpbbstudio.ass.operator.item' + - '@phpbbstudio.ass.notification' + - '@pagination' + - '@request' + - '@phpbbstudio.ass.router' + - '@template' + - '@phpbbstudio.ass.time' + - '@user' + - '@user_loader' + + phpbbstudio.ass.controller.shop: + class: phpbbstudio\ass\controller\shop_controller + arguments: + - '@config' + - '@phpbbstudio.ass.controller' + - '@dbal.conn' + - '@controller.helper' + - '@phpbbstudio.ass.items.manager' + - '@language' + - '@phpbbstudio.ass.operator.category' + - '@phpbbstudio.ass.operator.item' + - '@pagination' + - '@request' + - '@template' diff --git a/ext/phpbbstudio/ass/config/services_helpers.yml b/ext/phpbbstudio/ass/config/services_helpers.yml new file mode 100644 index 0000000..43b4893 --- /dev/null +++ b/ext/phpbbstudio/ass/config/services_helpers.yml @@ -0,0 +1,45 @@ +services: + phpbbstudio.ass.controller: + class: phpbbstudio\ass\helper\controller + arguments: + - '@phpbbstudio.aps.functions' + - '@auth' + - '@config' + - '@config_text' + - '@controller.helper' + - '@language' + - '@phpbbstudio.ass.operator.category' + - '@phpbbstudio.ass.router' + - '@template' + - '@user' + + phpbbstudio.ass.files: + class: phpbbstudio\ass\helper\files + arguments: + - '@cache' + - '@files.factory' + - '@filesystem' + - '%core.root_path%' + + phpbbstudio.ass.log: + class: phpbbstudio\ass\helper\log + arguments: + - '@dbal.conn' + - '@user' + - '%phpbbstudio.ass.tables.categories%' + - '%phpbbstudio.ass.tables.items%' + - '%phpbbstudio.ass.tables.logs%' + - '%tables.users%' + + phpbbstudio.ass.router: + class: phpbbstudio\ass\helper\router + arguments: + - '@controller.helper' + - '%core.root_path%' + - '%core.php_ext%' + + phpbbstudio.ass.time: + class: phpbbstudio\ass\helper\time + arguments: + - '@config' + - '@language' diff --git a/ext/phpbbstudio/ass/config/services_items.yml b/ext/phpbbstudio/ass/config/services_items.yml new file mode 100644 index 0000000..3ef1f45 --- /dev/null +++ b/ext/phpbbstudio/ass/config/services_items.yml @@ -0,0 +1,50 @@ +services: + phpbbstudio.ass.items: + class: phpbb\di\service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: phpbbstudio.ass.item } + + phpbbstudio.ass.items.manager: + class: phpbbstudio\ass\items\manager + arguments: + - '@language' + - '@template' + - '@phpbbstudio.ass.items' + + phpbbstudio.ass.items.base: + class: phpbbstudio\ass\items\type\base + shared: false + abstract: true + arguments: + - '@auth' + - '@config' + - '@dbal.conn' + - '@controller.helper' + - '@language' + - '@log' + - '@request' + - '@template' + - '@user' + - '%core.table_prefix%' + +# --- Items --- # + phpbbstudio.ass.items.points: + class: phpbbstudio\ass\items\type\points + parent: phpbbstudio.ass.items.base + shared: false + calls: + - [set_aps_distributor, ['@phpbbstudio.aps.distributor']] + - [set_aps_functions, ['@phpbbstudio.aps.functions']] + tags: + - { name: phpbbstudio.ass.item } + + phpbbstudio.ass.items.file: + class: phpbbstudio\ass\items\type\file + parent: phpbbstudio.ass.items.base + shared: false + calls: + - [set_files, ['@phpbbstudio.ass.files']] + tags: + - { name: phpbbstudio.ass.item } diff --git a/ext/phpbbstudio/ass/config/services_listeners.yml b/ext/phpbbstudio/ass/config/services_listeners.yml new file mode 100644 index 0000000..5ae63d4 --- /dev/null +++ b/ext/phpbbstudio/ass/config/services_listeners.yml @@ -0,0 +1,30 @@ +services: + phpbbstudio.ass.listener.blocks: + class: phpbbstudio\ass\event\blocks_listener + arguments: + - '@phpbbstudio.ass.operator.blocks' + - '@config' + - '@language' + tags: + - { name: event.listener } + + phpbbstudio.ass.listener.setup: + class: phpbbstudio\ass\event\setup_listener + arguments: + - '@config' + - '@phpbbstudio.aps.functions' + - '@language' + - '@template' + tags: + - { name: event.listener } + + phpbbstudio.ass.listener.exception: + class: phpbbstudio\ass\event\exception_listener + arguments: + - '@config_text' + - '@phpbbstudio.ass.controller' + - '@language' + - '@text_formatter.renderer' + - '@template' + tags: + - { name: event.listener } diff --git a/ext/phpbbstudio/ass/config/services_notifications.yml b/ext/phpbbstudio/ass/config/services_notifications.yml new file mode 100644 index 0000000..9128247 --- /dev/null +++ b/ext/phpbbstudio/ass/config/services_notifications.yml @@ -0,0 +1,29 @@ +services: + phpbbstudio.ass.notification: + class: phpbbstudio\ass\notification\notification + arguments: + - '@config' + - '@notification_manager' + - '@phpbbstudio.ass.operator.item' + - '@user' + + phpbbstudio.ass.notification.type.gift: + class: phpbbstudio\ass\notification\type\gift + shared: false # service MUST not be shared for this to work! + parent: notification.type.base + calls: + - [set_auth, ['@auth']] + - [set_router, ['@phpbbstudio.ass.router']] + - [set_user_loader, ['@user_loader']] + tags: + - { name: notification.type } + + phpbbstudio.ass.notification.type.stock: + class: phpbbstudio\ass\notification\type\stock + shared: false # service MUST not be shared for this to work! + parent: notification.type.base + calls: + - [set_auth, ['@auth']] + - [set_router, ['@phpbbstudio.ass.router']] + tags: + - { name: notification.type } diff --git a/ext/phpbbstudio/ass/config/services_objects.yml b/ext/phpbbstudio/ass/config/services_objects.yml new file mode 100644 index 0000000..c67614e --- /dev/null +++ b/ext/phpbbstudio/ass/config/services_objects.yml @@ -0,0 +1,75 @@ +services: + phpbbstudio.ass.entity.category: + class: phpbbstudio\ass\entity\category + shared: false + arguments: + - '@dbal.conn' + - '@text_formatter.parser' + - '@text_formatter.renderer' + - '@text_formatter.utils' + - '%phpbbstudio.ass.tables.categories%' + + phpbbstudio.ass.entity.item: + class: phpbbstudio\ass\entity\item + shared: false + arguments: + - '@config' + - '@dbal.conn' + - '@text_formatter.parser' + - '@text_formatter.renderer' + - '@phpbbstudio.ass.time' + - '@text_formatter.utils' + - '%phpbbstudio.ass.tables.items%' + + phpbbstudio.ass.operator.blocks: + class: phpbbstudio\ass\operator\blocks + arguments: + - '@phpbbstudio.aps.functions' + - '@auth' + - '@config' + - '@dbal.conn' + - '@group_helper' + - '@phpbbstudio.ass.operator.item' + - '@template' + - '@user_loader' + - '%phpbbstudio.ass.tables.categories%' + - '%phpbbstudio.ass.tables.items%' + - '%phpbbstudio.ass.tables.logs%' + + phpbbstudio.ass.operator.category: + class: phpbbstudio\ass\operator\category + arguments: + - '@auth' + - '@service_container' + - '@dbal.conn' + - '@phpbbstudio.ass.router' + - '%phpbbstudio.ass.tables.categories%' + + phpbbstudio.ass.operator.inventory: + class: phpbbstudio\ass\operator\inventory + arguments: + - '@phpbbstudio.aps.distributor' + - '@phpbbstudio.aps.functions' + - '@config' + - '@dbal.conn' + - '@phpbbstudio.ass.operator.item' + - '@user' + - '%phpbbstudio.ass.tables.inventory%' + - '%phpbbstudio.ass.tables.items%' + - '%tables.users%' + + phpbbstudio.ass.operator.item: + class: phpbbstudio\ass\operator\item + arguments: + - '@phpbbstudio.aps.dbal' + - '@auth' + - '@service_container' + - '@dbal.conn' + - '@phpbbstudio.ass.files' + - '@path_helper' + - '@phpbbstudio.ass.router' + - '@template' + - '@phpbbstudio.ass.time' + - '@user' + - '%phpbbstudio.ass.tables.categories%' + - '%phpbbstudio.ass.tables.items%' diff --git a/ext/phpbbstudio/ass/config/tables.yml b/ext/phpbbstudio/ass/config/tables.yml new file mode 100644 index 0000000..e4c17cf --- /dev/null +++ b/ext/phpbbstudio/ass/config/tables.yml @@ -0,0 +1,5 @@ +parameters: + phpbbstudio.ass.tables.categories: '%core.table_prefix%ass_categories' + phpbbstudio.ass.tables.inventory: '%core.table_prefix%ass_inventory' + phpbbstudio.ass.tables.items: '%core.table_prefix%ass_items' + phpbbstudio.ass.tables.logs: '%core.table_prefix%ass_logs' diff --git a/ext/phpbbstudio/ass/controller/acp_files_controller.php b/ext/phpbbstudio/ass/controller/acp_files_controller.php new file mode 100644 index 0000000..4fd060a --- /dev/null +++ b/ext/phpbbstudio/ass/controller/acp_files_controller.php @@ -0,0 +1,295 @@ +cache = $cache; + $this->config = $config; + $this->files = $files; + $this->language = $language; + $this->request = $request; + $this->template = $template; + $this->user = $user; + } + + /** + * Handle and display the "Files" ACP mode. + * + * @return void + * @access public + */ + public function files() + { + $this->language->add_lang(['ass_acp_files', 'ass_acp_common', 'ass_common'], 'phpbbstudio/ass'); + $this->language->add_lang('posting'); + + $mode = $this->request->variable('m', '', true); + $action = $this->request->variable('action', '', true); + $directory = $this->request->variable('dir', '', true); + $item_file = $this->request->variable('file', '', true); + $item_img = $this->request->variable('image', '', true); + $img_input = $this->request->variable('input', '', true); + + $s_item_img = $this->request->is_set('image'); + $img_value = $item_img ? $item_img : ($s_item_img ? true : ''); + + switch ($mode) + { + case 'images': + case 'files': + $this->files->set_mode($mode); + $json_response = new \phpbb\json_response; + + $form_key = 'ass_files'; + add_form_key($form_key); + + switch ($action) + { + case 'add_dir': + if (!check_form_key($form_key)) + { + trigger_error($this->language->lang('FORM_INVALID') . adm_back_link($this->get_file_action($mode, $directory), E_USER_WARNING)); + } + + $folder = $this->request->variable('folder', '', true); + + $refresh = str_replace('&', '&', $this->get_file_action($mode, $directory)); + + try + { + $this->files->add($directory, $folder); + } + catch (runtime_exception $e) + { + trigger_error($this->language->lang_array($e->getMessage(), array_merge([$this->language->lang('ASS_FOLDER')], $e->get_parameters())) . adm_back_link($refresh), E_USER_WARNING); + } + + if ($this->config['ass_purge_cache']) + { + $this->cache->purge(); + } + + if ($this->request->is_ajax()) + { + $json_response->send(['REFRESH_DATA' => ['url' => $refresh, 'time' => 0]]); + } + + redirect($refresh); + break; + + case 'add_file': + if (!check_form_key($form_key)) + { + trigger_error($this->language->lang('FORM_INVALID') . adm_back_link($this->get_file_action($mode, $directory)), E_USER_WARNING); + } + + $refresh = str_replace('&', '&', $this->get_file_action($mode, $directory)); + + try + { + $this->files->upload($directory, 'file'); + } + catch (runtime_exception $e) + { + trigger_error($this->language->lang_array($e->getMessage(), array_merge([$this->language->lang('ASS_FILENAME')], $e->get_parameters())) . adm_back_link($refresh), E_USER_WARNING); + } + + if ($this->config['ass_purge_cache']) + { + $this->cache->purge(); + } + + redirect($refresh); + break; + + case 'delete_dir': + case 'delete_file': + $type = $action === 'delete_dir' ? 'FOLDER' : 'FILE'; + + if (confirm_box(true)) + { + $this->files->delete($directory); + + if ($this->config['ass_purge_cache']) + { + $this->cache->purge(); + } + + trigger_error("ASS_{$type}_DELETE_SUCCESS"); + } + else + { + confirm_box(false, "ASS_{$type}_DELETE", ''); + } + break; + + case 'select_file': + if (($item_img || $item_file) && !$this->request->is_set('dir')) + { + $directory = pathinfo($item_img, PATHINFO_DIRNAME); + $directory = $directory === '.' ? '' : $directory; + } + + $this->template->assign_vars([ + 'S_FILE_SELECT' => $s_item_img ? $img_input : 'file', + ]); + break; + } + + $files = $this->files->view($directory); + + foreach ($files['folders'] as $folder) + { + $file_time = $this->files->get_file_time($directory, $folder); + + $this->template->assign_block_vars('ass_folders', [ + 'NAME' => $folder, + 'TIME' => $file_time ? $this->user->format_date($file_time) : '', + 'U_DELETE' => $this->get_file_action($mode, ($directory ? $directory . '%2F' : '') . $folder, 'delete_dir'), + 'U_VIEW' => $this->get_file_action($mode, ($directory ? $directory . '%2F' : '') . $folder, $action, $img_value, $item_file, $img_input), + ]); + } + + foreach ($files['files'] as $file) + { + $dir_file = $directory ? $directory . '/' . $file : $file; + + $file_size = $this->files->get_file_size($directory, $file); + $file_time = $this->files->get_file_time($directory, $file); + + $this->template->assign_block_vars('ass_files', [ + 'NAME' => $file, + 'ICON' => $this->files->get_file_icon($file), + 'IMG' => $this->files->get_path($directory, true, $file), + 'SIZE' => $file_size ? get_formatted_filesize($file_size) : '', + 'TIME' => $file_time ? $this->user->format_date($file_time) : '', + 'VALUE' => $dir_file, + 'S_SELECTED' => $s_item_img ? $dir_file === $item_img : $dir_file === $item_file, + 'U_DELETE' => $this->get_file_action($mode, ($directory ? $directory . '%2F' : '') . $file, 'delete_file'), + ]); + } + + $directories = array_filter(explode('/', $directory)); + + $this->template->assign_vars([ + 'DIRECTORIES' => $directories, + + 'S_FILE_MODE' => $mode, + + 'U_ACTION' => $this->get_file_action($mode, '', $action, $img_value, $item_file, $img_input), + 'U_ACTION_FORM' => $this->get_file_action($mode, implode('%2F', $directories), $action), + 'U_BACK' => $this->u_action, + ]); + break; + + default: + $this->template->assign_vars([ + 'ALLOWED_EXTS' => $mode === 'images' ? implode(',', $this->files->get_extensions()) : '', + 'S_FILE_INDEX' => true, + 'U_FILE_FILES' => $this->get_file_action('files'), + 'U_FILE_IMAGES' => $this->get_file_action('images'), + ]); + break; + } + } + + /** + * Get a custom form action for the "files" mode. + * + * @param string $mode The file mode (images|files) + * @param string $directory The file directory + * @param string $action The action + * @param string $image The image name + * @param string $file The file name + * @param string $input The input field name + * @return string The custom form action + * @access protected + */ + protected function get_file_action($mode, $directory = '', $action = '', $image = '', $file = '', $input = '') + { + $mode = $mode ? "&m={$mode}" : ''; + $action = $action ? "&action={$action}" : ''; + $directory = $directory ? "&dir={$directory}" : ''; + $file = $file ? "&file={$file}" : ''; + $input = $input ? "&input={$input}" : ''; + + $image = $image === true ? '&image=' : ($image ? "&image={$image}" : ''); + + return "{$this->u_action}{$mode}{$directory}{$action}{$image}{$file}{$input}"; + } + + /** + * Set custom form action. + * + * @param string $u_action Custom form action + * @return self $this This controller for chaining calls + * @access public + */ + public function set_page_url($u_action) + { + $this->u_action = $u_action; + + return $this; + } +} diff --git a/ext/phpbbstudio/ass/controller/acp_inventory_controller.php b/ext/phpbbstudio/ass/controller/acp_inventory_controller.php new file mode 100644 index 0000000..b1f28bc --- /dev/null +++ b/ext/phpbbstudio/ass/controller/acp_inventory_controller.php @@ -0,0 +1,604 @@ +db = $db; + $this->group_helper = $group_helper; + $this->language = $language; + $this->log = $log; + $this->notification = $notification; + $this->operator_cat = $operator_cat; + $this->operator_item = $operator_item; + $this->request = $request; + $this->template = $template; + $this->user = $user; + + $this->groups_table = $groups_table; + $this->users_table = $users_table; + $this->user_group_table = $user_group_table; + $this->inventory_table = $inventory_table; + + $this->root_path = $root_path; + $this->php_ext = $php_ext; + } + + /** + * Handle and display the "Inventory" ACP mode. + * + * @return void + * @access public + */ + public function inventory() + { + $this->language->add_lang(['ass_acp_common', 'ass_common'], 'phpbbstudio/ass'); + + $type = $this->request->variable('type', '', true); + $submit = $this->request->is_set_post('submit'); + + $errors = []; + + $form_key = 'ass_inventory'; + add_form_key($form_key); + + switch ($type) + { + case 'global'; + $action = $this->request->variable('action', 'add', true); + $item_ids = $this->request->variable('items', [0]); + $group_ids = $this->request->variable('groups', [0]); + $usernames = $this->request->variable('usernames', '', true); + + $items = []; + + /** @var category $category */ + foreach ($this->operator_cat->get_categories() as $category) + { + $this->template->assign_block_vars('categories', array_merge([ + 'S_INACTIVE' => !$category->get_active(), + ], $this->operator_cat->get_variables($category))); + + $items += $category_items = $this->operator_item->get_items($category->get_id()); + + /** @var item $item */ + foreach ($category_items as $item) + { + $this->template->assign_block_vars('categories.items', array_merge([ + 'S_INACTIVE' => !$item->get_active(), + 'S_SELECTED' => in_array($item->get_id(), $item_ids), + ], $this->operator_item->get_variables($item))); + } + } + + if ($submit) + { + if (!check_form_key($form_key)) + { + $errors[] = 'FORM_INVALID'; + } + + if (!in_array($action, ['add', 'delete'])) + { + $errors[] = 'NO_ACTION'; + } + + if (empty($item_ids)) + { + $errors[] = 'ACP_ASS_NO_ITEMS_SELECTED'; + } + + $user_ids = []; + $usernames_array = array_filter(explode("\n", $usernames)); + + if (empty($usernames_array) && empty($group_ids)) + { + $this->language->add_lang('acp/permissions'); + + $errors[] = 'NO_USER_GROUP_SELECTED'; + } + + if (!empty($usernames_array)) + { + $this->get_ids_from_usernames($usernames_array, $user_ids, $errors); + } + + if (!empty($group_ids)) + { + $this->get_ids_from_groups($group_ids, $user_ids); + } + + if (empty($errors) && empty($user_ids)) + { + $errors[] = 'NO_GROUP_MEMBERS'; + } + + if (empty($errors)) + { + $user_ids = array_unique($user_ids); + + $this->update_inventory($action, $user_ids, $item_ids); + + $count_items = count($item_ids); + $count_users = count($user_ids); + + $item_names = []; + + foreach ($item_ids as $item_id) + { + $item_names[] = $items[$item_id]->get_title(); + } + + $l_action = 'ACP_ASS_INVENTORY_' . utf8_strtoupper($action); + + $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, "LOG_{$l_action}", time(), [$count_items, implode(', ', $item_names), $count_users, implode(', ', $usernames_array)]); + + $message = $this->language->lang("{$l_action}_SUCCESS", $count_items); + $message .= '
                » ' . $this->language->lang('ACP_ASS_AMOUNT_ITEMS') . $this->language->lang('COLON') . ' ' . $count_items; + $message .= '
                » ' . $this->language->lang('ACP_ASS_AMOUNT_USERS') . $this->language->lang('COLON') . ' ' . $count_users; + $message .= adm_back_link($this->u_action); + + trigger_error($message); + } + } + + $sql = 'SELECT group_id, group_name, group_type + FROM ' . $this->groups_table . " + WHERE group_name <> 'BOTS' + AND group_name <> 'GUESTS' + ORDER BY group_type DESC, group_name ASC"; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $this->template->assign_block_vars('groups', [ + 'ID' => (int) $row['group_id'], + 'NAME' => $this->group_helper->get_name($row['group_name']), + 'S_SELECTED' => in_array((int) $row['group_id'], $group_ids), + 'S_SPECIAL' => GROUP_SPECIAL === (int) $row['group_type'], + ]); + } + $this->db->sql_freeresult($result); + + $this->template->assign_vars([ + 'USERNAMES' => $usernames, + 'S_ADD' => $action === 'add', + 'U_FIND_USER' => append_sid("{$this->root_path}memberlist.{$this->php_ext}", [ + 'mode' => 'searchuser', + 'form' => 'ass_inventory', + 'field' => 'usernames', + ]), + ]); + break; + + case 'manage': + $action = $this->request->variable('action', '', true); + $username = $this->request->variable('username', '', true); + $user_id = $this->request->variable('u', 0); + $item_ids = $this->request->variable('items', [0]); + + if (empty($username) && empty($user_id)) + { + $this->template->assign_var('U_FIND_USER', append_sid("{$this->root_path}memberlist.{$this->php_ext}", [ + 'mode' => 'searchuser', + 'form' => 'ass_inventory', + 'field' => 'username', + 'select_single' => true, + ])); + } + else + { + if (empty($user_id)) + { + $user_ids = []; + + $this->get_ids_from_usernames([$username], $user_ids, $errors); + + if (empty($user_ids)) + { + trigger_error($this->language->lang('NO_USER') . adm_back_link($this->u_action . "&type={$type}"), E_USER_WARNING); + } + + $user_id = (int) reset($user_ids); + } + + $rowset = []; + + $sql = 'SELECT i.*, u.username as gifter_name, u.user_colour as gifter_colour + FROM ' . $this->inventory_table . ' i + LEFT JOIN ' . $this->users_table . ' u + ON i.gifter_id = u.user_id + WHERE i.user_id = ' . (int) $user_id; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $rowset[(int) $row['item_id']] = $row; + } + $this->db->sql_freeresult($result); + + $inventory = [ + 'categories' => array_column($rowset, 'category_id'), + 'items' => array_keys($rowset), + ]; + + $items = []; + $categories = $this->operator_cat->get_categories(); + + /** @var category $category */ + foreach ($categories as $category) + { + $this->template->assign_block_vars('categories', array_merge([ + 'S_INACTIVE' => !$category->get_active(), + 'S_INVENTORY' => in_array($category->get_id(), $inventory['categories']), + ], $this->operator_cat->get_variables($category))); + + $items += $category_items = $this->operator_item->get_items($category->get_id()); + + /** @var item $item */ + foreach ($category_items as $item) + { + $variables = array_merge([ + 'S_INACTIVE' => !$item->get_active(), + 'S_INVENTORY' => in_array($item->get_id(), $inventory['items']), + 'S_SELECTED' => in_array($item->get_id(), $item_ids), + ], $this->operator_item->get_variables($item)); + + if ($variables['S_INVENTORY']) + { + $row = $rowset[$item->get_id()]; + + $variables = array_merge($variables, [ + 'GIFTER' => $row['gifter_id'] ? get_username_string('full', $row['gifter_id'], $row['gifter_name'], $row['gifter_colour']) : '', + 'PURCHASE_TIME' => $this->user->format_date($row['purchase_time']), + 'USE_TIME' => $row['use_time'] ? $this->user->format_date($row['use_time']) : $this->language->lang('NEVER'), + 'USE_COUNT' => (int) $row['use_count'], + ]); + } + + $this->template->assign_block_vars('categories.items', $variables); + } + } + + $u_back = $this->u_action . "&type={$type}&u={$user_id}"; + + if ($action === 'delete') + { + $item_id = $this->request->variable('iid', 0); + + if (confirm_box(true)) + { + $this->update_inventory($action, [$user_id], [$item_id]); + + if (empty($username)) + { + $username = $this->get_username($user_id); + } + + $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_ACP_ASS_INVENTORY_DELETE_USER', time(), [$items[$item_id]->get_title(), $username]); + + trigger_error($this->language->lang('ASS_DELETE_SUCCESS') . adm_back_link($u_back)); + } + else + { + confirm_box(false, 'ASS_DELETE', ''); + + redirect($u_back); + } + } + + if ($submit) + { + if (!check_form_key($form_key)) + { + $errors[] = 'FORM_INVALID'; + } + + if (empty($item_ids)) + { + $errors[] = 'ACP_ASS_NO_ITEMS_SELECTED'; + } + + if (empty($errors)) + { + $this->update_inventory('add', [$user_id], $item_ids); + + if (empty($username)) + { + $username = $this->get_username($user_id); + } + + $count_items = count($item_ids); + + $item_names = []; + + foreach ($item_ids as $item_id) + { + $item_names[] = $items[$item_id]->get_title(); + } + + $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_ACP_ASS_INVENTORY_ADD_USER', time(), [$count_items, implode(', ', $item_names), $username]); + + trigger_error($this->language->lang('ACP_ASS_INVENTORY_ADD_SUCCESS', $count_items) . adm_back_link($u_back)); + } + } + + $this->template->assign_vars([ + 'ASS_USERNAME' => $username, + + 'U_DELETE' => $this->u_action . "&type={$type}&u={$user_id}&action=delete&iid=", + ]); + } + break; + + default: + $this->template->assign_vars([ + 'U_GLOBAL' => $this->u_action . '&type=global', + 'U_MANAGE' => $this->u_action . '&type=manage', + ]); + break; + } + + $this->template->assign_vars([ + 'ERRORS' => $errors, + + 'S_TYPE' => $type, + + 'U_ACTION' => $this->u_action . ($type ? "&type={$type}" : ''), + 'U_BACK' => $this->u_action, + ]); + } + + /** + * Update users' inventories. + * + * @param string $action The action to perform (add|delete) + * @param array $user_ids The user identifiers + * @param array $item_ids The item identifiers + * @return void + * @access protected + */ + protected function update_inventory($action, array $user_ids, array $item_ids) + { + switch ($action) + { + case 'add': + $owned = []; + $stack = []; + + $sql = 'SELECT item_id, user_id + FROM ' . $this->inventory_table . ' + WHERE ' . $this->db->sql_in_set('item_id', $item_ids) . ' + AND ' . $this->db->sql_in_set('user_id', $user_ids); + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $item_id = (int) $row['item_id']; + $user_id = (int) $row['user_id']; + + $owned[$item_id][] = $user_id; + $stack[$user_id][$item_id][] = $item_id; + } + $this->db->sql_freeresult($result); + + $items = $this->operator_item->get_items_by_id($item_ids); + + foreach ($item_ids as $item_id) + { + /** @var item $item */ + $item = $items[$item_id]; + + $users = !empty($owned[$item_id]) && !$item->get_stack() ? array_diff($user_ids, $owned[$item_id]) : $user_ids; + + foreach ($users as $user_id) + { + $sql = 'INSERT INTO ' . $this->inventory_table . ' ' . $this->db->sql_build_array('INSERT', [ + 'category_id' => $item->get_category(), + 'item_id' => $item->get_id(), + 'user_id' => $user_id, + 'gifter_id' => (int) $this->user->data['user_id'], + 'purchase_time' => time(), + 'purchase_price' => 0.00, + ]); + $this->db->sql_query($sql); + + $index = !empty($stack[$user_id][$item_id]) ? count($stack[$user_id][$item_id]) : 0; + $index = $index + 1; + + $this->notification->gift($item, $user_id, $this->db->sql_nextid(), $index); + } + } + break; + + case 'delete': + $sql = 'DELETE FROM ' . $this->inventory_table . ' + WHERE ' . $this->db->sql_in_set('item_id', $item_ids) . ' + AND ' . $this->db->sql_in_set('user_id', $user_ids); + $this->db->sql_query($sql); + break; + } + } + + /** + * Get user identifiers from usernames. + * + * @param array $usernames The usernames + * @param array $user_ids The user identifiers + * @param array $errors The errors + * @return void + * @access protected + */ + protected function get_ids_from_usernames(array $usernames, array &$user_ids, array &$errors) + { + $usernames_clean = []; + $usernames_found = []; + + foreach ($usernames as $username) + { + $usernames_clean[$username] = utf8_clean_string($username); + } + + $sql = 'SELECT user_id, username_clean + FROM ' . $this->users_table . ' + WHERE ' . $this->db->sql_in_set('username_clean', $usernames_clean); + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $usernames_found[] = $row['username_clean']; + $user_ids[] = (int) $row['user_id']; + } + $this->db->sql_freeresult($result); + + $usernames_not_found = array_diff($usernames_clean, $usernames_found); + + if (!empty($usernames_not_found)) + { + $errors[] = count($usernames_not_found) > 1 ? 'NO_USERS' : 'NO_USER'; + + $not_found = array_intersect($usernames_clean, $usernames_not_found); + $not_found = array_flip($not_found); + + foreach ($not_found as $username) + { + $errors[] = '» ' . $username; + } + } + } + + /** + * Get user identifiers from group identifiers. + * + * @param array $group_ids The group identifiers + * @param array $user_ids The user identifiers + * @return void + * @access protected + */ + protected function get_ids_from_groups(array $group_ids, array &$user_ids) + { + $sql = 'SELECT user_id + FROM ' . $this->user_group_table . ' + WHERE user_pending <> 1 + AND ' . $this->db->sql_in_set('group_id', $group_ids); + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $user_ids[] = (int) $row['user_id']; + } + $this->db->sql_freeresult($result); + } + + /** + * Get a username from user identifier. + * + * @param int $user_id The user identifier + * @return string The username + * @access protected + */ + protected function get_username($user_id) + { + $sql = 'SELECT username + FROM ' . $this->users_table . ' + WHERE user_id = ' . (int) $user_id; + $result = $this->db->sql_query_limit($sql, 1); + $username = $this->db->sql_fetchfield('username'); + $this->db->sql_freeresult($result); + + return (string) $username; + } + + /** + * Set custom form action. + * + * @param string $u_action Custom form action + * @return self $this This controller for chaining calls + * @access public + */ + public function set_page_url($u_action) + { + $this->u_action = $u_action; + + return $this; + } +} diff --git a/ext/phpbbstudio/ass/controller/acp_items_controller.php b/ext/phpbbstudio/ass/controller/acp_items_controller.php new file mode 100644 index 0000000..5336dac --- /dev/null +++ b/ext/phpbbstudio/ass/controller/acp_items_controller.php @@ -0,0 +1,703 @@ +cache = $cache; + $this->items_manager = $items_manager; + $this->language = $language; + $this->log = $log; + $this->operator_cat = $operator_cat; + $this->operator_inv = $operator_inv; + $this->operator_item = $operator_item; + $this->request = $request; + $this->template = $template; + $this->time = $time; + $this->user = $user; + + $this->admin_path = $root_path . $admin_path; + $this->root_path = $root_path; + $this->php_ext = $php_ext; + } + + /** + * Handle and display the "Items" ACP mode. + * + * @return void + * @access public + */ + public function items() + { + $this->language->add_lang(['ass_acp_common', 'ass_common'], 'phpbbstudio/ass'); + + $action = $this->request->variable('action', '', true); + $item_id = $this->request->variable('iid', 0); + $category_id = $this->request->variable('cid', 0); + + $s_items = ($item_id || ($category_id && strpos($action, 'cat_') !== 0)); + + switch ($action) + { + case 'move': + $id = $this->request->variable('id', 0); + $order = $this->request->variable('order', 0) + 1; + + $s_items ? $this->operator_item->move($id, $order) : $this->operator_cat->move($id, $order); + break; + + case 'resolve': + $item = $this->operator_item->get_entity()->load($item_id); + $category = $this->operator_cat->get_entity()->load($item->get_category()); + + if (confirm_box(true)) + { + $item->set_conflict(false) + ->save(); + + $category->set_conflicts($category->get_conflicts() - 1) + ->save(); + + $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_ACP_ASS_ITEM_RESOLVED', time(), [$item->get_title()]); + + if ($this->request->is_ajax()) + { + $json_response = new \phpbb\json_response; + + $json_response->send([ + 'MESSAGE_TITLE' => $this->language->lang('INFORMATION'), + 'MESSAGE_TEXT' => $this->language->lang('ACP_ASS_ITEM_RESOLVE_SUCCESS') + ]); + } + + trigger_error($this->language->lang('ACP_ASS_ITEM_RESOLVE_SUCCESS') . adm_back_link($this->u_action . '&iid=' . $item->get_id())); + } + else + { + confirm_box(false, 'ACP_ASS_ITEM_RESOLVE', ''); + + redirect($this->u_action . '&iid=' . $item->get_id()); + } + break; + + case 'type': + $json_response = new \phpbb\json_response; + + $type = $this->items_manager->get_type($this->request->variable('type', '', true)); + + if ($type !== null) + { + $data = $item_id ? $this->operator_item->get_entity()->load($item_id)->get_data() : []; + + $this->template->set_filenames(['type' => $type->get_acp_template($data)]); + + $this->template->assign_var('U_ACTION', $this->u_action); + + try + { + $body = $this->template->assign_display('type'); + + $json_response->send([ + 'success' => true, + 'body' => $body, + ]); + } + /** @noinspection PhpRedundantCatchClauseInspection */ + catch (\Twig\Error\Error $e) + { + $json_response->send([ + 'error' => true, + 'MESSAGE_TEXT' => $e->getMessage(), + 'MESSAGE_TITLE' => $this->language->lang('INFORMATION'), + ]); + } + } + else + { + $json_response->send([ + 'error' => true, + 'MESSAGE_TEXT' => $this->language->lang('ASS_ITEM_TYPE_NOT_EXIST'), + 'MESSAGE_TITLE' => $this->language->lang('INFORMATION'), + ]); + } + break; + + case 'cat_delete': + case 'delete': + $s_item = $action === 'delete'; + + $item = $s_item ? $this->operator_item->get_entity()->load($item_id) : null; + $category = $this->operator_cat->get_entity()->load($s_item ? $item->get_category() : $category_id); + + $l_mode = $s_item ? 'ITEM' : 'CATEGORY'; + $u_mode = $this->u_action . ($s_item ? "&cid={$item->get_category()}" : ''); + + if ($s_item) + { + $type = $this->items_manager->get_type($item->get_type()); + + if ($type !== null && $type->is_admin_authed() === false) + { + trigger_error($this->language->lang('ACP_ASS_ITEM_TYPE_NO_AUTH'), E_USER_WARNING); + } + } + + if (confirm_box(true)) + { + if (!$s_item) + { + $this->operator_cat->delete_category($category_id); + } + + $this->operator_item->delete_items($category_id, $item_id); + $this->operator_inv->delete_items($category_id, $item_id); + + $log_title = $s_item ? $item->get_title() : $category->get_title(); + + $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, "LOG_ACP_ASS_{$l_mode}_DELETED", false, [$log_title]); + + $message = $this->language->lang("ACP_ASS_{$l_mode}_DELETE_SUCCESS"); + + if (!$this->request->is_ajax()) + { + $message .= adm_back_link($u_mode); + } + + trigger_error($message); + } + else + { + confirm_box(false, "ACP_ASS_{$l_mode}_DELETE", build_hidden_fields([ + 'action' => $action, + 'cid' => $category_id, + 'iid' => $item_id, + ])); + + redirect($u_mode); + } + break; + + case 'cat_add': + case 'cat_edit': + case 'add': + case 'copy': + case 'edit': + $s_edit = in_array($action, ['edit', 'cat_edit']); + $s_item = in_array($action, ['add', 'copy', 'edit']); + + $entity = $s_item ? $this->operator_item->get_entity() : $this->operator_cat->get_entity(); + + if ($s_edit) + { + try + { + $entity->load(($s_item ? $item_id : $category_id)); + } + catch (runtime_exception $e) + { + $message = $this->language->lang($e->getMessage(), $this->language->lang($s_item ? 'ASS_ITEM' : 'ASS_CATEGORY')); + trigger_error($message . adm_back_link($this->u_action), E_USER_WARNING); + } + } + else if ($s_item) + { + // Copy an item + if ($action === 'copy') + { + $data = $this->operator_item->get_items_by_id([$item_id], false); + + if (empty($data[0])) + { + $message = $this->language->lang('ASS_ERROR_NOT_FOUND', $this->language->lang('ASS_ITEM')); + trigger_error($message . adm_back_link($this->u_action), E_USER_WARNING); + } + + $data = array_diff_key($data[0], array_flip([ + 'item_id', + 'item_title', + 'item_slug', + 'item_purchases', + 'item_stock', + 'item_create_time', + 'item_edit_time', + 'item_conflict', + 'category_slug', + ])); + + $entity->import($data); + + $action = 'add'; + $item_id = 0; + $category_id = $entity->get_category(); + } + else + { + $entity->set_category($category_id); + } + } + + if ($s_item && $s_edit) + { + $type = $this->items_manager->get_type($entity->get_type()); + + if ($type !== null && $type->is_admin_authed() === false) + { + $message = $this->language->lang('ACP_ASS_ITEM_TYPE_NO_AUTH'); + $u_back = $this->u_action . ($entity->get_category() ? '&cid=' . $entity->get_category() : ''); + + trigger_error($message . adm_back_link($u_back), E_USER_WARNING); + } + } + + $this->add_edit_data($entity, $s_item); + + $this->template->assign_vars([ + 'S_ASS_ADD' => !$s_edit, + 'S_ASS_EDIT' => $s_edit, + ]); + break; + } + + if ($s_items) + { + /** @var item $entity */ + foreach ($this->operator_item->get_items($category_id) as $entity) + { + /** @var item_type $type */ + $type = $this->items_manager->get_type($entity->get_type()); + + $s_auth = $type !== null ? $type->is_admin_authed() : true; + + $this->template->assign_block_vars('ass_items', [ + 'ID' => $entity->get_id(), + 'TITLE' => $entity->get_title(), + 'SLUG' => $entity->get_slug(), + 'ICON' => $entity->get_icon(), + 'CONFLICT' => $entity->get_conflict(), + + 'S_ACTIVE' => $entity->get_active(), + 'S_AVAILABLE' => $this->operator_item->is_available($entity), + 'S_AUTH' => $s_auth, + + 'U_DELETE' => $s_auth ? "{$this->u_action}&action=delete&iid={$entity->get_id()}" : '', + 'U_COPY' => $s_auth ? "{$this->u_action}&action=copy&iid={$entity->get_id()}" : '', + 'U_EDIT' => $s_auth ? "{$this->u_action}&action=edit&iid={$entity->get_id()}" : '', + ]); + } + } + else + { + /** @var category $entity */ + foreach ($this->operator_cat->get_categories() as $entity) + { + $this->template->assign_block_vars('ass_categories', [ + 'ID' => $entity->get_id(), + 'TITLE' => $entity->get_title(), + 'SLUG' => $entity->get_slug(), + 'ICON' => $entity->get_icon(), + 'CONFLICT' => $entity->get_conflicts(), + + 'S_ACTIVE' => $entity->get_active(), + 'S_AUTH' => true, + + 'U_DELETE' => "{$this->u_action}&action=cat_delete&cid={$entity->get_id()}", + 'U_EDIT' => "{$this->u_action}&action=cat_edit&cid={$entity->get_id()}", + 'U_VIEW' => "{$this->u_action}&cid={$entity->get_id()}", + ]); + } + } + + $this->template->assign_vars([ + 'S_ITEMS' => $s_items, + + 'U_ACTION' => $this->u_action . ($action ? "&action=$action" : '') . ($category_id ? "&cid=$category_id" : '') . ($item_id ? "&iid=$item_id" : ''), + 'U_ASS_ADD' => $this->u_action . '&action=' . ($s_items ? 'add' : 'cat_add') . ($category_id ? "&cid=$category_id" : ''), + 'U_BACK' => $this->u_action, + ]); + } + + /** + * Handle adding and editing an entity. + * + * @param item|category $entity The entity + * @param bool $s_item Whether it's an item or a category + * @return void + * @access protected + */ + protected function add_edit_data($entity, $s_item) + { + $errors = []; + $submit = $this->request->is_set_post('submit'); + $s_edit = (bool) $entity->get_id(); + $l_mode = $s_item ? 'ITEM' : 'CATEGORY'; + + $form_key = 'add_edit_categories'; + add_form_key($form_key); + + $data = [ + 'active' => $this->request->variable('active', false), + 'title' => $this->request->variable('title', '', true), + 'slug' => $this->request->variable('slug', '', true), + 'icon' => $this->request->variable('icon', '', true), + 'desc' => $this->request->variable('desc', '', true), + ]; + + if ($s_item) + { + $data += [ + 'type' => $this->request->variable('type', '', true), + 'price' => $this->request->variable('price', 0.00), + 'stock_unlimited' => $this->request->variable('stock_unlimited', false), + 'stock' => $this->request->variable('stock', 0), + 'stock_threshold' => $this->request->variable('stock_threshold', 0), + 'gift' => $this->request->variable('gift', false), + 'gift_only' => $this->request->variable('gift_only', false), + 'gift_type' => $this->request->variable('gift_type', false), + 'gift_percentage' => $this->request->variable('gift_percentage', 0), + 'gift_price' => $this->request->variable('gift_price', 0.00), + 'sale_price' => $this->request->variable('sale_price', 0.00), + 'sale_start' => $this->request->variable('sale_start', ''), + 'sale_until' => $this->request->variable('sale_until', ''), + 'featured_start' => $this->request->variable('featured_start', ''), + 'featured_until' => $this->request->variable('featured_until', ''), + 'available_start' => $this->request->variable('available_start', ''), + 'available_until' => $this->request->variable('available_until', ''), + 'count' => $this->request->variable('count', 0), + 'stack' => $this->request->variable('stack', 1), + 'refund_string' => $this->request->variable('refund_string', '', true), + 'expire_string' => $this->request->variable('expire_string', '', true), + 'delete_string' => $this->request->variable('delete_string', '', true), + 'background' => $this->request->variable('background', '', true), + 'images' => $this->request->variable('images', [''], true), + 'related_enabled' => $this->request->variable('related_enabled', false), + 'related_items' => $this->request->variable('related_items', [0]), + ]; + + $post_variables = $this->request->get_super_global(\phpbb\request\request_interface::POST); + + $data['data'] = isset($post_variables['data']) ? $this->request->escape($post_variables['data'], true) : []; + } + + if ($submit) + { + if (!check_form_key($form_key)) + { + $errors[] = $this->language->lang('FORM_INVALID'); + } + + foreach ($data as $key => $value) + { + try + { + $entity->{"set_{$key}"}($value); + + if ($key === 'slug' && $value !== $entity->get_slug()) + { + $s_purge = true; + } + } + catch (runtime_exception $e) + { + $errors[] = $this->get_error_message($e, $l_mode); + } + } + + if ($s_item) + { + $type = $this->items_manager->get_type($entity->get_type()); + + if ($type === null) + { + $errors[] = $this->language->lang('ASS_ITEM_TYPE_NOT_EXIST'); + } + else + { + $errors += (array) $type->validate_acp_data($entity->get_data()); + } + } + + if (empty($errors)) + { + $function = $s_edit ? 'save' : 'insert'; + $message = $s_edit ? "ACP_ASS_{$l_mode}_EDIT_SUCCESS" : "ACP_ASS_{$l_mode}_ADD_SUCCESS"; + $action = $s_edit ? "LOG_ACP_ASS_{$l_mode}_EDITED" : "LOG_ACP_ASS_{$l_mode}_ADDED"; + + try + { + $entity->$function(); + + $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, $action, false, [$entity->get_title()]); + + if (!empty($s_purge)) + { + $this->cache->purge(); + } + + $return = $this->u_action . ($s_item ? "&cid={$entity->get_category()}" : ''); + + meta_refresh(3, $return); + + trigger_error($this->language->lang($message) . adm_back_link($return)); + } + catch (runtime_exception $e) + { + $errors[] = $this->get_error_message($e, $l_mode); + } + } + } + + $this->generate_bbcodes(); + + if ($s_item) + { + $type = $this->items_manager->get_type($entity->get_type()); + + $this->items_manager->set_types_for_select($entity->get_type()); + + $this->template->assign_vars($this->operator_item->get_variables($entity, 'ITEM_', false)); + + $this->template->assign_vars([ + 'ITEM_HELP_DATA' => $this->get_help_data(array_keys($data), $s_edit), + + 'T_ITEM_TEMPLATE' => $type !== null ? $type->get_acp_template($entity->get_data()) : '', + + 'U_ITEM_IMAGE' => $this->u_action . '&m=images&action=select_file&image=', + 'U_ITEM_RESOLVE' => $this->u_action . '&iid=' . $entity->get_id() . '&action=resolve', + 'U_ITEM_TYPE' => $this->u_action . '&iid=' . $entity->get_id() . '&action=type', + 'U_ITEM_ERROR_LOG' => append_sid("{$this->admin_path}index.{$this->php_ext}", [ + 'i' => 'acp_logs', + 'mode' => 'admin', + 'keywords' => urlencode(htmlspecialchars_decode($entity->get_title())), + ]), + ]); + + $this->set_related_items_for_select($entity->get_id(), $entity->get_related_items()); + + if ($s_edit && !$submit && $type === null) + { + $errors[] = $this->language->lang('ASS_ITEM_TYPE_NOT_EXIST'); + } + } + else + { + $this->template->assign_vars($this->operator_cat->get_variables($entity, 'CATEGORY_', false)); + } + + $this->template->assign_vars([ + 'ASS_ERRORS' => $errors, + 'DATE_FORMAT' => $this->time->get_format(), + 'TIMEZONE' => $this->time->get_timezone(), + ]); + } + + /** + * Generate BBCodes for a textarea editor. + * + * @return void + * @access protected + */ + protected function generate_bbcodes() + { + include_once $this->root_path . 'includes/functions_display.' . $this->php_ext; + + $this->language->add_lang('posting'); + + display_custom_bbcodes(); + + $this->template->assign_vars([ + 'S_BBCODE_IMG' => true, + 'S_BBCODE_QUOTE' => true, + 'S_BBCODE_FLASH' => true, + 'S_LINKS_ALLOWED' => true, + ]); + } + + /** + * Get a localised error message from a thrown exception. + * + * @param runtime_exception $e The thrown exception + * @param string $mode The mode (ITEM|CATEGORY) + * @return string The localised error message. + * @access protected + */ + protected function get_error_message(runtime_exception $e, $mode) + { + $params = $e->get_parameters(); + + $field = array_shift($params); + $field = $field ? "ACP_ASS_{$mode}_{$field}" : "ASS_{$mode}"; + + $params = array_merge([$this->language->lang($field)], $params); + + return $this->language->lang_array($e->getMessage(), $params); + } + + /** + * The item's data keys. + * + * @param array $data Item's data keys + * @param bool $s_edit Whether or not the item is being edited + * @return array Item help data values + * @access protected + */ + protected function get_help_data(array $data, $s_edit) + { + $this->language->add_lang('ass_acp_help', 'phpbbstudio/ass'); + + $data = array_filter($data, function($value) { + return $value !== 'data' && strpos($value, '_until') === false; + }); + + if ($s_edit) + { + $data = array_merge($data, ['dates', 'states', 'stock_info', 'sale_info']); + } + + return $data; + } + + /** + * Assign categories and items to the template for the related items selection. + * + * @param int $item_id The item identifiers + * @param array $item_ids The related items identifiers + * @return void + * @access protected + */ + protected function set_related_items_for_select($item_id, array $item_ids) + { + /** @var category $category */ + foreach ($this->operator_cat->get_categories() as $category) + { + $this->template->assign_block_vars('categories', array_merge([ + 'S_INACTIVE' => !$category->get_active(), + ], $this->operator_cat->get_variables($category))); + + /** @var item $item */ + foreach ($this->operator_item->get_items($category->get_id()) as $item) + { + if ($item->get_id() === $item_id) + { + continue; + } + + $this->template->assign_block_vars('categories.items', array_merge([ + 'S_INACTIVE' => !$item->get_active(), + 'S_SELECTED' => in_array($item->get_id(), $item_ids), + ], $this->operator_item->get_variables($item))); + } + } + } + + /** + * Set custom form action. + * + * @param string $u_action Custom form action + * @return self $this This controller for chaining calls + * @access public + */ + public function set_page_url($u_action) + { + $this->u_action = $u_action; + + return $this; + } +} diff --git a/ext/phpbbstudio/ass/controller/acp_logs_controller.php b/ext/phpbbstudio/ass/controller/acp_logs_controller.php new file mode 100644 index 0000000..f121ac5 --- /dev/null +++ b/ext/phpbbstudio/ass/controller/acp_logs_controller.php @@ -0,0 +1,246 @@ +config = $config; + $this->items_manager = $items_manager; + $this->language = $language; + $this->log = $log; + $this->log_phpbb = $log_phpbb; + $this->operator_cat = $operator_cat; + $this->operator_item = $operator_item; + $this->pagination = $pagination; + $this->request = $request; + $this->template = $template; + $this->user = $user; + } + + /** + * Handle and display the "Logs" ACP mode. + * + * @return void + * @access public + */ + public function logs() + { + $this->language->add_lang(['ass_acp_common', 'ass_common'], 'phpbbstudio/ass'); + + $show_array = [ + 'all' => ['title' => 'ASS_ALL', 'sql' => ''], + 'use' => ['title' => 'ASS_USAGES', 'sql' => 'l.item_purchase = 0'], + 'buy' => ['title' => 'ASS_PURCHASES', 'sql' => 'l.item_purchase = 1 AND l.recipient_id = 0'], + 'given' => ['title' => 'ASS_GIFTS_GIVEN', 'sql' => 'l.item_purchase = 1 AND l.recipient_id <> 0'], + ]; + $sort_array = [ + 'time' => ['title' => 'TIME', 'sql' => 'l.log_time'], + 'price' => ['title' => 'ASS_ITEM_PRICE', 'sql' => 'l.points_sum'], + 'item' => ['title' => 'ASS_ITEM_TITLE', 'sql' => 'i.item_title'], + 'category' => ['title' => 'ASS_CATEGORY_TITLE', 'sql' => 'c.category_title'], + 'recipient' => ['title' => 'ASS_RECIPIENT_NAME', 'sql' => 'recipient_name'], + ]; + $dir_array = [ + 'desc' => ['title' => 'DESCENDING', 'sql' => 'DESC'], + 'asc' => ['title' => 'ASCENDING', 'sql' => 'ASC'], + ]; + + $page = $this->request->variable('page', 1); + $show = $this->request->variable('display', 'all', true); + $sort = $this->request->variable('sort', 'time', true); + $dir = $this->request->variable('direction', 'desc', true); + + $show = in_array($show, array_keys($show_array)) ? $show : 'all'; + $sort = in_array($sort, array_keys($sort_array)) ? $sort : 'time'; + $dir = in_array($dir, array_keys($dir_array)) ? $dir : 'desc'; + + $delete_mark = $this->request->variable('del_marked', false, false, \phpbb\request\request_interface::POST); + $delete_all = $this->request->variable('del_all', false, false, \phpbb\request\request_interface::POST); + $marked = $this->request->variable('mark', [0]); + + $log_action = $this->u_action . "&display={$show}&sort={$sort}&direction={$dir}"; + + if (($delete_mark || $delete_all)) + { + if (confirm_box(true)) + { + $this->log->delete($delete_all, $marked); + + $l_delete = $delete_all ? 'ALL' : (count($marked) > 1 ? 'ENTRIES' : 'ENTRY'); + + $this->log_phpbb->add('admin', $this->user->data['user_id'], $this->user->ip, "LOG_ACP_ASS_LOG_DELETED_{$l_delete}"); + + trigger_error($this->language->lang("ACP_ASS_LOG_DELETED_{$l_delete}") . adm_back_link($log_action . "&page{$page}")); + } + else + { + confirm_box(false, $this->language->lang('CONFIRM_OPERATION'), build_hidden_fields([ + 'del_marked' => $delete_mark, + 'del_all' => $delete_all, + 'mark' => $marked, + ])); + + redirect($log_action . "&page{$page}"); + } + } + + $sql_where = $show_array[$show]['sql']; + $sql_order = $sort_array[$sort]['sql']; + $sql_dir = $dir_array[$dir]['sql']; + + $limit = (int) $this->config['ass_logs_per_page']; + $start = ($page - 1) * $limit; + + $total = $this->log->get_user_logs_count($sql_where); + $rowset = $this->log->get_user_logs($sql_where, $sql_order, $sql_dir, $limit, $start); + + $categories = $this->operator_cat->get_categories_by_id(array_column($rowset, 'category_id')); + $items = $this->operator_item->get_items_by_id(array_column($rowset, 'item_id')); + + foreach ($rowset as $row) + { + $category_id = (int) $row['category_id']; + $item_id = (int) $row['item_id']; + + /** @var category $category */ + $category = !empty($categories[$category_id]) ? $categories[$category_id] : null; + + /** @var item $item */ + $item = !empty($items[$item_id]) ? $items[$item_id] : null; + + /** @var item_type $type */ + $type = $item ? $this->items_manager->get_type($item->get_type()) : null; + + $this->template->assign_block_vars('ass_logs', [ + 'CATEGORY_TITLE' => $category ? $category->get_title() : $this->language->lang('ASS_UNAVAILABLE_CATEGORY'), + 'ITEM_TITLE' => $item ? $item->get_title() : $this->language->lang('ASS_UNAVAILABLE_ITEM'), + + 'LOG_ACTION' => $type ? $this->language->lang($type->get_language('log')) : $this->language->lang('ASS_UNAVAILABLE_' . (!$item ? 'ITEM' : 'TYPE')), + 'LOG_ID' => $row['log_id'], + 'LOG_IP' => $row['log_ip'], + 'LOG_TIME' => $this->user->format_date($row['log_time']), + + 'POINTS_NEW' => $row['points_new'], + 'POINTS_OLD' => $row['points_old'], + 'POINTS_SUM' => -$row['points_sum'], + + 'RECIPIENT' => $row['recipient_id'] ? get_username_string('full', $row['recipient_id'], $row['recipient_name'], $row['recipient_colour']) : '', + 'USER' => get_username_string('full', $row['user_id'], $row['username'], $row['user_colour']), + + 'S_PURCHASE' => $row['item_purchase'], + 'S_GIFT_RECEIVE' => $row['recipient_id'] != 0, + ]); + } + + $this->pagination->generate_template_pagination($log_action, 'pagination', 'page', $total, $limit, $start); + + $this->template->assign_vars([ + 'SORT_DISPLAY' => $show, + 'SORT_DISPLAY_ARRAY' => $show_array, + 'SORT_SORT' => $sort, + 'SORT_SORT_ARRAY' => $sort_array, + 'SORT_DIR' => $dir, + 'SORT_DIR_ARRAY' => $dir_array, + + 'TOTAL_LOGS' => $this->language->lang('TOTAL_LOGS', $total), + + 'U_ACTION' => $this->u_action, + ]); + } + + /** + * Set custom form action. + * + * @param string $u_action Custom form action + * @return self $this This controller for chaining calls + * @access public + */ + public function set_page_url($u_action) + { + $this->u_action = $u_action; + + return $this; + } +} diff --git a/ext/phpbbstudio/ass/controller/acp_overview_controller.php b/ext/phpbbstudio/ass/controller/acp_overview_controller.php new file mode 100644 index 0000000..cb540c3 --- /dev/null +++ b/ext/phpbbstudio/ass/controller/acp_overview_controller.php @@ -0,0 +1,444 @@ +config = $config; + $this->config_text = $config_text; + $this->db = $db; + $this->language = $language; + $this->operator_item = $operator_item; + $this->parser = $parser; + $this->renderer = $renderer; + $this->request = $request; + $this->template = $template; + $this->user_loader = $user_loader; + $this->utils = $utils; + + $this->categories_table = $categories_table; + $this->items_table = $items_table; + $this->logs_table = $logs_table; + } + + /** + * Handle and display the "Overview" ACP mode. + * + * @return void + * @access public + */ + public function overview() + { + $this->language->add_lang(['ass_acp_common', 'ass_common'], 'phpbbstudio/ass'); + + $action = $this->request->variable('action', '', true); + $submit = $this->request->is_set_post('submit'); + + $notes = $this->config_text->get('ass_admin_notes'); + + if ($action === 'notes') + { + if ($submit) + { + $notes = $this->parser->parse($this->request->variable('notes', '', true)); + + $this->config_text->set('ass_admin_notes', $notes); + } + else + { + $this->template->assign_vars([ + 'NOTES_EDIT' => $this->utils->unparse($notes), + 'S_NOTES' => true, + ]); + } + } + + $item_modes = [ + 'featured', 'featured_coming', 'sale', 'sale_coming', + 'low_stock', 'low_sellers', 'top_sellers', 'recent', + ]; + + foreach ($item_modes as $mode) + { + $items = $this->get_items($mode); + + foreach ($items as $item) + { + $this->template->assign_block_vars($mode, $this->operator_item->get_variables($item)); + } + } + + foreach ($this->get_recent() as $row) + { + $item = $this->operator_item->get_entity()->import($row); + + $this->template->assign_block_vars('purchases', array_merge( + $this->operator_item->get_variables($item), + ['PURCHASE_TIME' => (int) $row['log_time']] + )); + } + + $buyers = $this->get_users('buyers'); + $gifters = $this->get_users('gifters'); + $spenders = $this->get_users('spenders'); + + $this->user_loader->load_users(array_merge(array_keys($buyers), array_keys($gifters), array_keys($spenders))); + + $users = [ + 'buyers' => $buyers, + 'gifters' => $gifters, + 'spenders' => $spenders, + ]; + + foreach ($users as $user_mode => $users_array) + { + foreach ($users_array as $user_id => $count) + { + $this->template->assign_block_vars($user_mode, [ + 'NAME' => $this->user_loader->get_username($user_id, 'full'), + 'AVATAR' => $this->user_loader->get_avatar($user_id), + 'COUNT' => $count, + ]); + } + } + + $this->template->assign_vars([ + 'COUNTS' => $this->get_counts(), + 'NOTES' => $notes ? $this->renderer->render(htmlspecialchars_decode($notes, ENT_COMPAT)) : '', + + 'GIFTING_ENABLED' => (bool) $this->config['ass_gift_enabled'], + 'NO_IMAGE_ICON' => (string) $this->config['ass_no_image_icon'], + 'SHOP_ACTIVE' => (bool) $this->config['ass_active'], + 'SHOP_ENABLED' => (bool) $this->config['ass_enabled'], + + 'U_ACTION' => $this->u_action, + 'U_NOTES' => $this->u_action . '&action=notes', + ]); + } + + /** + * Get items for a specific mode. + * + * @param string $mode The item mode (featured|sale|etc..) + * @return array Item entities + * @access protected + */ + protected function get_items($mode) + { + $sql_array = [ + 'SELECT' => 'i.*', + 'FROM' => [$this->items_table => 'i'], + 'WHERE' => $this->get_sql_where($mode), + 'ORDER_BY' => $this->get_sql_order($mode), + ]; + + $sql = $this->db->sql_build_query('SELECT', $sql_array); + $result = $this->db->sql_query_limit($sql, 5); + $rowset = $this->db->sql_fetchrowset($result); + $this->db->sql_freeresult($result); + + return $this->operator_item->get_entities($rowset); + } + + /** + * Get recent items. + * + * @return array Item entities + * @access protected + */ + protected function get_recent() + { + $sql = 'SELECT i.*, l.log_time + FROM ' . $this->logs_table . ' l, + ' . $this->items_table . ' i + WHERE i.item_id = l.item_id + AND l.item_purchase = 1 + ORDER BY l.log_time DESC'; + $result = $this->db->sql_query_limit($sql, 5); + $rowset = $this->db->sql_fetchrowset($result); + $this->db->sql_freeresult($result); + + return (array) $rowset; + } + + /** + * Get users for a specific mode. + * + * @param string $mode The mode (buyers|gifters|spenders) + * @return array User rows + * @access protected + */ + protected function get_users($mode) + { + $users = []; + + switch ($mode) + { + case 'buyers': + $select = 'COUNT(log_id)'; + $where = ' WHERE recipient_id = 0'; + break; + + case 'gifters': + $select = 'COUNT(log_id)'; + $where = ' WHERE recipient_id <> 0'; + break; + + case 'spenders': + default: + $select = 'SUM(points_sum)'; + $where = ''; + break; + } + + $sql = 'SELECT ' . $select . ' as count, user_id + FROM ' . $this->logs_table . $where . ' + GROUP BY user_id + ORDER BY count DESC'; + $result = $this->db->sql_query_limit($sql, 5); + while ($row = $this->db->sql_fetchrow($result)) + { + $users[(int) $row['user_id']] = $row['count']; + } + $this->db->sql_freeresult($result); + + return (array) $users; + } + + /** + * Get counts for various things. + * + * @return array Array of counts + * @access protected + */ + protected function get_counts() + { + $counts = [ + 'categories' => (int) $this->db->get_row_count($this->categories_table), + 'items' => (int) $this->db->get_row_count($this->items_table), + ]; + + $sql = 'SELECT COUNT(i.item_id) as count + FROM ' . $this->items_table . ' i + WHERE ' . $this->get_sql_where('featured'); + $result = $this->db->sql_query_limit($sql , 1); + $counts['featured'] = (int) $this->db->sql_fetchfield('count'); + $this->db->sql_freeresult($result); + + $sql = 'SELECT COUNT(i.item_id) as count + FROM ' . $this->items_table . ' i + WHERE ' . $this->get_sql_where('sale'); + $result = $this->db->sql_query_limit($sql , 1); + $counts['sale'] = (int) $this->db->sql_fetchfield('count'); + $this->db->sql_freeresult($result); + + $sql = 'SELECT SUM(item_purchases) as count + FROM ' . $this->items_table; + $result = $this->db->sql_query_limit($sql , 1); + $counts['purchases'] = (int) $this->db->sql_fetchfield('count'); + $this->db->sql_freeresult($result); + + $sql = 'SELECT SUM(points_sum) as count + FROM ' . $this->logs_table; + $result = $this->db->sql_query_limit($sql , 1); + $counts['spent'] = (double) $this->db->sql_fetchfield('count'); + $this->db->sql_freeresult($result); + + $sql = 'SELECT COUNT(item_conflict) as count + FROM ' . $this->items_table . ' + WHERE item_conflict = 1'; + $result = $this->db->sql_query_limit($sql , 1); + $counts['errors'] = (double) $this->db->sql_fetchfield('count'); + $this->db->sql_freeresult($result); + + return $counts; + } + + /** + * Get the SQL WHERE statement for a specific mode + * + * @param string $mode + * @return string + * @access protected + */ + protected function get_sql_where($mode) + { + switch ($mode) + { + case 'low_stock': + return 'i.item_stock_unlimited <> 1'; + + case 'featured': + return 'i.item_sale_start < ' . time() . ' + AND i.item_featured_start <> 0 + AND i.item_featured_until <> 0 + AND (' . time() . ' BETWEEN i.item_featured_start AND i.item_featured_until)'; + + case 'featured_coming': + return 'i.item_featured_start <> 0 + AND i.item_featured_until <> 0 + AND i.item_featured_start > ' . time(); + + case 'sale': + return 'i.item_featured_start < ' . time() . ' + AND i.item_sale_start <> 0 + AND i.item_sale_until <> 0 + AND (' . time() . ' BETWEEN i.item_sale_start AND item_sale_until)'; + + case 'sale_coming': + return 'i.item_sale_start <> 0 + AND i.item_sale_until <> 0 + AND i.item_sale_start > ' . time(); + + default: + return ''; + } + } + + /** + * Get the SQL ORDER BY statement for a specific mode. + * + * @param string $mode + * @return string + * @access protected + */ + protected function get_sql_order($mode) + { + switch ($mode) + { + case 'low_stock': + return 'i.item_stock ASC, i.item_title ASC'; + + case 'low_sellers': + return 'i.item_purchases ASC, i.item_title ASC'; + + case 'top_sellers': + return 'i.item_purchases DESC, i.item_title ASC'; + + case 'recent': + return 'i.item_create_time DESC'; + + case 'featured': + return 'i.item_featured_until ASC, i.item_title ASC'; + + case 'featured_coming': + return 'i.item_featured_start ASC, i.item_title ASC'; + + case 'sale': + return 'i.item_sale_until ASC, i.item_title ASC'; + + case 'sale_coming': + return 'i.item_sale_start ASC, i.item_title ASC'; + + default: + return 'i.item_title ASC'; + } + } + + /** + * Set custom form action. + * + * @param string $u_action Custom form action + * @return self $this This controller for chaining calls + * @access public + */ + public function set_page_url($u_action) + { + $this->u_action = $u_action; + + return $this; + } +} diff --git a/ext/phpbbstudio/ass/controller/acp_settings_controller.php b/ext/phpbbstudio/ass/controller/acp_settings_controller.php new file mode 100644 index 0000000..ffbee8e --- /dev/null +++ b/ext/phpbbstudio/ass/controller/acp_settings_controller.php @@ -0,0 +1,341 @@ +aps_functions = $aps_functions; + $this->config = $config; + $this->config_text = $config_text; + $this->language = $language; + $this->log = $log; + $this->parser = $parser; + $this->request = $request; + $this->template = $template; + $this->user = $user; + $this->utils = $utils; + + $this->root_path = $root_path; + $this->php_ext = $php_ext; + } + + /** + * Handle and display the "Settings" ACP mode. + * + * @return void + * @access public + */ + public function settings() + { + $this->language->add_lang(['ass_acp_common', 'ass_common'], 'phpbbstudio/ass'); + + $errors = []; + $submit = $this->request->is_set_post('submit'); + + $form_key = 'shop_settings'; + add_form_key($form_key); + + if ($submit) + { + if (!check_form_key($form_key)) + { + $errors[] = $this->language->lang('FORM_INVALID'); + } + } + + if ($this->request->variable('action', '', true) === 'locations') + { + $this->link_locations(); + } + + $banner_sizes = ['small', 'tiny']; + $banner_colours = ['blue', 'red', 'green', 'orange', 'aqua', 'yellow', 'pink', 'violet', 'purple', 'gold', 'silver', 'bronze']; + $icon_colours = ['blue', 'red', 'green', 'orange', 'aqua', 'yellow', 'pink', 'violet', 'purple', 'gold', 'silver', 'bronze', + 'bluegray', 'gray', 'lightgray', 'black', 'white', 'lighten', 'darken']; + + $panels = [ + 'featured' => ['limit' => ['min' => 0, 'max' => 10], 'order' => ['min' => 1, 'max' => 6], 'width' => ['min' => 4, 'max' => 6]], + 'sale' => ['limit' => ['min' => 0, 'max' => 10], 'order' => ['min' => 1, 'max' => 6], 'width' => ['min' => 4, 'max' => 6]], + 'featured_sale' => ['limit' => ['min' => 0, 'max' => 4], 'order' => ['min' => 1, 'max' => 6], 'width' => ['min' => 4, 'max' => 6]], + 'random' => ['limit' => ['min' => 0, 'max' => 20], 'order' => ['min' => 1, 'max' => 6], 'width' => ['min' => 3, 'max' => 4]], + 'recent' => ['limit' => ['min' => 0, 'max' => 10], 'order' => ['min' => 1, 'max' => 6], 'width' => ['min' => 4, 'max' => 6]], + 'limited' => ['limit' => ['min' => 0, 'max' => 10], 'order' => ['min' => 1, 'max' => 6], 'width' => ['min' => 4, 'max' => 6]], + ]; + + $options = ['banner_size', 'banner_colour', 'icon_colour', 'icon', 'limit', 'order', 'width']; + $settings = [ + 'int' => ['enabled', 'active', 'gift_enabled', 'deactivate_conflicts', 'purge_cache', 'items_per_page', 'logs_per_page', 'carousel_arrows', 'carousel_dots', 'carousel_fade', 'carousel_play', 'carousel_play_speed', 'carousel_speed'], + 'string' => ['shop_icon', 'inventory_icon', 'no_image_icon', 'gift_icon'], + ]; + + // General settings + foreach ($settings as $type => $data) + { + foreach ($data as $name) + { + $config_name = "ass_{$name}"; + $default = $this->config[$config_name]; + settype($default, $type); + + $this->template->assign_var(utf8_strtoupper($name), $default); + + if ($submit && empty($errors)) + { + $value = $this->request->variable($name, '', $type === 'string'); + + if ($value !== $default) + { + $this->config->set($config_name, $value); + } + } + } + } + + // Panel settings + $variables = []; + + foreach ($panels as $panel => $data) + { + foreach ($options as $option) + { + $name = "{$panel}_{$option}"; + $config_name = "ass_panel_{$name}"; + + $default = $this->config[$config_name]; + $variables[utf8_strtoupper($option)][$panel] = $default; + + if ($submit && empty($errors)) + { + $value = $this->request->variable($name, $default); + + if (isset($data[$option])) + { + if ($value < $data[$option]['min']) + { + $field = $this->language->lang('ACP_ASS_PANEL_' . utf8_strtoupper($panel)); + $field .= $this->language->lang('COLON'); + $field .= ' ' . $this->language->lang('ACP_ASS_PANEL_' . utf8_strtoupper($option)); + + $errors[] = $this->language->lang('ASS_ERROR_TOO_LOW', $field, $data[$option]['min'], $value); + + continue; + } + + if ($value > $data[$option]['max']) + { + $field = $this->language->lang('ACP_ASS_PANEL_' . utf8_strtoupper($panel)); + $field .= $this->language->lang('COLON'); + $field .= ' ' . $this->language->lang('ACP_ASS_PANEL_' . utf8_strtoupper($option)); + + $errors[] = $this->language->lang('ASS_ERROR_TOO_HIGH', $field, $data[$option]['max'], $value); + + continue; + } + } + + if ($value != $default) + { + $this->config->set($config_name, $value); + } + } + } + } + + uksort($panels, function($a, $b) + { + if ($this->config["ass_panel_{$a}_order"] == $this->config["ass_panel_{$b}_order"]) + { + return 0; + } + + return $this->config["ass_panel_{$a}_order"] < $this->config["ass_panel_{$b}_order"] ? -1 : 1; + }); + + if ($submit && empty($errors)) + { + $message = $this->request->variable('inactive_desc', '', true); + $message = $this->parser->parse($message); + + $this->config_text->set('ass_inactive_desc', $message); + + meta_refresh(3, $this->u_action); + + trigger_error($this->language->lang('CONFIG_UPDATED') . adm_back_link($this->u_action)); + } + + $message = $this->config_text->get('ass_inactive_desc'); + $message = $this->utils->unparse($message); + + $this->generate_bbcodes(); + + $this->template->assign_vars(array_merge($variables, [ + 'ERRORS' => $errors, + + 'INACTIVE_DESC' => $message, + + 'SHOP_BLOCKS' => $panels, + 'SHOP_ICON_COLOURS' => $icon_colours, + 'SHOP_BANNER_COLOURS' => $banner_colours, + 'SHOP_BANNER_SIZES' => $banner_sizes, + + 'U_ACTION' => $this->u_action, + 'U_LOCATIONS' => $this->u_action . '&action=locations', + ])); + } + + /** + * Generate BBCodes for a textarea editor. + * + * @return void + * @access protected + */ + protected function generate_bbcodes() + { + include_once $this->root_path . 'includes/functions_display.' . $this->php_ext; + + $this->language->add_lang('posting'); + + display_custom_bbcodes(); + + $this->template->assign_vars([ + 'S_BBCODE_IMG' => true, + 'S_BBCODE_QUOTE' => true, + 'S_BBCODE_FLASH' => true, + 'S_LINKS_ALLOWED' => true, + ]); + } + + /** + * Handles the link locations from the settings page. + * + * @return void + * @access protected + */ + protected function link_locations() + { + $this->language->add_lang('aps_acp_common', 'phpbbstudio/aps'); + + $locations = $this->aps_functions->get_link_locations('ass_link_locations'); + $variables = ['S_ASS_LOCATIONS' => true]; + + foreach ($locations as $location => $status) + { + $variables[$location] = (bool) $status; + } + + $this->template->assign_vars($variables); + + if ($this->request->is_set_post('submit_locations')) + { + $links = []; + + foreach (array_keys($locations) as $location) + { + $links[$location] = $this->request->variable((string) $location, false); + } + + $this->aps_functions->set_link_locations($links, 'ass_link_locations'); + + $this->log->add('admin', $this->user->data['user_id'], $this->user->data['user_ip'], 'LOG_ACP_ASS_LOCATIONS'); + + trigger_error($this->language->lang('ACP_APS_LOCATIONS_SUCCESS') . adm_back_link($this->u_action)); + } + } + + /** + * Set custom form action. + * + * @param string $u_action Custom form action + * @return self $this This controller for chaining calls + * @access public + */ + public function set_page_url($u_action) + { + $this->u_action = $u_action; + + return $this; + } +} diff --git a/ext/phpbbstudio/ass/controller/inventory_controller.php b/ext/phpbbstudio/ass/controller/inventory_controller.php new file mode 100644 index 0000000..e69deec --- /dev/null +++ b/ext/phpbbstudio/ass/controller/inventory_controller.php @@ -0,0 +1,968 @@ +aps_distributor = $aps_distributor; + $this->aps_functions = $aps_functions; + $this->auth = $auth; + $this->config = $config; + $this->controller = $controller; + $this->helper = $helper; + $this->items_manager = $items_manager; + $this->language = $language; + $this->log = $log; + $this->log_phpbb = $log_phpbb; + $this->operator_cat = $operator_cat; + $this->operator_inv = $operator_inv; + $this->operator_item = $operator_item; + $this->notification = $notification; + $this->pagination = $pagination; + $this->request = $request; + $this->router = $router; + $this->template = $template; + $this->time = $time; + $this->user = $user; + $this->user_loader = $user_loader; + } + + /** + * Handle the purchase/gift action. + * + * @param string $category_slug The category slug + * @param string $item_slug The item slug + * @param bool $purchase Whether it's a purchase or a gift + * @return Response + * @access public + */ + public function purchase($category_slug, $item_slug, $purchase) + { + $this->controller->check_shop(); + + if (!$this->user->data['is_registered']) + { + throw new shop_exception(401, 'ASS_ERROR_NOT_AUTH_PURCHASE'); + } + + $category = $this->operator_cat->load_entity($category_slug); + $item = $this->operator_item->load_entity($item_slug, $category->get_slug(), $category->get_id()); + + $this->template->assign_vars($this->operator_item->get_variables($item)); + + if ($purchase && !$this->auth->acl_get('u_ass_can_purchase')) + { + throw new shop_exception(403, 'ASS_ERROR_NOT_AUTH_PURCHASE'); + } + + if (!$this->operator_item->is_available($item)) + { + throw new shop_exception(410, 'ASS_ERROR_NOT_AVAILABLE'); + } + + if (!$purchase) + { + if (!$this->auth->acl_get('u_ass_can_gift')) + { + throw new shop_exception(403, 'ASS_ERROR_NOT_AUTH_GIFT'); + } + + if (!$item->get_gift()) + { + throw new shop_exception(400, 'ASS_ERROR_NOT_GIFTABLE'); + } + } + + if (!$this->operator_inv->check_price($item, $purchase)) + { + throw new shop_exception(400, 'ASS_ERROR_NOT_ENOUGH_POINTS', [$this->aps_functions->get_name()]); + } + + if (!$item->get_stock() && !$item->get_stock_unlimited()) + { + throw new shop_exception(400, 'ASS_ERROR_OUT_OF_STOCK'); + } + + $stack = 0; + $user_id = 0; + $username = ''; + + if (confirm_box(true) && !$purchase) + { + $username = $this->request->variable('username', '', true); + $user_id = (int) $this->user_loader->load_user_by_username($username); + $user2 = $this->user_loader->get_user($user_id); + + if ($user_id === ANONYMOUS) + { + throw new shop_exception(404, 'NO_USER'); + } + + if ($user_id === (int) $this->user->data['user_id']) + { + throw new shop_exception(403, 'ASS_ERROR_NOT_GIFT_SELF'); + } + + $auth2 = new \phpbb\auth\auth; + $auth2->acl($user2); + + if (!$auth2->acl_get('u_ass_can_receive_gift')) + { + throw new shop_exception(403, 'ASS_ERROR_NOT_AUTH_RECEIVE'); + } + + $username = $this->user_loader->get_username($user_id, 'no_profile'); + } + + if ($purchase || (confirm_box(true) && !$purchase)) + { + $stack = $this->operator_inv->get_inventory_stack($item, $user_id); + + if ($stack >= $item->get_stack()) + { + $message = $purchase ? 'ASS_ERROR_STACK_LIMIT' : 'ASS_ERROR_STACK_LIMIT_USER'; + $params = $purchase ? [] : [$username]; + + throw new shop_exception(409, $message, $params); + } + + $auth = !empty($auth2) ? $auth2 : $this->auth; + + if ($stack && !$auth->acl_get('u_ass_can_stack')) + { + $message = $purchase ? 'ASS_ERROR_STACK_NO_AUTH' : 'ASS_ERROR_STACK_NO_AUTH_USER'; + $params = $purchase ? [] : [$username]; + + throw new shop_exception(409, $message, $params); + } + } + + if (!$this->request->is_ajax()) + { + $this->controller->create_shop('shop', $category, $item); + } + + $l_mode = $purchase ? 'ASS_PURCHASE' : 'ASS_GIFT'; + + $this->template->assign_vars([ + 'ASS_ITEM_STACK' => $stack, + 'ASS_PURCHASE_PRICE' => $this->operator_inv->get_price($item, $purchase), + 'S_ASS_PURCHASE' => $purchase, + ]); + + if (confirm_box(true)) + { + $points_new = $this->operator_inv->add_purchase($item, $user_id, $purchase); + $inventory_id = $this->operator_inv->get_purchase_id(); + + $item->set_purchases($item->get_purchases() + 1) + ->set_stock($item->get_stock() - (int) !$item->get_stock_unlimited()) + ->save(); + + if ($item->get_stock() === $item->get_stock_threshold() && !$item->get_stock_unlimited()) + { + $this->notification->low_stock($item); + } + + if (!$purchase) + { + $this->notification->gift($item, $user_id, $inventory_id, $stack + 1); + + if ($this->config['allow_privmsg'] && $this->auth->acl_get('u_sendpm')) + { + $u_pm = $this->router->regular('ucp', [ + 'i' => 'pm', + 'mode' => 'compose', + 'u' => $user_id, + ], true, $this->request->is_ajax()); + } + } + + $this->log->add($item, true, $this->operator_inv->get_price($item), $user_id); + + $this->template->assign_vars([ + 'NEW_USER_POINTS' => $points_new, + 'RECIPIENT_NAME' => $username, + 'U_SEND_PM' => !empty($u_pm) ? $u_pm : '', + ]); + + if ($this->request->is_ajax()) + { + $this->template->set_filenames([ + 'body' => 'ass_purchase_ajax.html', + ]); + + $this->template->assign_var('S_PURCHASE_SUCCESS', true); + + return new JsonResponse([ + 'MESSAGE_TITLE' => $this->language->lang($l_mode), + 'MESSAGE_TEXT' => $this->template->assign_display('body'), + 'id' => $item->get_id(), + 'points' => $this->aps_functions->display_points($points_new, false), + 'stock' => !$item->get_stock_unlimited() ? $item->get_stock() : false, + ]); + } + else + { + return $this->helper->render('ass_purchase.html', $this->language->lang($l_mode)); + } + } + else + { + $ajax = $this->request->is_ajax() ? '_ajax' : ''; + $body = "ass_purchase{$ajax}.html"; + + confirm_box(false, $l_mode, '', $body, $this->helper->get_current_url()); + + return new RedirectResponse($this->router->item($category->get_slug(), $item->get_slug())); + } + } + + /** + * Display the inventory and handle any actions. + * + * @param string $category_slug The category slug + * @param string $item_slug The item slug + * @param int $index The item index + * @param string $action The action + * @return Response + * @access public + */ + public function inventory($category_slug, $item_slug, $index, $action) + { + $index0 = (int) $index - 1; + + $this->controller->check_shop(); + + $this->operator_inv->clean_inventory(); + + $category = $category_slug ? $this->operator_cat->load_entity($category_slug) : null; + $item = $item_slug ? $this->operator_item->load_entity($item_slug, $category->get_slug(), $category->get_id()) : null; + + $s_category = $category !== null; + $s_item = $item !== null; + + $inventory = $this->operator_inv->get_inventory($category); + + $cat_ids = array_column($inventory, 'category_id'); + $item_ids = array_column($inventory, 'item_id', 'inventory_id'); + + $categories = $this->operator_cat->get_categories_by_id($cat_ids); + $items = $this->operator_item->get_items_by_id($item_ids); + + $variables = []; + $item_map = []; + + foreach ($item_ids as $inventory_id => $item_id) + { + $item_map[$item_id] = array_keys($item_ids, $item_id); + } + + if ($s_item && !in_array($item->get_id(), array_keys($items))) + { + throw new shop_exception(404, 'ASS_ERROR_NOT_OWNED'); + } + + $this->controller->create_shop('inventory', $category); + + if ($s_category && $s_item && !empty($action)) + { + $type = $this->items_manager->get_type($item->get_type()); + + if ($type === null) + { + $this->log_phpbb->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_ACP_ASS_ITEM_TYPE_ERROR', time(), [$category->get_title(), $item->get_title()]); + + throw new shop_item_exception(404, 'ASS_ITEM_TYPE_NOT_EXIST'); + } + + $row = $this->operator_inv->get_inventory_item($item, $index0); + + switch ($action) + { + case 'activate': + $type->set_category($category); + $type->set_item($item); + + if (confirm_box(true)) + { + try + { + $success = $type->activate($item->get_data()); + } + catch (shop_item_exception $e) + { + $this->log_phpbb->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_ACP_ASS_ITEM_TYPE_ERROR', time(), [$category->get_title(), $item->get_title()]); + + if (!$item->get_conflict()) + { + if ($this->config['ass_deactivate_conflicts']) + { + $item->set_active(false); + } + + $item->set_conflict(true) + ->save(); + + $category->set_conflicts($category->get_conflicts() + 1) + ->save(); + } + + throw $e; + } + + $message = !empty($success) ? $this->language->lang($type->get_language('success')) : 'Some error message'; + $title = !empty($success) ? $this->language->lang($type->get_language('title')) : $this->language->lang('INFORMATION'); + + $count = (int) $row['use_count']; + + if (!empty($success) && empty($success['file'])) + { + $count++; + } + + $limit = $item->get_count() && $item->get_count() === $count; + $delete = !$item->get_delete_seconds() && $limit; + + $file = !empty($success['file']) ? $success['file'] : false; + + if ($file) + { + $limit = $item->get_count() && $item->get_count() === ($count + 1); + $delete = !$item->get_delete_seconds() && $limit; + + $u_file = $this->router->inventory($item->get_category_slug(), $item->get_slug(), $index, 'download', ['hash' => generate_link_hash($file)]); + } + else if (!empty($success)) + { + $data = [ + 'use_count' => $count, + 'use_time' => time(), + ]; + + $delete + ? $this->operator_inv->delete($row['inventory_id']) + : $this->operator_inv->activated($row['inventory_id'], $data); + + $data['use_time'] = $this->user->format_date($data['use_time']); + + $this->log->add($item, false); + } + + if ($this->request->is_ajax()) + { + if ($delete) + { + $inventory_ids = $item_map[$item->get_id()]; + + unset($inventory_ids[array_search($row['inventory_id'], $inventory_ids)]); + + if (!empty($inventory_ids)) + { + $inventory_ids = array_values($inventory_ids); + + // Get the first inventory row + $row = $inventory[$inventory_ids[0]]; + + $stack = $this->get_stack_info($inventory_ids, $index0); + + $vars = $this->get_inventory_variables($category, $item, $row, $stack); + + if (!empty($vars['BACKGROUND_SRC'])) + { + // Fix the incorrect web root path + $vars['BACKGROUND_SRC'] = $this->operator_item->get_absolute_background_path($vars['BACKGROUND_SRC']); + } + + $this->template->set_filenames(['item' => '@phpbbstudio_ass/ass_item_inventory.html']); + $this->template->assign_vars(['item' => $vars]); + $next_item = $this->template->assign_display('item'); + } + } + + return new JsonResponse([ + 'MESSAGE_TITLE' => $title, + 'MESSAGE_TEXT' => $message, + 'success' => $success, + 'delete' => $delete, + 'limit' => $limit ? $this->language->lang('ASS_ITEM_USE_REACHED') : false, + 'id' => $item->get_id(), + 'data' => !empty($data) ? $data : false, + 'file' => !empty($u_file) ? $u_file . '&force=1' : false, + 'item' => !empty($next_item) ? $next_item : false, + 'index' => $index, + ]); + } + else + { + if (!empty($u_file)) + { + return new RedirectResponse($u_file); + } + + return $this->helper->message($message); + } + } + else + { + confirm_box(false, $type->get_language(), '', $type->get_confirm_template($item->get_data()), $this->helper->get_current_url()); + + return new RedirectResponse($this->router->inventory($item->get_category_slug(), $item->get_slug(), $index)); + } + break; + + case 'download': + $hash = $this->request->variable('hash', '', true); + + $data = $type->activate($item->get_data()); + + if (empty($data['file'])) + { + break; + } + + $file = $data['file']; + + if (check_link_hash($hash, $file) && $this->request->is_set('force', \phpbb\request\request_interface::GET)) + { + $count = (int) $row['use_count'] + 1; + $limit = $item->get_count() && $item->get_count() === $count; + $delete = !$item->get_delete_seconds() && $limit; + + $data = [ + 'use_count' => $count, + 'use_time' => time(), + ]; + + $delete + ? $this->operator_inv->delete($row['inventory_id']) + : $this->operator_inv->activated($row['inventory_id'], $data); + + $data['use_time'] = $this->user->format_date($data['use_time']); + + $this->log->add($item, false); + + return $this->download($file); + } + else if ($this->request->is_set('hash', \phpbb\request\request_interface::GET)) + { + $u_file = $this->router->inventory($item->get_category_slug(), $item->get_slug(), $index, 'download', ['hash' => $hash, 'force' => true]); + + $this->template->assign_var('U_DOWNLOAD_FILE', $u_file); + } + break; + + case 'delete': + case 'refund': + $l_action = 'ASS_' . utf8_strtoupper($action); + + if (confirm_box(true)) + { + if ($action === 'refund') + { + if (!empty($row['use_count'])) + { + throw new shop_exception(403, 'ASS_ERROR_NOT_REFUND'); + } + + $points = $this->aps_functions->equate_points($this->user->data['user_points'], $row['purchase_price']); + $points = $this->aps_functions->boundaries($points); + $points = $this->aps_functions->format_points($points); + + $this->aps_distributor->update_points($points); + + $item->set_purchases($item->get_purchases() - 1) + ->set_stock($item->get_stock() + (int) !$item->get_stock_unlimited()) + ->save(); + } + + $this->operator_inv->delete($row['inventory_id']); + + if ($this->request->is_ajax()) + { + $inventory_ids = $item_map[$item->get_id()]; + + unset($inventory_ids[array_search($row['inventory_id'], $inventory_ids)]); + + if (!empty($inventory_ids)) + { + $inventory_ids = array_values($inventory_ids); + + // Get the first inventory row + $row = $inventory[$inventory_ids[0]]; + + $stack = $this->get_stack_info($inventory_ids, $index0); + + $vars = $this->get_inventory_variables($category, $item, $row, $stack); + + if (!empty($vars['BACKGROUND_SRC'])) + { + // Fix the incorrect web root path + $vars['BACKGROUND_SRC'] = $this->operator_item->get_absolute_background_path($vars['BACKGROUND_SRC']); + } + + $this->template->set_filenames(['item' => '@phpbbstudio_ass/ass_item_inventory.html']); + $this->template->assign_vars(['item' => $vars]); + $next_item = $this->template->assign_display('item'); + } + + return new JsonResponse([ + 'MESSAGE_TITLE' => $this->language->lang($l_action), + 'MESSAGE_TEXT' => $this->language->lang($l_action . '_SUCCESS'), + 'id' => $item->get_id(), + 'item' => !empty($next_item) ? $next_item : false, + 'index' => $index, + ]); + } + else + { + return $this->helper->message($l_action . '_SUCCESS'); + } + } + else + { + $body = $this->request->is_ajax() ? '@phpbbstudio_ass/ass_confirm_body.html' : 'confirm_body.html'; + + confirm_box(false, $l_action, '', $body, $this->helper->get_current_url()); + + return new RedirectResponse($this->helper->route('phpbbstudio_ass_inventory')); + } + break; + } + } + + $counts = [ + 'expire' => 0, + 'gifts' => 0, + 'total' => 0, + ]; + + /** @var category $cat */ + foreach ($categories as $cat) + { + $this->template->assign_block_vars('ass_categories', $this->operator_cat->get_variables($cat)); + + /** @var item $it */ + foreach ($items as $it) + { + if ($it->get_category() === $cat->get_id()) + { + $inventory_ids = $item_map[$it->get_id()]; + $inventory_id = $inventory_ids[0]; + + $s_this_item = $s_item && $item->get_id() === $it->get_id(); + + if ($s_this_item) + { + if (isset($inventory_ids[$index0])) + { + $inventory_id = $inventory_ids[$index0]; + } + else + { + throw new shop_exception(404, 'ASS_ERROR_NOT_OWNED_STACK'); + } + } + + // Get the first inventory row + $row = $inventory[$inventory_id]; + + $stack = $this->get_stack_info($inventory_ids, $index0); + + $vars = $this->get_inventory_variables($cat, $it, $row, $stack, $index, $counts); + + $this->template->assign_block_vars('ass_categories.items', $vars); + + if (empty($variables) && $s_this_item) + { + $variables = $vars; + } + } + } + } + + if (!empty($variables['S_TYPE_ERROR'])) + { + $this->log_phpbb->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_ACP_ASS_ITEM_TYPE_ERROR', time(), [$category->get_title(), $item->get_title()]); + } + + $this->template->assign_vars([ + 'ITEM_INFO' => $variables, + + 'COUNT_EXPIRE' => $counts['expire'], + 'COUNT_GIFTS' => $counts['gifts'], + 'COUNT_TOTAL' => $counts['total'], + + 'S_IS_GIFT' => $action === 'gift', + + 'T_SHOP_ICON' => $s_category ? $category->get_icon() : $this->config['ass_inventory_icon'], + + 'L_VIEW_SHOP' => $s_category ? $category->get_title() : $this->language->lang('ASS_SHOP_INDEX'), + 'U_VIEW_SHOP' => $s_category ? $this->router->category($category->get_slug()) : $this->helper->route('phpbbstudio_ass_shop'), + ]); + + return $this->helper->render('ass_inventory.html', $this->language->lang('ASS_INVENTORY')); + } + + /** + * Display the history page. + * + * @param int $page The page number + * @return Response + * @access public + */ + public function history($page) + { + $this->controller->check_shop(); + $this->controller->create_shop_crumbs('history'); + + $points_name = $this->aps_functions->get_name(); + $points_new = $this->language->lang('ASS_POINTS_NEW', $points_name); + $points_old = $this->language->lang('ASS_POINTS_OLD', $points_name); + + $show_array = [ + 'all' => ['title' => 'ASS_ALL', 'sql' => ''], + 'use' => ['title' => 'ASS_USAGES', 'sql' => 'l.item_purchase = 0'], + 'buy' => ['title' => 'ASS_PURCHASES', 'sql' => 'l.item_purchase = 1'], + 'given' => ['title' => 'ASS_GIFTS_GIVEN', 'sql' => 'l.item_purchase = 1 AND l.recipient_id <> 0'], + 'received' => ['title' => 'ASS_GIFTS_RECEIVED', 'sql' => 'l.recipient_id = ' . (int) $this->user->data['user_id']], + ]; + $sort_array = [ + 'time' => ['title' => 'TIME', 'sql' => 'l.log_time'], + 'old' => ['title' => $points_old, 'sql' => 'l.points_old'], + 'new' => ['title' => $points_new, 'sql' => 'l.points_new'], + 'price' => ['title' => 'ASS_ITEM_PRICE', 'sql' => 'l.points_sum'], + 'item' => ['title' => 'ASS_ITEM_TITLE', 'sql' => 'i.item_title'], + 'category' => ['title' => 'ASS_CATEGORY_TITLE', 'sql' => 'c.category_title'], + 'recipient' => ['title' => 'ASS_RECIPIENT_NAME', 'sql' => 'recipient_name'], + ]; + $dir_array = [ + 'desc' => ['title' => 'DESCENDING', 'sql' => 'DESC'], + 'asc' => ['title' => 'ASCENDING', 'sql' => 'ASC'], + ]; + + $show = $this->request->variable('display', 'all', true, \phpbb\request\request_interface::GET); + $sort = $this->request->variable('sort', 'time', true, \phpbb\request\request_interface::GET); + $dir = $this->request->variable('direction', 'desc', true, \phpbb\request\request_interface::GET); + + $show = in_array($show, array_keys($show_array)) ? $show : 'all'; + $sort = in_array($sort, array_keys($sort_array)) ? $sort : 'time'; + $dir = in_array($dir, array_keys($dir_array)) ? $dir : 'desc'; + + $sql_where = $show_array[$show]['sql']; + $sql_order = $sort_array[$sort]['sql']; + $sql_dir = $dir_array[$dir]['sql']; + + $limit = (int) $this->config['ass_logs_per_page']; + $start = ($page - 1) * $limit; + + $total = $this->log->get_user_logs_count($sql_where, $this->user->data['user_id']); + $rowset = $this->log->get_user_logs($sql_where, $sql_order, $sql_dir, $limit, $start, $this->user->data['user_id']); + + $categories = $this->operator_cat->get_categories_by_id(array_column($rowset, 'category_id')); + $items = $this->operator_item->get_items_by_id(array_column($rowset, 'item_id')); + + foreach ($rowset as $row) + { + $category_id = (int) $row['category_id']; + $item_id = (int) $row['item_id']; + + /** @var category $category */ + $category = !empty($categories[$category_id]) ? $categories[$category_id] : null; + + /** @var item $item */ + $item = !empty($items[$item_id]) ? $items[$item_id] : null; + + /** @var item_type $type */ + $type = $item ? $this->items_manager->get_type($item->get_type()) : null; + + $this->template->assign_block_vars('ass_logs', [ + 'CATEGORY_TITLE' => $category ? $category->get_title() : $this->language->lang('ASS_UNAVAILABLE_CATEGORY'), + 'ITEM_TITLE' => $item ? $item->get_title() : $this->language->lang('ASS_UNAVAILABLE_ITEM'), + + 'LOG_ACTION' => $type ? $this->language->lang($type->get_language('log')) : $this->language->lang('ASS_UNAVAILABLE_' . (!$item ? 'ITEM' : 'TYPE')), + 'LOG_ID' => $row['log_id'], + 'LOG_IP' => $row['log_ip'], + 'LOG_TIME' => $this->user->format_date($row['log_time']), + + 'POINTS_NEW' => $row['points_new'], + 'POINTS_OLD' => $row['points_old'], + 'POINTS_SUM' => -$row['points_sum'], + + 'RECIPIENT' => $row['recipient_id'] ? get_username_string('full', $row['recipient_id'], $row['recipient_name'], $row['recipient_colour']) : '', + 'USER' => get_username_string('full', $row['user_id'], $row['username'], $row['user_colour']), + + 'S_PURCHASE' => $row['item_purchase'], + 'S_GIFT_RECEIVE' => $row['recipient_id'] == $this->user->data['user_id'], + + 'U_CATEGORY' => $category ? $this->router->category($category->get_slug()) : '', + 'U_ITEM' => $item ? $this->router->item($item->get_category_slug(), $item->get_slug()) : '', + ]); + } + + $this->pagination->generate_template_pagination([ + 'routes' => ['phpbbstudio_ass_history', 'phpbbstudio_ass_history_pagination'], + 'params' => ['display' => $show, 'sort' => $sort, 'direction' => $dir], + ], 'shop_pagination', 'page', $total, $limit, $start); + + $this->template->assign_vars([ + 'SORT_DISPLAY' => $show, + 'SORT_DISPLAY_ARRAY' => $show_array, + 'SORT_SORT' => $sort, + 'SORT_SORT_ARRAY' => $sort_array, + 'SORT_DIR' => $dir, + 'SORT_DIR_ARRAY' => $dir_array, + + 'TOTAL_LOGS' => $this->language->lang('TOTAL_LOGS', $total), + + 'S_ASS_INVENTORY' => true, + ]); + + return $this->helper->render('ass_history.html', $this->language->lang('ASS_INVENTORY')); + } + + /** + * Create a download response for a specific file. + * + * @param string $file The file name + * @return Response + * @access protected + */ + protected function download($file) + { + $response = new Response($file); + + $response->headers->set('Content-type', 'application/octet-stream'); + $response->headers->set('Content-Disposition', 'attachment; filename="' . basename($file) . '";'); + $response->headers->set('Content-length', filesize($file)); + + $response->sendHeaders(); + $response->setContent(readfile($file)); + + return $response; + } + + /** + * Get an inventory item's template variables. + * + * @param category $category The category entity + * @param item $item The item entity + * @param array $row The inventory row + * @param array $stack The item stack information + * @param int $index The item index + * @param array $counts The inventory overview counts + * @return array The inventory item template variables + * @access protected + */ + protected function get_inventory_variables(category $category, item $item, array $row, array $stack, $index = 1, array &$counts = []) + { + /** @var item_type $type */ + $type = $this->items_manager->get_type($item->get_type()); + + $s_type = $type !== null; + + if ($s_type) + { + $type->set_category($category); + $type->set_item($item); + } + + $s_has_expired = $this->time->has_expired($row['purchase_time'], $item->get_expire_seconds()); + $s_will_expire = $this->time->will_expire($row['purchase_time'], $item->get_expire_seconds()); + + if (!empty($counts)) + { + $counts = [ + 'expire' => !$s_has_expired && $s_will_expire ? $counts['expire'] + 1 : $counts['expire'], + 'gifts' => $row['gifter_id'] ? $counts['gifts'] + 1 : $counts['gifts'], + 'total' => $counts['total'] + $stack['count'], + ]; + } + + return array_merge($this->operator_item->get_variables($item, '', true, $index), [ + 'ACTIVATE' => $s_type ? $this->language->lang($type->get_language('action')) : '', + 'GIFTER_NAME' => $row['gifter_id'] ? get_username_string('full', $row['gifter_id'], $row['gifter_name'], $row['gifter_colour']) : '', + 'PURCHASE_UNIX' => (int) $row['purchase_time'], + 'STACK_COUNT' => (int) $stack['count'], + 'USE_COUNT' => (int) $row['use_count'], + 'USE_UNIX' => (int) $row['use_time'], + + 'S_AJAX' => $s_type ? $type->get_confirm_ajax() : '', + 'S_GIFTED' => !empty($row['gifter_id']), + 'S_LIMIT' => $item->get_count() && (int) $row['use_count'] >= $item->get_count(), + 'S_REFUND' => $item->get_refund_seconds() && !$row['use_count'] ? (int) $row['purchase_time'] + $item->get_refund_seconds() > time() : false, + 'S_HAS_EXPIRED' => $s_has_expired, + 'S_WILL_EXPIRE' => $s_will_expire, + 'S_TYPE_ERROR' => !$s_type, + + 'U_STACK_NEXT' => $stack['next'] ? $this->router->inventory($item->get_category_slug(), $item->get_slug(), $stack['next']) : '', + 'U_STACK_PREV' => $stack['prev'] ? $this->router->inventory($item->get_category_slug(), $item->get_slug(), $stack['prev']) : '', + ]); + } + + /** + * Calculate an inventory item's stacking information. + * + * @param array $array The inventory identifiers + * @param int $index The current index (0 based) + * @return array The stacking information + * @access protected + */ + protected function get_stack_info(array $array, $index) + { + // The amount of inventory items for this specific item + $count = count($array); + + // Whether or not the current item index is the first or the last + $prev = $index !== 0 ? $index - 1 : false; + $next = $index < ($count - 1) ? $index + 1 : false; + + /** + * Because the array with inventory identifiers is 0-based, + * but we use a 1-based approach for routes, + * we have to increment the previous and next indices by 1. + */ + $prev = $prev !== false ? $prev + 1 : 0; + $next = $next !== false ? $next + 1 : 0; + + return [ + 'count' => (int) $count, + 'next' => (int) $next, + 'prev' => (int) $prev, + ]; + } +} diff --git a/ext/phpbbstudio/ass/controller/shop_controller.php b/ext/phpbbstudio/ass/controller/shop_controller.php new file mode 100644 index 0000000..959518a --- /dev/null +++ b/ext/phpbbstudio/ass/controller/shop_controller.php @@ -0,0 +1,333 @@ +config = $config; + $this->controller = $controller; + $this->db = $db; + $this->helper = $helper; + $this->items_manager = $items_manager; + $this->language = $language; + $this->operator_cat = $operator_cat; + $this->operator_item = $operator_item; + $this->pagination = $pagination; + $this->request = $request; + $this->template = $template; + } + + /** + * Display the shop index. + * + * @return Response + * @access public + */ + public function shop() + { + $this->controller->check_shop(); + $this->controller->create_shop('shop'); + + $this->controller->setup_carousel(); + $this->controller->setup_panels(); + + $panels = [ + 'limited' => ['carousel' => true, 'title' => 'ASS_ITEMS_LIMITED'], + 'recent' => ['carousel' => true, 'title' => 'ASS_ITEMS_RECENT'], + 'sale' => ['carousel' => true, 'title' => 'ASS_SALE_ITEMS'], + 'featured' => ['carousel' => true, 'title' => 'ASS_FEATURED_ITEMS'], + 'featured_sale' => ['carousel' => false], + 'random' => ['carousel' => false], + ]; + + uksort($panels, function($a, $b) + { + if ($this->config["ass_panel_{$a}_order"] == $this->config["ass_panel_{$b}_order"]) + { + return 0; + } + + return $this->config["ass_panel_{$a}_order"] < $this->config["ass_panel_{$b}_order"] ? -1 : 1; + }); + + foreach (array_keys($panels) as $panel) + { + if ($this->config["ass_panel_{$panel}_limit"]) + { + $this->operator_item->assign_specific_items($panel, $this->config["ass_panel_{$panel}_limit"]); + } + } + + $this->template->assign_vars(['ass_panels' => $panels]); + + return $this->helper->render('ass_shop.html', $this->language->lang('ASS_SHOP')); + } + + /** + * Display a shop category. + * + * @param string $category_slug The category slug + * @param int $page The page number + * @return Response + * @access public + */ + public function category($category_slug, $page = 1) + { + $this->controller->check_shop(); + + $category = $this->operator_cat->load_entity($category_slug); + + $this->controller->create_shop('shop', $category); + + $this->controller->setup_panels(); + + $this->template->assign_vars($this->operator_cat->get_variables($category)); + + $sql_where = ''; + + $types = [0 => null]; + $type_array = ['ASS_ALL']; + + foreach($this->operator_item->get_item_types($category->get_id()) as $type) + { + $types[] = $type; + $type_array[] = $this->items_manager->get_type($type)->get_language('title'); + } + + $params_array = [ + 'above' => ['default' => '', 'sql' => 'i.item_price > {VALUE}'], + 'below' => ['default' => '', 'sql' => 'i.item_price < {VALUE}'], + 'gift' => ['default' => 0, 'sql' => 'i.item_gift = {VALUE}'], + 'sale' => ['default' => 0, 'sql' => 'i.item_sale_start < ' . time() . ' AND i.item_sale_until < ' . time()], + 'type' => ['default' => 0, 'sql' => 'i.item_type = {VALUE}'], + 'title' => ['default' => '', 'sql' => $this->db->sql_lower_text('i.item_title') . ' {VALUE}', 'mb' => true], + ]; + $days_array = [ + 0 => 'ASS_ALL', + 1 => '1_DAY', + 7 => '7_DAYS', + 14 => '2_WEEKS', + 30 => '1_MONTH', + 90 => '3_MONTHS', + 180 => '6_MONTHS', + 365 => '1_YEAR', + ]; + $sort_array = [ + 'order' => ['title' => 'ASS_ITEM_ORDER', 'sql' => 'i.item_order'], + 'item' => ['title' => 'ASS_ITEM_TITLE', 'sql' => 'i.item_title'], + 'price' => ['title' => 'ASS_ITEM_PRICE', 'sql' => 'i.item_price'], + 'stock' => ['title' => 'ASS_ITEM_STOCK', 'sql' => 'i.item_stock'], + 'time' => ['title' => 'ASS_ITEM_CREATE_TIME', 'sql' => 'i.item_create_time'], + ]; + $dir_array = [ + 'desc' => ['title' => 'DESCENDING', 'sql' => 'DESC'], + 'asc' => ['title' => 'ASCENDING', 'sql' => 'ASC'], + ]; + + $days = $this->request->variable('days', 0, false, \phpbb\request\request_interface::GET); + $sort = $this->request->variable('sort', 'order', true, \phpbb\request\request_interface::GET); + $dir = $this->request->variable('direction', 'asc', true, \phpbb\request\request_interface::GET); + + $dir = in_array($dir, array_keys($dir_array)) ? $dir : 'asc'; + $sort = in_array($sort, array_keys($sort_array)) ? $sort : 'order'; + $days = in_array($days, array_keys($days_array)) ? $days : 0; + $time = $days * \phpbbstudio\ass\helper\time::DAY; + + $params = [ + 'sort' => $sort, + 'direction' => $dir, + ]; + + if ($time) + { + $params['days'] = $days; + + $sql_where .= ' AND i.item_create_time > ' . (time() - $time); + } + + foreach ($params_array as $key => $param) + { + $value = $this->request->variable( + $key, + $params_array[$key]['default'], + !empty($params_array[$key]['mb']), + \phpbb\request\request_interface::GET + ); + + if (!empty($value)) + { + $params[$key] = $value; + + $value_sql = $value !== -1 ? $value : 0; + + switch ($key) + { + case 'type': + if (in_array($value, array_keys($type_array))) + { + $value_sql = "'" . $types[$value] . "'"; + } + break; + + case 'title': + $value_sql = $this->db->sql_like_expression(utf8_strtolower($value_sql) . $this->db->get_any_char()); + break; + } + + $param_sql = str_replace('{VALUE}', $value_sql, $params_array[$key]['sql']); + + $sql_where .= ' AND ' . $param_sql; + + $this->template->assign_var('SORT_' . utf8_strtoupper($key), $value); + } + } + + $sql_dir = $dir_array[$dir]['sql']; + $sql_order = $sort_array[$sort]['sql']; + + $limit = (int) $this->config['ass_items_per_page']; + $start = ($page - 1) * $limit; + + $total = $this->operator_item->get_item_count($category->get_id()); + $items = $this->operator_item->get_items($category->get_id(), $sql_where, $sql_order, $sql_dir, true, $limit, $start); + + foreach ($items as $item) + { + $this->template->assign_block_vars('ass_items', $this->operator_item->get_variables($item)); + } + + $this->pagination->generate_template_pagination([ + 'routes' => ['phpbbstudio_ass_category', 'phpbbstudio_ass_category_pagination'], + 'params' => array_merge(['category_slug' => $category->get_slug()], $params), + ], 'shop_pagination', 'page', $total, $limit, $start); + + $this->template->assign_vars([ + 'ITEMS_COUNT' => $this->language->lang('ASS_ITEMS_COUNT', $total), + + 'SORT_DAYS' => $days, + 'SORT_DAYS_ARRAY' => $days_array, + 'SORT_DIR' => $dir, + 'SORT_DIR_ARRAY' => $dir_array, + 'SORT_SORT' => $sort, + 'SORT_SORT_ARRAY' => $sort_array, + 'SORT_TYPE_ARRAY' => $type_array, + + 'T_PANEL_SIZE' => $limit < 8 ? 6 : 3, + ]); + + return $this->helper->render('ass_category.html', $category->get_title()); + } + + /** + * Display a shop item. + * + * @param string $category_slug The category slug + * @param string $item_slug The item slug + * @return Response + * @access public + */ + public function item($category_slug, $item_slug) + { + $this->controller->check_shop(); + + $category = $this->operator_cat->load_entity($category_slug); + $item = $this->operator_item->load_entity($item_slug, $category->get_slug(), $category->get_id()); + + if (!$this->operator_item->is_available($item)) + { + throw new shop_exception(410, 'ASS_ERROR_NOT_AVAILABLE'); + } + + $this->controller->create_shop('shop', $category, $item); + + $this->controller->setup_carousel(); + $this->controller->setup_panels(); + + if ($item->get_related_enabled()) + { + $this->operator_item->assign_related_items($item); + } + + $this->template->assign_vars($this->operator_item->get_variables($item)); + + return $this->helper->render('ass_item.html', $item->get_title()); + } +} diff --git a/ext/phpbbstudio/ass/docs/.htaccess b/ext/phpbbstudio/ass/docs/.htaccess new file mode 100644 index 0000000..4128d34 --- /dev/null +++ b/ext/phpbbstudio/ass/docs/.htaccess @@ -0,0 +1,4 @@ + + Order Allow,Deny + Deny from All + diff --git a/ext/phpbbstudio/ass/docs/CHANGELOG.md b/ext/phpbbstudio/ass/docs/CHANGELOG.md new file mode 100644 index 0000000..fcddb5a --- /dev/null +++ b/ext/phpbbstudio/ass/docs/CHANGELOG.md @@ -0,0 +1,79 @@ +# phpBB Studio - Advanced Shop System + +#### v1.1.4-RC on 11-03-2020 +- Fixed ACP slider settings +- Fixed ACP Logs pagination +- Fixed special characters displaying incorrectly +- Added phpBB 3.3 compatibility + +#### v1.1.3-RC1 on 20-12-2019 +- Entered the stage features frozen. +- Fixed a bug in the shop blocks +- Fixed the wrong count in shop user blocks +- Added an option to determine where the Shop link shows up +- Fixed a bug where the ACP Inventory would not use the correct user +- Fixed a bug where the gift notification would throw a warning +- Added notifications for ACP Inventory gifts + +#### v1.1.2-beta on 01-12-2019 +- Fixed ACP "Overview" spenders block +- Fixed a bug where sale or featured items would not show up +- Added automatic cache purge option after file uploads +- Added shop blocks to the APS display page +- Added version checker + +#### v1.1.1-beta +- Fixed users being able to gift themselves _(now for real)_ +- Fixed pressing enter causing the page to reload instead of submitting the gift form +- Fixed redundant `=` in an HTML class attribute +- Fixed points distribution, now all goes through Advanced Points System +- Fixed the permission for managing the ACP Inventory not showing up +- Fixed entity import names to prevent warnings on lower PHP versions +- Fixed “File” item type not downloading when immediately deleted +- Fixed double slashes (`//`) in shop folders' and files' paths +- Added additional checks to file functions to ensure file existance +- Added style template events +- Added item stacking + - Multiple of the same item can now be in a user's inventory + - Only when the item is allowed to be stacked by the Administrator + - The maximum item stack is configurable on a per item basis + - Users need the permission to be authorised to stack items + - Full explanation added to the “Take the tour” upon item creation +- Extended index panels configuration + - Panels can now be disabled by giving them a limit of 0 + - Panels can now be ordered, determining in which order they are displayed + - Panels width is now also configurable by the administrator + +#### v1.1.0-beta +- Fixed shop index’ random items not being random +- Fixed category’s items displaying in reverse order +- Fixed inactive category’s items still being visible / usable. +- Fixed ACP item description’s colour palette + - No longer interferes with the “Create a group” item type palette + - Now responsive and properly resizes on different screen sizes +- Fixed ACP item prices values + - Proper minimum and maximum values have been added to the HTML + - PHP logic has been added in order to check the respective values and show errors +- Fixed ACP item gift percentage, it can now also be negative _(-100% / +999%)_ +- Fixed users being able to gift themselves +- Added ACP item _“Gift only”_ configuration + - Items can be configured so they can only be gifted to an other user and not purchased for personal use + - Please note, if _Gift only_ is set to Yes, but _Can be gifted_ is set to No, no actions will be shown in the shop +- Added ACP item _“Availability”_ configuration + - Items can be configured to only be available within a certain time + - If the item is no longer available, it will not show up in the shop, but it will still show up in users’ inventories +- Added ACP item _“Related items”_ configuration + - Related items can now be toggled on/off + - Upto 8 items can be selected to be related to this item + - If no items are selected, it will show the 4 closest ordered items +- Added ACP item _copy_ functionality + - Items can now be copied, duplicated, to easily create similar items within a single category +- Added ACP Files module back link +- Added ACP Inventory module + - Administrators can now manage users’ inventory + - Either globally add/delete items from user(s) and/or group(s) at once + - Or adding/deleting items from a single user’s inventory +- Enhanced ACP select boxes, they now use _Select2.js_ + +#### v1.0.0-beta + - first release diff --git a/ext/phpbbstudio/ass/docs/EVENTS.txt b/ext/phpbbstudio/ass/docs/EVENTS.txt new file mode 100644 index 0000000..1bccb91 --- /dev/null +++ b/ext/phpbbstudio/ass/docs/EVENTS.txt @@ -0,0 +1,19 @@ +/** + * Advanced Shop System extension © Copyright phpBB Studio 2019 + * https://www.phpbbstudio.com + * + * ASS is a free extension for the phpBB Forum Software Package. + * 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 extension 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 license.txt file. + * The same can be viewed at + */ + +No events required while using phpBB equal or greater than 3.2.7 diff --git a/ext/phpbbstudio/ass/docs/FEATURES.md b/ext/phpbbstudio/ass/docs/FEATURES.md new file mode 100644 index 0000000..05cbc31 --- /dev/null +++ b/ext/phpbbstudio/ass/docs/FEATURES.md @@ -0,0 +1,8 @@ +# phpBB Studio - Advanced Shop System + +- Fully integrated Shop System for the [Advanced Points System](https://github.com/phpBB-Studio/AdvancedPointsSystem) +- Ease-of-use filesystem interface for item images and files. +- High customisation options and possibilities per item. +- Several item types included by default
                *(Ranks and Files included by default)* +- More item types are available for expanding. +- Fully extendable by other extensions.
                *(with detailed explanation and examples)* diff --git a/ext/phpbbstudio/ass/docs/README.md b/ext/phpbbstudio/ass/docs/README.md new file mode 100644 index 0000000..264511e --- /dev/null +++ b/ext/phpbbstudio/ass/docs/README.md @@ -0,0 +1,86 @@ +

                Advanced Shop System

                +

                An extension for the phpBB Forum Software.

                + +

                + GPLv2 License +

                + +## Table of Contents +> - [Install](#install) +> - [Uninstall](#uninstall) +> - [Support](#support) +> - [Translations](#translations) +> - [Features](#features) +> - [Other items](#other-items) +> - [Other extensions](#other-extensions) +> - [Extending Advanced Shop System](#extending-advanced-shop-system) +> - [You might also like](#you-might-also-like) +> - [License](#license) + +## Install +1. Download the latest validated release +2. Unzip the downloaded release and copy it to the `ext` directory of your phpBB board. +3. Navigate in the ***ACP*** to `Customise » Extension management » Manage extensions`. +4. Look for `phpBB Studio - Advanced Shop System` under the **Disabled Extensions** list, and click its **`Enable`** link. +5. Set up and configure `Advanced Shop System` by navigating in the ***ACP*** to `Extensions » Advanced Shop System`. + +> *Read more about [installing phpBB Extensions](https://www.phpbb.com/extensions/installing/#installing).* + +## Uninstall +1. Navigate in the ***ACP*** to `Customise » Extension management » Manage extensions`. +2. Look for `phpBB Studio - Advanced Shop System` under the **Enabled Extensions** list, and click its **`Disable`** link. +3. To permanently uninstall, click **`Delete Data`** and then delete the `/ext/phpbbstudio/ass` directory. + +> *Read more about [uninstalling phpBB Extensions](https://www.phpbb.com/extensions/installing/#removing).* + +## Support +- **Important: Only official release versions validated by the phpBB Extensions Team should be installed on a live forum. Pre-release (beta, RC) versions downloaded from this repository are only to be used for testing on offline/development forums and are not officially supported.** +- Report bugs and other issues to our **[Issue Tracker](https://github.com/phpBB-Studio/AdvancedShopSystem/issues)**. +- Support requests can be posted and discussed in the **[Extension support](https://phpbbstudio.com/viewforum.php?f=5)** forum over at the [phpBB Studio](https://www.phpbbstudio.com). +- Support requests can be posted and discussed in the **[Development topic](https://www.phpbb.com/community/viewforum.php?f=456)** over at [phpBB.com](https://www.phpbb.com). + +## Translations +- Translations should be posted in the corresponding forum in **[Extension support](https://phpbbstudio.com/viewforum.php?f=5)** over at the [phpBB Studio](https://www.phpbbstudio.com). +- Each translation should be created in a **separate** topic. +- The topic should either contain a **zip archive** as an attachment or a link to your **GitHub repository**. +- Translations should ***not*** be posted in the Development topic over at [phpBB.com](https://www.phpbb.com). +- Translations should ***not*** be created as Pull Requests over at the [GitHub](https://github.com/phpBB-Studio/) repository. + +## Features +- Fully integrated Shop System for the [Advanced Points System](https://github.com/phpBB-Studio/AdvancedPointsSystem) +- Ease-of-use filesystem interface for item images and files. +- High customisation options and possibilities per item. +- Several item types included by default
                *(Points and Files included by default)* +- More item types are available for expanding. +- Fully extendable by other extensions.
                *(with detailed explanation and examples)* + +## Other items +- [Rank](https://phpbbstudio.com/extensions/advanced-shop-system-rank) +- [Avatar](https://phpbbstudio.com/extensions/advanced-shop-system-avatar) +- [Username](https://phpbbstudio.com/extensions/advanced-shop-system-username) +- [Groups](https://phpbbstudio.com/extensions/advanced-shop-system-groups) *(Create a group and Join a group)* +- [Permissions](https://phpbbstudio.com/extensions/advanced-shop-system-permissions) *(Global permissions and Local permissions)* + +## Other extensions +- [Advanced Points System](https://github.com/phpBB-Studio/AdvancedPointsSystem) +- [Advanced Points System · Purchases](https://phpbbstudio.com/extensions/advanced-points-system-purchases) +- [Advanced Points System · Auto Groups](https://github.com/phpBB-Studio/AdvancedShopSystemAutoGroups) + +## Extending Advanced Shop System +For the extension developers amongst us, we have written a comprehensive Wiki that should describe everything in detail. +This can be read over at [Extending Advanced Shop System](https://github.com/phpBB-Studio/AdvancedShopSystem/wiki/Extending-ASS). If there are still any questions, feel free to ask. + +## You might also like +- Dice Rolls +- Highlight Posts +- Who Read What +- Sub Global Topic +- Topic Cement Style +- Topic Events + + +## License +GNU General Public License, version 2 ([GPLv2](../license.txt)). + +--- +> [phpbbstudio.com](https://www.phpbbstudio.com) · GitHub [phpbb-studio](https://github.com/phpbb-studio/) · phpBB [3Di](https://www.phpbb.com/community/memberlist.php?mode=viewprofile&u=177467) / [mrgoldy](https://www.phpbb.com/community/memberlist.php?mode=viewprofile&u=1114105) diff --git a/ext/phpbbstudio/ass/docs/images/ass.png b/ext/phpbbstudio/ass/docs/images/ass.png new file mode 100644 index 0000000..a596e5f Binary files /dev/null and b/ext/phpbbstudio/ass/docs/images/ass.png differ diff --git a/ext/phpbbstudio/ass/docs/images/dice_rolls.png b/ext/phpbbstudio/ass/docs/images/dice_rolls.png new file mode 100644 index 0000000..881f732 Binary files /dev/null and b/ext/phpbbstudio/ass/docs/images/dice_rolls.png differ diff --git a/ext/phpbbstudio/ass/docs/images/highlight_posts.png b/ext/phpbbstudio/ass/docs/images/highlight_posts.png new file mode 100644 index 0000000..3373c3e Binary files /dev/null and b/ext/phpbbstudio/ass/docs/images/highlight_posts.png differ diff --git a/ext/phpbbstudio/ass/docs/images/shop1.png b/ext/phpbbstudio/ass/docs/images/shop1.png new file mode 100644 index 0000000..90c3285 Binary files /dev/null and b/ext/phpbbstudio/ass/docs/images/shop1.png differ diff --git a/ext/phpbbstudio/ass/docs/images/shop2.png b/ext/phpbbstudio/ass/docs/images/shop2.png new file mode 100644 index 0000000..94f6e2f Binary files /dev/null and b/ext/phpbbstudio/ass/docs/images/shop2.png differ diff --git a/ext/phpbbstudio/ass/docs/images/shop3.png b/ext/phpbbstudio/ass/docs/images/shop3.png new file mode 100644 index 0000000..3c6f1bc Binary files /dev/null and b/ext/phpbbstudio/ass/docs/images/shop3.png differ diff --git a/ext/phpbbstudio/ass/docs/images/shop4.png b/ext/phpbbstudio/ass/docs/images/shop4.png new file mode 100644 index 0000000..4c0c8a3 Binary files /dev/null and b/ext/phpbbstudio/ass/docs/images/shop4.png differ diff --git a/ext/phpbbstudio/ass/docs/images/shop5.png b/ext/phpbbstudio/ass/docs/images/shop5.png new file mode 100644 index 0000000..4417bab Binary files /dev/null and b/ext/phpbbstudio/ass/docs/images/shop5.png differ diff --git a/ext/phpbbstudio/ass/docs/images/subglobal_topic.png b/ext/phpbbstudio/ass/docs/images/subglobal_topic.png new file mode 100644 index 0000000..84c1ce3 Binary files /dev/null and b/ext/phpbbstudio/ass/docs/images/subglobal_topic.png differ diff --git a/ext/phpbbstudio/ass/docs/images/topic_cement.png b/ext/phpbbstudio/ass/docs/images/topic_cement.png new file mode 100644 index 0000000..141dd84 Binary files /dev/null and b/ext/phpbbstudio/ass/docs/images/topic_cement.png differ diff --git a/ext/phpbbstudio/ass/docs/images/topic_events.png b/ext/phpbbstudio/ass/docs/images/topic_events.png new file mode 100644 index 0000000..55c9cd2 Binary files /dev/null and b/ext/phpbbstudio/ass/docs/images/topic_events.png differ diff --git a/ext/phpbbstudio/ass/docs/images/who_read_what.png b/ext/phpbbstudio/ass/docs/images/who_read_what.png new file mode 100644 index 0000000..8b0267f Binary files /dev/null and b/ext/phpbbstudio/ass/docs/images/who_read_what.png differ diff --git a/ext/phpbbstudio/ass/docs/index.html b/ext/phpbbstudio/ass/docs/index.html new file mode 100644 index 0000000..f766ef4 --- /dev/null +++ b/ext/phpbbstudio/ass/docs/index.html @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/ext/phpbbstudio/ass/entity/category.php b/ext/phpbbstudio/ass/entity/category.php new file mode 100644 index 0000000..69935c2 --- /dev/null +++ b/ext/phpbbstudio/ass/entity/category.php @@ -0,0 +1,345 @@ +db = $db; + $this->parser = $parser; + $this->renderer = $renderer; + $this->utils = $utils; + + $this->categories_table = $categories_table; + } + + /** + * {@inheritDoc} + */ + public function load($id, $slug = '') + { + $where = $id <> 0 ? 'category_id = ' . (int) $id : "category_slug = '" . $this->db->sql_escape($slug) . "'"; + + $sql = 'SELECT * FROM ' . $this->categories_table . ' WHERE ' . $where; + $result = $this->db->sql_query($sql); + $this->data = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if ($this->data === false) + { + throw new runtime_exception('ASS_ERROR_NOT_FOUND'); + } + + return $this; + } + + /** + * {@inheritDoc} + */ + public function import(array $data) + { + $this->data = $data; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function save() + { + if (empty($this->data['category_id'])) + { + throw new runtime_exception('ASS_ERROR_NOT_EXISTS'); + } + + $data = array_diff_key($this->data, ['category_id' => null]); + + $sql = 'UPDATE ' . $this->categories_table . ' + SET ' . $this->db->sql_build_array('UPDATE', $data) . ' + WHERE category_id = ' . $this->get_id(); + $this->db->sql_query($sql); + + return $this; + } + + /** + * {@inheritDoc} + */ + public function insert() + { + if (!empty($this->data['category_id'])) + { + throw new runtime_exception('ASS_ERROR_ALREADY_EXISTS'); + } + + $sql = 'SELECT COALESCE(MAX(category_order), 0) as category_order FROM ' . $this->categories_table; + $result = $this->db->sql_query($sql); + $order = (int) $this->db->sql_fetchfield('category_order'); + $this->db->sql_freeresult($result); + + $this->data['category_order'] = ++$order; + + $sql = 'INSERT INTO ' . $this->categories_table . ' ' . $this->db->sql_build_array('INSERT', $this->data); + $this->db->sql_query($sql); + + $this->data['category_id'] = (int) $this->db->sql_nextid(); + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_id() + { + return isset($this->data['category_id']) ? (int) $this->data['category_id'] : 0; + } + + /** + * {@inheritDoc} + */ + public function get_title() + { + return isset($this->data['category_title']) ? (string) $this->data['category_title'] : ''; + } + + /** + * {@inheritDoc} + */ + public function set_title($title) + { + $title = (string) $title; + + if ($title === '') + { + throw new runtime_exception('ASS_ERROR_TOO_SHORT', ['TITLE', 0, 0]); + } + + if (($length = utf8_strlen($title)) > 255) + { + throw new runtime_exception('ASS_ERROR_TOO_LONG', ['TITLE', 255, $length]); + } + + $this->data['category_title'] = $title; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_slug() + { + return isset($this->data['category_slug']) ? (string) $this->data['category_slug'] : ''; + } + + /** + * {@inheritDoc} + */ + public function set_slug($slug) + { + $slug = (string) $slug; + + if ($slug === '') + { + throw new runtime_exception('ASS_ERROR_TOO_SHORT', ['SLUG', 0, 0]); + } + + if (($length = utf8_strlen($slug)) > 255) + { + throw new runtime_exception('ASS_ERROR_TOO_LONG', ['SLUG', 255, $length]); + } + + // Route should not contain any unexpected special characters + if (!preg_match('/^[^!"#$%&*\'()+,.\/\\\\:;<=>?@\\[\\]^`{|}~ ]*$/', $slug)) + { + throw new runtime_exception('ASS_ERROR_ILLEGAL_CHARS', ['SLUG']); + } + + // Routes must be unique + if (!$this->get_id() || ($this->get_id() && $this->get_slug() !== '' && $this->get_slug() !== $slug)) + { + $sql = 'SELECT category_title + FROM ' . $this->categories_table . " + WHERE category_slug = '" . $this->db->sql_escape($slug) . "' + AND category_id <> " . $this->get_id(); + $result = $this->db->sql_query_limit($sql, 1); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if ($row !== false) + { + throw new runtime_exception('ASS_ERROR_NOT_UNIQUE', ['SLUG', $row['category_title']]); + } + } + + $this->data['category_slug'] = $slug; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_icon() + { + return isset($this->data['category_icon']) ? (string) $this->data['category_icon'] : ''; + } + + /** + * {@inheritDoc} + */ + public function set_icon($icon) + { + $icon = (string) $icon; + + if ($icon === '') + { + throw new runtime_exception('ASS_ERROR_TOO_SHORT', ['ICON', 0, 0]); + } + + $this->data['category_icon'] = $icon; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_desc_for_display() + { + return isset($this->data['category_desc']) ? (string) $this->renderer->render(htmlspecialchars_decode($this->data['category_desc'], ENT_COMPAT)) : ''; + } + + /** + * {@inheritDoc} + */ + public function get_desc() + { + return isset($this->data['category_desc']) ? (string) $this->utils->unparse($this->data['category_desc']) : ''; + } + + /** + * {@inheritDoc} + */ + public function set_desc($desc) + { + $desc = (string) $desc; + + $desc = $this->parser->parse($desc); + + $this->data['category_desc'] = $desc; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_active() + { + return isset($this->data['category_active']) ? (bool) $this->data['category_active'] : true; + } + + /** + * {@inheritDoc} + */ + public function set_active($active) + { + $active = (bool) $active; + + $this->data['category_active'] = $active; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_order() + { + return isset($this->data['category_order']) ? (int) $this->data['category_order'] : 0; + } + + /** + * {@inheritDoc} + */ + public function set_order($order) + { + $order = (int) $order; + + $this->data['category_order'] = $order; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_conflicts() + { + return isset($this->data['item_conflicts']) ? (int) $this->data['item_conflicts'] : 0; + } + + /** + * {@inheritDoc} + */ + public function set_conflicts($conflicts) + { + $conflicts = (int) $conflicts; + + $this->data['item_conflicts'] = $conflicts; + + return $this; + } +} diff --git a/ext/phpbbstudio/ass/entity/category_interface.php b/ext/phpbbstudio/ass/entity/category_interface.php new file mode 100644 index 0000000..e861a66 --- /dev/null +++ b/ext/phpbbstudio/ass/entity/category_interface.php @@ -0,0 +1,193 @@ +config = $config; + $this->db = $db; + $this->parser = $parser; + $this->renderer = $renderer; + $this->time = $time; + $this->utils = $utils; + + $this->items_table = $items_table; + } + + /** + * {@inheritDoc} + */ + public function load($id, $slug = '', $category_id = 0) + { + $where = ($id <> 0) ? 'item_id = ' . (int) $id : 'category_id = ' . (int) $category_id . " AND item_slug = '" . $this->db->sql_escape($slug) . "'"; + + $sql = 'SELECT * FROM ' . $this->items_table . ' WHERE ' . $where; + $result = $this->db->sql_query($sql); + $this->data = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if ($this->data === false) + { + throw new runtime_exception('ASS_ERROR_NOT_FOUND'); + } + + return $this; + } + + /** + * {@inheritDoc} + */ + public function import(array $data) + { + $this->data = $data; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function save() + { + if (empty($this->data['item_id'])) + { + throw new runtime_exception('ASS_ERROR_NOT_EXISTS'); + } + + $data = array_diff_key($this->data, ['item_id' => null, 'category_slug' => null]); + + $data['item_edit_time'] = time(); + + $sql = 'UPDATE ' . $this->items_table . ' + SET ' . $this->db->sql_build_array('UPDATE', $data) . ' + WHERE item_id = ' . $this->get_id(); + $this->db->sql_query($sql); + + return $this; + } + + /** + * {@inheritDoc} + */ + public function insert() + { + if (!empty($this->data['item_id'])) + { + throw new runtime_exception('ASS_ERROR_ALREADY_EXISTS'); + } + + $sql = 'SELECT COALESCE(MAX(item_order), 0) as item_order FROM ' . $this->items_table; + $result = $this->db->sql_query($sql); + $order = (int) $this->db->sql_fetchfield('item_order'); + $this->db->sql_freeresult($result); + + $this->data['item_order'] = ++$order; + $this->data['item_create_time'] = time(); + + $sql = 'INSERT INTO ' . $this->items_table . ' ' . $this->db->sql_build_array('INSERT', $this->data); + $this->db->sql_query($sql); + + $this->data['item_id'] = (int) $this->db->sql_nextid(); + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_id() + { + return isset($this->data['item_id']) ? (int) $this->data['item_id'] : 0; + } + + /** + * {@inheritDoc} + */ + public function get_category() + { + return isset($this->data['category_id']) ? (int) $this->data['category_id'] : 0; + } + + /** + * {@inheritDoc} + */ + public function set_category($category_id) + { + $category_id = (int) $category_id; + + $this->data['category_id'] = $category_id; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_category_slug() + { + return isset($this->data['category_slug']) ? (string) $this->data['category_slug'] : ''; + } + + /** + * {@inheritDoc} + */ + public function set_category_slug($slug) + { + $slug = (string) $slug; + + $this->data['category_slug'] = $slug; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_title() + { + return isset($this->data['item_title']) ? (string) $this->data['item_title'] : ''; + } + + /** + * {@inheritDoc} + */ + public function set_title($title) + { + $title = (string) $title; + + if ($title === '') + { + throw new runtime_exception('ASS_ERROR_TOO_SHORT', ['TITLE', 0, 0]); + } + + if (($length = utf8_strlen($title)) > 255) + { + throw new runtime_exception('ASS_ERROR_TOO_LONG', ['TITLE', 255, $length]); + } + + $this->data['item_title'] = $title; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_slug() + { + return isset($this->data['item_slug']) ? (string) $this->data['item_slug'] : ''; + } + + /** + * {@inheritDoc} + */ + public function set_slug($slug) + { + $slug = (string) $slug; + + if ($slug === '') + { + throw new runtime_exception('ASS_ERROR_TOO_SHORT', ['SLUG', 0, 0]); + } + + if (($length = utf8_strlen($slug)) > 255) + { + throw new runtime_exception('ASS_ERROR_TOO_LONG', ['SLUG', 255, $length]); + } + + // Route should not contain any unexpected special characters + if (!preg_match('/^[^!"#$%&*\'()+,.\/\\\\:;<=>?@\\[\\]^`{|}~ ]*$/', $slug)) + { + throw new runtime_exception('ASS_ERROR_ILLEGAL_CHARS', ['SLUG']); + } + + // Routes must be unique + if (!$this->get_id() || ($this->get_id() && $this->get_slug() !== '' && $this->get_slug() !== $slug)) + { + $sql = 'SELECT item_title + FROM ' . $this->items_table . " + WHERE item_slug = '" . $this->db->sql_escape($slug) . "' + AND item_id <> " . $this->get_id() . ' + AND category_id = ' . $this->get_category(); + $result = $this->db->sql_query_limit($sql, 1); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if ($row !== false) + { + throw new runtime_exception('ASS_ERROR_NOT_UNIQUE', ['SLUG', $row['item_title']]); + } + } + + $this->data['item_slug'] = $slug; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_icon() + { + return isset($this->data['item_icon']) ? (string) $this->data['item_icon'] : ''; + } + + /** + * {@inheritDoc} + */ + public function set_icon($icon) + { + $icon = (string) $icon; + + $this->data['item_icon'] = $icon; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_desc_for_display() + { + return isset($this->data['item_desc']) ? (string) $this->renderer->render(htmlspecialchars_decode($this->data['item_desc'], ENT_COMPAT)) : ''; + } + + /** + * {@inheritDoc} + */ + public function get_desc() + { + return isset($this->data['item_desc']) ? (string) $this->utils->unparse($this->data['item_desc']) : ''; + } + + /** + * {@inheritDoc} + */ + public function set_desc($desc) + { + $desc = (string) $desc; + + $desc = $this->parser->parse($desc); + + $this->data['item_desc'] = $desc; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_active() + { + return isset($this->data['item_active']) ? (bool) $this->data['item_active'] : true; + } + + /** + * {@inheritDoc} + */ + public function set_active($active) + { + $active = (bool) $active; + + $this->data['item_active'] = $active; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_order() + { + return isset($this->data['item_order']) ? (int) $this->data['item_order'] : 0; + } + + /** + * {@inheritDoc} + */ + public function set_order($order) + { + $order = (int) $order; + + $this->data['item_order'] = $order; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_type() + { + return isset($this->data['item_type']) ? (string) $this->data['item_type'] : ''; + } + + /** + * {@inheritDoc} + */ + public function set_type($type) + { + $type = (string) $type; + + if ($type === '') + { + throw new runtime_exception('ASS_ERROR_TOO_SHORT', ['TYPE', 0, 0]); + } + + $this->data['item_type'] = $type; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_data() + { + return isset($this->data['item_data']) ? (array) json_decode($this->data['item_data'], true) : []; + } + + /** + * {@inheritDoc} + */ + public function set_data(array $data) + { + $data = (array) $data; + + $this->data['item_data'] = json_encode($data); + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_price() + { + return isset($this->data['item_price']) ? (double) $this->data['item_price'] : 0.00; + } + + /** + * {@inheritDoc} + */ + public function set_price($price) + { + $price = (double) $price; + + if ($price < 0) + { + throw new runtime_exception('ASS_ERROR_TOO_LOW', ['PRICE', 0, $price]); + } + + if ($price > self::DECIMAL_14) + { + throw new runtime_exception('ASS_ERROR_TOO_HIGH', ['PRICE', self::DECIMAL_14, $price]); + } + + $this->data['item_price'] = $price; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_count() + { + return isset($this->data['item_count']) ? (int) $this->data['item_count'] : 0; + } + + /** + * {@inheritDoc} + */ + public function set_count($count) + { + $count = (int) $count; + + if ($count < 0) + { + throw new runtime_exception('ASS_ERROR_TOO_LOW', ['COUNT', 0, $count]); + } + + if ($count > self::ULINT) + { + throw new runtime_exception('ASS_ERROR_TOO_HIGH', ['COUNT', self::ULINT, $count]); + } + + $this->data['item_count'] = $count; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_stack() + { + return isset($this->data['item_stack']) ? (int) $this->data['item_stack'] : 1; + } + + /** + * {@inheritDoc} + */ + public function set_stack($stack) + { + $stack = (int) $stack; + + if ($stack < 1) + { + throw new runtime_exception('ASS_ERROR_TOO_LOW', ['STACK', 1, $stack]); + } + + if ($stack > self::ULINT) + { + throw new runtime_exception('ASS_ERROR_TOO_HIGH', ['STACK', self::ULINT, $stack]); + } + + $this->data['item_stack'] = $stack; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_purchases() + { + return isset($this->data['item_purchases']) ? (int) $this->data['item_purchases'] : 0; + } + + /** + * {@inheritDoc} + */ + public function set_purchases($purchases) + { + $purchases = (int) $purchases; + + if ($purchases < 0) + { + throw new runtime_exception('ASS_ERROR_TOO_LOW', ['PURCHASES', 0, $purchases]); + } + + if ($purchases > self::ULINT) + { + throw new runtime_exception('ASS_ERROR_TOO_HIGH', ['PURCHASES', self::ULINT, $purchases]); + } + + $this->data['item_purchases'] = $purchases; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_stock() + { + return isset($this->data['item_stock']) ? (int) $this->data['item_stock'] : 0; + } + + /** + * {@inheritDoc} + */ + public function set_stock($stock) + { + $stock = (int) $stock; + + if ($stock < 0) + { + throw new runtime_exception('ASS_ERROR_TOO_LOW', ['STOCK', 0, $stock]); + } + + if ($stock > self::ULINT) + { + throw new runtime_exception('ASS_ERROR_TOO_HIGH', ['STOCK', self::ULINT, $stock]); + } + + $this->data['item_stock'] = $stock; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_stock_threshold() + { + return isset($this->data['item_stock_threshold']) ? (int) $this->data['item_stock_threshold'] : 0; + } + + /** + * {@inheritDoc} + */ + public function set_stock_threshold($threshold) + { + $threshold = (int) $threshold; + + if ($threshold < 0) + { + throw new runtime_exception('ASS_ERROR_TOO_LOW', ['STOCK_THRESHOLD', 0, $threshold]); + } + + if ($threshold > self::ULINT) + { + throw new runtime_exception('ASS_ERROR_TOO_HIGH', ['STOCK_THRESHOLD', self::ULINT, $threshold]); + } + + $this->data['item_stock_threshold'] = $threshold; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_stock_unlimited() + { + return isset($this->data['item_stock_unlimited']) ? (bool) $this->data['item_stock_unlimited'] : true; + } + + /** + * {@inheritDoc} + */ + public function set_stock_unlimited($unlimited) + { + $unlimited = (bool) $unlimited; + + $this->data['item_stock_unlimited'] = $unlimited; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_expire_string() + { + return isset($this->data['item_expire_string']) ? (string) $this->data['item_expire_string'] : ''; + } + + /** + * {@inheritDoc} + */ + public function set_expire_string($string) + { + $string = (string) $string; + + $seconds = strtotime($string); + + if ($string !== '' && $seconds === false) + { + throw new runtime_exception('ASS_ERROR_INVALID', ['EXPIRE_STRING']); + } + + $this->set_expire_seconds($seconds); + + $this->data['item_expire_string'] = $string; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_expire_seconds() + { + return isset($this->data['item_expire_seconds']) ? (int) $this->data['item_expire_seconds'] : 0; + } + + /** + * {@inheritDoc} + */ + public function set_expire_seconds($seconds) + { + $seconds = (int) $seconds; + + if ($seconds !== 0) + { + $seconds = $seconds - time(); + + if ($seconds < 0) + { + throw new runtime_exception('ASS_ERROR_UNSIGNED', ['EXPIRE_SECONDS']); + } + } + + $this->data['item_expire_seconds'] = $seconds; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_delete_string() + { + return isset($this->data['item_delete_string']) ? (string) $this->data['item_delete_string'] : ''; + } + + /** + * {@inheritDoc} + */ + public function set_delete_string($string) + { + $string = (string) $string; + + $seconds = strtotime($string); + + if ($string !== '' && $seconds === false) + { + throw new runtime_exception('ASS_ERROR_INVALID', ['DELETE_STRING']); + } + + $this->set_delete_seconds($seconds); + + $this->data['item_delete_string'] = $string; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_delete_seconds() + { + return isset($this->data['item_delete_seconds']) ? (int) $this->data['item_delete_seconds'] : 0; + } + + /** + * {@inheritDoc} + */ + public function set_delete_seconds($seconds) + { + $seconds = (int) $seconds; + + if ($seconds !== 0) + { + $seconds = $seconds - time(); + + if ($seconds < 0) + { + throw new runtime_exception('ASS_ERROR_UNSIGNED', ['DELETE_SECONDS']); + } + } + + $this->data['item_delete_seconds'] = $seconds; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_refund_string() + { + return isset($this->data['item_refund_string']) ? (string) $this->data['item_refund_string'] : ''; + } + + /** + * {@inheritDoc} + */ + public function set_refund_string($string) + { + $string = (string) $string; + + $seconds = strtotime($string); + + if ($string !== '' && $seconds === false) + { + throw new runtime_exception('ASS_ERROR_INVALID', ['REFUND_STRING']); + } + + $this->set_refund_seconds($seconds); + + $this->data['item_refund_string'] = $string; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_refund_seconds() + { + return isset($this->data['item_refund_seconds']) ? (int) $this->data['item_refund_seconds'] : 0; + } + + /** + * {@inheritDoc} + */ + public function set_refund_seconds($seconds) + { + $seconds = (int) $seconds; + + if ($seconds !== 0) + { + $seconds = $seconds - time(); + + if ($seconds < 0) + { + throw new runtime_exception('ASS_ERROR_UNSIGNED', ['REFUND_SECONDS']); + } + } + + $this->data['item_refund_seconds'] = $seconds; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_gift() + { + return isset($this->data['item_gift']) ? (bool) $this->data['item_gift'] : true; + } + + /** + * {@inheritDoc} + */ + public function set_gift($gift) + { + $gift = (bool) $gift; + + $this->data['item_gift'] = $gift; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_gift_only() + { + return isset($this->data['item_gift_only']) ? (bool) $this->data['item_gift_only'] : false; + } + + /** + * {@inheritDoc} + */ + public function set_gift_only($gift_only) + { + $gift_only = (bool) $gift_only; + + $this->data['item_gift_only'] = $gift_only; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_gift_type() + { + return isset($this->data['item_gift_type']) ? (bool) $this->data['item_gift_type'] : true; + } + + /** + * {@inheritDoc} + */ + public function set_gift_type($type) + { + $type = (bool) $type; + + $this->data['item_gift_type'] = $type; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_gift_percentage() + { + return isset($this->data['item_gift_percentage']) ? (int) $this->data['item_gift_percentage'] : 0; + } + + /** + * {@inheritDoc} + */ + public function set_gift_percentage($percentage) + { + $percentage = (int) $percentage; + + if ($percentage < -100) + { + throw new runtime_exception('ASS_ERROR_TOO_LOW', ['PRICE', -100, $percentage]); + } + + if ($percentage > self::INT_3) + { + throw new runtime_exception('ASS_ERROR_TOO_HIGH', ['PRICE', self::INT_3, $percentage]); + } + + $this->data['item_gift_percentage'] = $percentage; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_gift_price() + { + return isset($this->data['item_gift_price']) ? (double) $this->data['item_gift_price'] : 0.00; + } + + /** + * {@inheritDoc} + */ + public function set_gift_price($price) + { + $price = (double) $price; + + if ($price < 0) + { + throw new runtime_exception('ASS_ERROR_TOO_LOW', ['GIFT_PRICE', 0, $price]); + } + + if ($price > self::DECIMAL_14) + { + throw new runtime_exception('ASS_ERROR_TOO_HIGH', ['GIFT_PRICE', self::DECIMAL_14, $price]); + } + + $this->data['item_gift_price'] = $price; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_sale_price() + { + return isset($this->data['item_sale_price']) ? (double) $this->data['item_sale_price'] : 0.00; + } + + /** + * {@inheritDoc} + */ + public function set_sale_price($price) + { + $price = (double) $price; + + if ($price < 0) + { + throw new runtime_exception('ASS_ERROR_TOO_LOW', ['SALE_PRICE', 0, $price]); + } + + if ($price > self::DECIMAL_14) + { + throw new runtime_exception('ASS_ERROR_TOO_HIGH', ['SALE_PRICE', self::DECIMAL_14, $price]); + } + + $this->data['item_sale_price'] = $price; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_sale_start() + { + return isset($this->data['item_sale_start']) ? (int) $this->data['item_sale_start'] : 0; + } + + /** + * {@inheritDoc} + */ + public function set_sale_start($time) + { + $time = (string) $time; + + if ($time !== '') + { + $time = $this->time->create_from_format($time); + } + + $time = (int) $time; + + $this->data['item_sale_start'] = $time; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_sale_until() + { + return isset($this->data['item_sale_until']) ? (int) $this->data['item_sale_until'] : 0; + } + + /** + * {@inheritDoc} + */ + public function set_sale_until($time) + { + $time = (string) $time; + + if ($time !== '') + { + $time = $this->time->create_from_format($time); + } + + $time = (int) $time; + + $this->data['item_sale_until'] = $time; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_featured_start() + { + return isset($this->data['item_featured_start']) ? (int) $this->data['item_featured_start'] : 0; + } + + /** + * {@inheritDoc} + */ + public function set_featured_start($time) + { + $time = (string) $time; + + if ($time !== '') + { + $tz = date_default_timezone_get(); + + date_default_timezone_set($this->config['board_timezone']); + + $time = date_timestamp_get(\DateTime::createFromFormat('d/m/Y H:i', $time)); + + date_default_timezone_set($tz); + } + + $time = (int) $time; + + $this->data['item_featured_start'] = $time; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_featured_until() + { + return isset($this->data['item_featured_until']) ? (int) $this->data['item_featured_until'] : 0; + } + + /** + * {@inheritDoc} + */ + public function set_featured_until($time) + { + $time = (string) $time; + + if ($time !== '') + { + $tz = date_default_timezone_get(); + + date_default_timezone_set($this->config['board_timezone']); + + $time = date_timestamp_get(\DateTime::createFromFormat('d/m/Y H:i', $time)); + + date_default_timezone_set($tz); + } + + $time = (int) $time; + + $this->data['item_featured_until'] = $time; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_available_start() + { + return isset($this->data['item_available_start']) ? (int) $this->data['item_available_start'] : 0; + } + + /** + * {@inheritDoc} + */ + public function set_available_start($time) + { + $time = (string) $time; + + if ($time !== '') + { + $tz = date_default_timezone_get(); + + date_default_timezone_set($this->config['board_timezone']); + + $time = date_timestamp_get(\DateTime::createFromFormat('d/m/Y H:i', $time)); + + date_default_timezone_set($tz); + } + + $time = (int) $time; + + $this->data['item_available_start'] = $time; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_available_until() + { + return isset($this->data['item_available_until']) ? (int) $this->data['item_available_until'] : 0; + } + + /** + * {@inheritDoc} + */ + public function set_available_until($time) + { + $time = (string) $time; + + if ($time !== '') + { + $tz = date_default_timezone_get(); + + date_default_timezone_set($this->config['board_timezone']); + + $time = date_timestamp_get(\DateTime::createFromFormat('d/m/Y H:i', $time)); + + date_default_timezone_set($tz); + } + + $time = (int) $time; + + $this->data['item_available_until'] = $time; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_background() + { + return isset($this->data['item_background']) ? (string) $this->data['item_background'] : ''; + } + + /** + * {@inheritDoc} + */ + public function set_background($background) + { + $background = (string) $background; + + $this->data['item_background'] = $background; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_images() + { + return isset($this->data['item_images']) ? (array) json_decode($this->data['item_images'], true) : []; + } + + /** + * {@inheritDoc} + */ + public function set_images(array $images) + { + $images = (array) $images; + + $images = array_filter($images); + + $this->data['item_images'] = json_encode($images); + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_related_enabled() + { + return isset($this->data['item_related_enabled']) ? (bool) $this->data['item_related_enabled'] : true; + } + + /** + * {@inheritDoc} + */ + public function set_related_enabled($related) + { + $related = (bool) $related; + + $this->data['item_related_enabled'] = $related; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_related_items() + { + return isset($this->data['item_related_items']) ? (array) json_decode($this->data['item_related_items'], true) : []; + } + + /** + * {@inheritDoc} + */ + public function set_related_items(array $items) + { + $items = (array) $items; + + $items = array_filter($items); + $items = array_unique($items); + + if (($count = count($items)) > 8) + { + throw new runtime_exception('ASS_ERROR_TOO_HIGH', ['RELATED_ITEMS', 8, $count]); + } + + $this->data['item_related_items'] = json_encode($items); + + return $this; + } + + /** + * {@inheritDoc} + */ + public function get_create_time() + { + return isset($this->data['item_create_time']) ? (int) $this->data['item_create_time'] : 0; + } + + /** + * {@inheritDoc} + */ + public function get_edit_time() + { + return isset($this->data['item_edit_time']) ? (int) $this->data['item_edit_time'] : 0; + } + + /** + * {@inheritDoc} + */ + public function get_conflict() + { + return isset($this->data['item_conflict']) ? (bool) $this->data['item_conflict'] : false; + } + + /** + * {@inheritDoc} + */ + public function set_conflict($state) + { + $state = (bool) $state; + + $this->data['item_conflict'] = $state; + + return $this; + } +} diff --git a/ext/phpbbstudio/ass/entity/item_interface.php b/ext/phpbbstudio/ass/entity/item_interface.php new file mode 100644 index 0000000..6ff94f7 --- /dev/null +++ b/ext/phpbbstudio/ass/entity/item_interface.php @@ -0,0 +1,785 @@ +blocks = $blocks; + $this->config = $config; + $this->language = $language; + } + + /** + * Assign functions defined in this class to event listeners in the core. + * + * @return array + * @access public + * @static + */ + static public function getSubscribedEvents() + { + return ['phpbbstudio.aps.display_blocks' => 'ass_display_blocks']; + } + + /** + * Load language after user set up. + * + * @event core.user_setup_after + * @param \phpbb\event\data $event The event object + * @return void + * @access public + */ + public function ass_display_blocks(\phpbb\event\data $event) + { + $this->language->add_lang(['ass_common', 'ass_display'], 'phpbbstudio/ass'); + + $blocks = $event['page_blocks']; + + $blocks['items'] = [ + 'title' => $this->language->lang('ASS_SHOP'), + 'auth' => $this->config['ass_enabled'] && $this->config['ass_active'], + 'blocks' => [], + ]; + + foreach ($this->blocks->get_blocks() as $type => $data) + { + foreach ($data as $block => $title) + { + $blocks['items']['blocks'][$block] = [ + 'title' => $this->language->lang($title), + 'template' => '@phpbbstudio_ass/blocks/' . $block . '.html', + 'function' => [$this->blocks, $block], + ]; + } + } + + $event['page_blocks'] = $blocks; + } +} diff --git a/ext/phpbbstudio/ass/event/exception_listener.php b/ext/phpbbstudio/ass/event/exception_listener.php new file mode 100644 index 0000000..6e84c93 --- /dev/null +++ b/ext/phpbbstudio/ass/event/exception_listener.php @@ -0,0 +1,168 @@ +config_text = $config_text; + $this->controller = $controller; + $this->language = $language; + $this->renderer = $renderer; + $this->template = $template; + + $this->type_caster = new \phpbb\request\type_cast_helper(); + $this->debug = defined('DEBUG'); + } + + /** + * Assign functions defined in this class to event listeners in the core. + * + * @return array + * @access public + * @static + */ + static public function getSubscribedEvents() + { + return [ + KernelEvents::EXCEPTION => 'on_kernel_exception', + ]; + } + + /** + * Handle any shop exception. + * + * @param GetResponseForExceptionEvent $event The event object + * @return void + * @access public + */ + public function on_kernel_exception(GetResponseForExceptionEvent $event) + { + $exception = $event->getException(); + + if ($exception instanceof shop_exception) + { + $message = $exception->getMessage(); + $this->type_caster->set_var($message, $message, 'string', true, false); + + $message = $this->language->lang_array($message, $exception->get_parameters()); + + // Show text in bold + $message = preg_replace('#<(/?strong)>#i', '<$1>', $message); + + if (!$event->getRequest()->isXmlHttpRequest()) + { + $this->controller->create_shop('shop'); + + page_header($this->language->lang('INFORMATION')); + + if ($exception instanceof shop_inactive_exception) + { + $desc = $this->config_text->get('ass_inactive_desc'); + $desc = $this->renderer->render(htmlspecialchars_decode($desc, ENT_COMPAT)); + } + + if ($exception instanceof shop_item_exception) + { + $desc = $this->language->lang('ASS_ERROR_LOGGED'); + } + + $this->template->assign_vars([ + 'EXCEPTION_CODE' => $exception->getStatusCode(), + 'EXCEPTION_TEXT' => $message, + 'EXCEPTION_DESC' => !empty($desc) ? $desc : '', + ]); + + $this->template->set_filenames([ + 'body' => 'ass_exception.html', + ]); + + page_footer(true, false, false); + + $response = new Response($this->template->assign_display('body'), 500); + } + else + { + $data = [ + 'title' => $this->language->lang('INFORMATION'), + ]; + + if (!empty($message)) + { + $data['message'] = $message; + } + + if ($this->debug) + { + $data['trace'] = $exception->getTrace(); + } + + $response = new JsonResponse($data, 500); + } + + $response->setStatusCode($exception->getStatusCode()); + $response->headers->add($exception->getHeaders()); + + $event->setResponse($response); + } + } +} diff --git a/ext/phpbbstudio/ass/event/setup_listener.php b/ext/phpbbstudio/ass/event/setup_listener.php new file mode 100644 index 0000000..0180085 --- /dev/null +++ b/ext/phpbbstudio/ass/event/setup_listener.php @@ -0,0 +1,149 @@ +config = $config; + $this->functions = $functions; + $this->language = $language; + $this->template = $template; + } + + /** + * Assign functions defined in this class to event listeners in the core. + * + * @return array + * @access public + * @static + */ + static public function getSubscribedEvents() + { + return [ + 'core.user_setup_after' => 'ass_load_lang', + 'core.page_header_after' => 'ass_setup_links', + 'core.permissions' => 'ass_setup_permissions', + ]; + } + + /** + * Load language after user set up. + * + * @event core.user_setup_after + * @return void + * @access public + */ + public function ass_load_lang() + { + $this->language->add_lang('ass_lang', 'phpbbstudio/ass'); + } + + /** + * Set up ASS link locations. + * + * @return void + */ + public function ass_setup_links() + { + $locations = array_filter($this->functions->get_link_locations('ass_link_locations')); + + if ($locations) + { + $this->template->assign_vars(array_combine(array_map(function($key) { + return 'S_ASS_' . strtoupper($key); + }, array_keys($locations)), $locations)); + } + + $this->template->assign_vars([ + 'ASS_SHOP_ICON' => (string) $this->config['ass_shop_icon'], + 'S_ASS_ENABLED' => (bool) $this->config['ass_enabled'], + ]); + } + + /** + * Add ASS permissions. + * + * @event core.permissions + * @param \phpbb\event\data $event The event object + * @return void + * @access public + */ + public function ass_setup_permissions(\phpbb\event\data $event) + { + $categories = $event['categories']; + $permissions = $event['permissions']; + + if (empty($categories['phpbb_studio'])) + { + /* Setting up a custom CAT */ + $categories['phpbb_studio'] = 'ACL_CAT_PHPBB_STUDIO'; + + $event['categories'] = $categories; + } + + $perms = [ + 'a_ass_inventory', + 'a_ass_items', + 'a_ass_files', + 'a_ass_logs', + 'a_ass_overview', + 'a_ass_settings', + 'u_ass_can_gift', + 'u_ass_can_purchase', + 'u_ass_can_receive_gift', + 'u_ass_can_receive_stock_notifications', + 'u_ass_can_stack', + 'u_ass_can_view_inactive_items', + 'u_ass_can_view_inactive_shop', + ]; + + foreach ($perms as $permission) + { + $permissions[$permission] = ['language' => 'ACL_' . utf8_strtoupper($permission), 'cat' => 'phpbb_studio']; + } + + $event['permissions'] = $permissions; + } +} diff --git a/ext/phpbbstudio/ass/exceptions/shop_exception.php b/ext/phpbbstudio/ass/exceptions/shop_exception.php new file mode 100644 index 0000000..edd302f --- /dev/null +++ b/ext/phpbbstudio/ass/exceptions/shop_exception.php @@ -0,0 +1,18 @@ +container->get('ext.manager'); + + if (!$ext_manager->is_enabled('phpbbstudio/aps')) + { + if (phpbb_version_compare(PHPBB_VERSION, '3.3.0@dev', '<')) + { + $user = $this->container->get('user'); + $lang = $user->lang; + + $user->add_lang_ext('phpbbstudio/ass', 'ass_ext'); + + $lang['EXTENSION_NOT_ENABLEABLE'] .= '
                ' . $user->lang('ASS_REQUIRES_APS'); + + $user->lang = $lang; + + return false; + } + + if (phpbb_version_compare(PHPBB_VERSION, '3.3.0@dev', '>')) + { + $language= $this->container->get('language'); + $language->add_lang('ass_ext', 'phpbbstudio/ass'); + + return $language->lang('ASS_REQUIRES_APS'); + } + } + + $md_manager = $ext_manager->create_extension_metadata_manager('phpbbstudio/aps'); + $aps_version = (string) $md_manager->get_metadata('version'); + $aps_required = '1.0.6-RC'; + + /** Make sure the APS version is 1.0.6-RC or higher */ + if (phpbb_version_compare($aps_version, $aps_required, '<')) + { + if (phpbb_version_compare(PHPBB_VERSION, '3.3.0@dev', '<')) + { + $user = $this->container->get('user'); + $lang = $user->lang; + + $user->add_lang_ext('phpbbstudio/ass', 'ass_ext'); + + $lang['EXTENSION_NOT_ENABLEABLE'] .= '
                ' . $user->lang('ASS_REQUIRES_APS_VERSION', $aps_required); + + $user->lang = $lang; + + return false; + } + + if (phpbb_version_compare(PHPBB_VERSION, '3.3.0@dev', '>')) + { + $language= $this->container->get('language'); + $language->add_lang('ass_ext', 'phpbbstudio/ass'); + + return $language->lang('ASS_REQUIRES_APS_VERSION', $aps_required); + } + } + + return true; + } + + /** + * Enable notifications for the extension. + * + * @param mixed $old_state State returned by previous call of this method + * @return mixed Returns false after last step, otherwise temporary state + * @access public + */ + public function enable_step($old_state) + { + if ($old_state === false) + { + /** @var \phpbb\notification\manager $notification_manager */ + $notification_manager = $this->container->get('notification_manager'); + + $notification_manager->enable_notifications('phpbbstudio.ass.notification.type.gift'); + $notification_manager->enable_notifications('phpbbstudio.ass.notification.type.stock'); + + return 'notification'; + } + + return parent::enable_step($old_state); + } + + /** + * Disable notifications for the extension. + * + * @param mixed $old_state State returned by previous call of this method + * @return mixed Returns false after last step, otherwise temporary state + * @access public + */ + public function disable_step($old_state) + { + if ($old_state === false) + { + try + { + if ($this->container->hasParameter('phpbbstudio.ass.extended')) + { + $language = $this->container->get('language'); + $language->add_lang('ass_ext', 'phpbbstudio/ass'); + + $message = $language->lang('ASS_DISABLE_EXTENDED', $this->container->getParameter('phpbbstudio.ass.extended')); + + // Trigger error for the ACP + @trigger_error($message, E_USER_WARNING); + + // Throw an exception for the CLI + throw new \RuntimeException($message); + } + } + catch (\InvalidArgumentException $e) + { + // Continue + } + + /** @var \phpbb\notification\manager $notification_manager */ + $notification_manager = $this->container->get('notification_manager'); + + $notification_manager->disable_notifications('phpbbstudio.ass.notification.type.gift'); + $notification_manager->disable_notifications('phpbbstudio.ass.notification.type.stock'); + + return 'notification'; + } + + return parent::disable_step($old_state); + } + + /** + * Purge notifications for the extension. + * + * @param mixed $old_state State returned by previous call of this method + * @return mixed Returns false after last step, otherwise temporary state + * @access public + */ + public function purge_step($old_state) + { + if ($old_state === false) + { + /** @var \phpbb\notification\manager $notification_manager */ + $notification_manager = $this->container->get('notification_manager'); + + $notification_manager->purge_notifications('phpbbstudio.ass.notification.type.gift'); + $notification_manager->purge_notifications('phpbbstudio.ass.notification.type.stock'); + + return 'notification'; + } + + return parent::purge_step($old_state); + } +} diff --git a/ext/phpbbstudio/ass/helper/controller.php b/ext/phpbbstudio/ass/helper/controller.php new file mode 100644 index 0000000..d350b24 --- /dev/null +++ b/ext/phpbbstudio/ass/helper/controller.php @@ -0,0 +1,276 @@ +aps_functions = $aps_functions; + $this->auth = $auth; + $this->config = $config; + $this->config_text = $config_text; + $this->helper = $helper; + $this->language = $language; + $this->operator_cat = $operator_cat; + $this->router = $router; + $this->template = $template; + $this->user = $user; + } + + /** + * Check whether the shop is enabled and active. + * + * @return void + * @throws http_exception + * @throws shop_inactive_exception + * @access public + */ + public function check_shop() + { + if (!$this->config['ass_enabled']) + { + throw new http_exception(404, 'PAGE_NOT_FOUND'); + } + + $this->language->add_lang('ass_common', 'phpbbstudio/ass'); + $this->language->add_lang('aps_display', 'phpbbstudio/aps'); + + if (!$this->config['ass_active'] && !$this->auth->acl_get('u_ass_can_view_inactive_shop')) + { + throw new shop_inactive_exception(409, 'ASS_SHOP_INACTIVE'); + } + } + + /** + * Create and set up the shop. + * + * @param string $mode The shop mode (shop|inventory|history) + * @param category|null $category The category entity + * @param item|null $item The item entity + * @return void + * @access public + */ + public function create_shop($mode, category $category = null, item $item = null) + { + $this->create_shop_crumbs($mode, $category, $item); + $this->create_shop_navbar($mode, $category); + + $this->template->assign_vars([ + 'S_ASS_INVENTORY' => $mode === 'inventory', + 'S_ASS_SHOP' => $mode === 'shop', + 'S_CAN_GIFT' => $this->auth->acl_get('u_ass_can_gift') && $this->config['ass_gift_enabled'], + 'S_CAN_PURCHASE' => $this->auth->acl_get('u_ass_can_purchase') && $this->user->data['is_registered'], + 'S_RECEIVE_GIFT' => $this->auth->acl_get('u_ass_can_receive_gift'), + ]); + } + + /** + * Create and set up the shop navigation. + * + * @param string $mode The shop mode (shop|inventory|history) + * @param category|null $category The category entity + * @return void + * @access public + */ + public function create_shop_navbar($mode, category $category = null) + { + $categories = $this->operator_cat->get_categories(true); + + $title = $mode === 'shop' ? 'ASS_SHOP_INDEX' : 'ASS_INVENTORY'; + $route = $mode === 'shop' ? 'phpbbstudio_ass_shop' : 'phpbbstudio_ass_inventory'; + + $this->template->assign_block_vars('ass_shop_categories', [ + 'ID' => 0, + 'TITLE' => $this->language->lang($title), + 'ICON' => 'fa-bookmark', + 'S_SELECTED' => $category === null, + 'U_VIEW' => $this->helper->route($route), + ]); + + /** @var category $cat */ + foreach ($categories as $cat) + { + $this->template->assign_block_vars('ass_shop_categories', [ + 'ID' => $cat->get_id(), + 'TITLE' => $cat->get_title(), + 'ICON' => $cat->get_icon(), + 'S_SELECTED' => $category !== null && $cat->get_id() === $category->get_id(), + 'U_VIEW' => $this->router->category($cat->get_slug(), $mode), + ]); + } + } + + /** + * Create and set up the shop breadcrumbs. + * + * @param string $mode The shop mode (shop|inventory|history) + * @param category|null $category The category entity + * @param item|null $item The item entity + * @return void + * @access public + */ + public function create_shop_crumbs($mode, category $category = null, item $item = null) + { + $title = $mode === 'shop' ? 'ASS_SHOP_INDEX' : 'ASS_INVENTORY'; + $route = $mode === 'shop' ? 'phpbbstudio_ass_shop' : 'phpbbstudio_ass_inventory'; + + $this->template->assign_block_vars_array('navlinks', [ + [ + 'FORUM_NAME' => $this->aps_functions->get_name(), + 'U_VIEW_FORUM' => $this->helper->route('phpbbstudio_aps_display'), + ], + [ + 'FORUM_NAME' => $this->language->lang($title), + 'U_VIEW_FORUM' => $this->helper->route($route), + ], + ]); + + if ($mode === 'history') + { + $this->template->assign_block_vars('navlinks', [ + 'FORUM_NAME' => $this->language->lang('ASS_HISTORY'), + 'U_VIEW_FORUM' => $this->helper->route('phpbbstudio_ass_history'), + ]); + } + + if ($category instanceof category) + { + $this->template->assign_block_vars('navlinks', [ + 'FORUM_NAME' => $category->get_title(), + 'U_VIEW_FORUM' => $this->router->category($category->get_slug(), $mode), + ]); + } + + if ($item instanceof item) + { + $this->template->assign_block_vars('navlinks', [ + 'FORUM_NAME' => $item->get_title(), + 'U_VIEW_FORUM' => $this->router->item($item->get_category_slug(), $item->get_slug()), + ]); + } + } + + /** + * Set up and assign carousel variables. + * + * @return void + * @access public + */ + public function setup_carousel() + { + $this->template->assign_vars([ + 'SHOP_CAROUSEL_ARROWS' => (bool) $this->config['ass_carousel_arrows'], + 'SHOP_CAROUSEL_DOTS' => (bool) $this->config['ass_carousel_dots'], + 'SHOP_CAROUSEL_FADE' => (bool) $this->config['ass_carousel_fade'], + 'SHOP_CAROUSEL_PLAY' => (bool) $this->config['ass_carousel_play'], + 'SHOP_CAROUSEL_PLAY_SPEED' => (int) $this->config['ass_carousel_play_speed'], + 'SHOP_CAROUSEL_SPEED' => (int) $this->config['ass_carousel_speed'], + ]); + } + + /** + * Set up and assign panel variables. + * + * @return void + * @access public + */ + public function setup_panels() + { + $panels = ['featured', 'sale', 'featured_sale', 'recent', 'limited', 'random']; + $options = [ + 'icon' => '', + 'icon_colour' => 'icon-', + 'banner_colour' => 'shop-panel-icon-', + 'banner_size' => 'shop-panel-icon-', + ]; + + foreach ($panels as $panel) + { + if ($this->config["ass_panel_{$panel}_icon"]) + { + $icon = ''; + + foreach ($options as $option => $prefix) + { + $icon .= " {$prefix}{$this->config["ass_panel_{$panel}_{$option}"]}"; + } + + $this->template->assign_var('SHOP_PANEL_' . utf8_strtoupper($panel) . '_ICON', $icon); + } + } + } +} diff --git a/ext/phpbbstudio/ass/helper/files.php b/ext/phpbbstudio/ass/helper/files.php new file mode 100644 index 0000000..d18b4e8 --- /dev/null +++ b/ext/phpbbstudio/ass/helper/files.php @@ -0,0 +1,471 @@ + ['7z', 'ace', 'bz2', 'gtar', 'gz', 'rar', 'tar', 'tgz', 'torrent', 'zip'], + 'images' => ['gif', 'jpeg', 'jpg', 'png', 'tga', 'tif', 'tiff'], + 'documents' => ['ai', 'doc', 'docm', 'docx', 'dot', 'dotm', 'dotx', 'odg', 'odp', 'ods', 'odt', 'pdf', 'ppt', 'pptm', 'pptx', 'ps', 'rtf', 'xls', 'xlsb', 'xlsm', 'xlsx'], + 'text' => ['c', 'cpp', 'csv', 'diz', 'h', 'hpp', 'ini', 'js', 'log', 'txt', 'xml'], + 'other' => ['mp3', 'mpeg', 'mpg', 'ogg', 'ogm'], + ]; + + /** @var array File extensions specific icons */ + protected $specific_icons = [ + 'powerpoint-o' => ['ppt', 'pptm', 'pptx'], + 'excel-o' => ['csv', 'xls', 'xlsb', 'xlsm', 'xlsx'], + 'pdf-o' => ['pdf'], + 'audio-o' => ['mp3'], + 'movie-o' => ['mpeg', 'mpg', 'ogg', 'ogm'], + 'code-o' => ['c', 'cpp', 'h', 'hpp', 'ini', 'js'], + ]; + + /** @var array File extensions icons */ + protected $icons = [ + 'archives' => 'file-archive-o', + 'images' => 'file-image-o', + 'documents' => 'file-word-o', + 'text' => 'file-text-o', + 'other' => 'file-o', + ]; + + /** + * Constructor. + * + * @param \phpbb\cache\service $cache Cache object + * @param \phpbb\files\factory $factory Files factory object + * @param \phpbb\filesystem\filesystem $filesystem Filesystem object + * @param string $root_path phpBB root path + * @return void + * @access public + */ + public function __construct( + \phpbb\cache\service $cache, + \phpbb\files\factory $factory, + \phpbb\filesystem\filesystem $filesystem, + $root_path + ) + { + $this->cache = $cache; + $this->factory = $factory; + $this->filesystem = $filesystem; + + $this->root_path = $root_path; + } + + /** + * Get the shop files modes. + * + * @return array The shop files modes + * @access public + */ + public function get_modes() + { + return (array) $this->modes; + } + + /** + * Set the shop files mode. + * + * @param string $mode The shop files mode + * @return self $this This object for chaining calls + * @access public + */ + public function set_mode($mode) + { + $this->mode = $mode; + + return $this; + } + + /** + * Set the finder instance. + * + * @return void + * @access public + */ + public function set_finder() + { + if ($this->finder === null) + { + $this->finder = new finder($this->filesystem, $this->root_path, $this->cache); + } + } + + /** + * Get the file size of a shop file. + * + * @param string $directory The shop file directory + * @param string $file The shop file + * @return int + * @access public + */ + public function get_file_size($directory, $file) + { + $path = $this->get_path($directory, true, $file); + + return $this->filesystem->exists($path) ? filesize($path) : 0; + } + + /** + * Get the file modification time of a shop file. + * + * @param string $directory The shop file directory + * @param string $file The shop file + * @return int + * @access public + */ + public function get_file_time($directory, $file) + { + $path = $this->get_path($directory, true, $file); + + return $this->filesystem->exists($path) ? filemtime($path) : 0; + } + + /** + * Get the file icon of a shop file. + * + * @param string $file The shop file + * @return string + * @access public + */ + public function get_file_icon($file) + { + $extension = pathinfo($file, PATHINFO_EXTENSION); + + foreach ($this->specific_icons as $icon => $extensions) + { + if (in_array($extension, $extensions)) + { + return $icon; + } + } + + foreach ($this->extensions as $type => $extensions) + { + if (in_array($extension, $extensions)) + { + return $this->icons[$type]; + } + } + + return $this->icons['other']; + } + + /** + * View a shop file directory. + * + * @param string $directory The shop file directory + * @return array The files and folders in the shop file directory + * @access public + */ + public function view($directory = '') + { + $files = []; + $folders = []; + + $needle = $this->get_path($directory); + $target = $this->get_path($directory, false); + + $this->set_finder(); + + foreach ($this->finder->core_path($target)->get_files() as $path) + { + $file = $this->clean_path($path, $needle); + + if ($this->is_htaccess($file)) + { + continue; + } + + if ($this->is_not_nested($file)) + { + $files[] = $file; + } + } + + foreach ($this->finder->core_path($target)->get_directories() as $path) + { + $folder = $this->clean_path($path, $needle); + + if ($this->is_not_nested($folder)) + { + $folders[] = $folder; + } + } + + if ($this->mode === 'files') + { + $this->create_htaccess(); + } + + return [ + 'files' => $files, + 'folders' => $folders, + ]; + } + + /** + * Select a shop file. + * + * @param string $directory The shop file directory + * @param string $image The shop file image + * @return array + * @access public + */ + public function select($directory, $image = '') + { + $files = []; + + $needle = $this->get_path($directory); + $target = $this->get_path($directory, false); + + $this->set_finder(); + + foreach ($this->finder->core_path($target)->get_files() as $path) + { + $file = $this->clean_path($path, $needle); + + if ($this->is_not_nested($path)) + { + $files[] = [ + 'NAME' => $file, + 'S_SELECTED' => $path === $image, + ]; + } + } + + return $files; + } + + /** + * Add a shop file. + * + * @param string $directory The shop file directory + * @param string $folder The shop file folder + * @return void + * @access public + */ + public function add($directory, $folder) + { + if ($folder === '') + { + throw new runtime_exception('ASS_ERROR_TOO_SHORT', [0, 0]); + } + + if (!preg_match('/^[^!"#$%&*\'()+,.\/\\\\:;<=>?@\\[\\]^`{|}~ ]*$/', $folder)) + { + throw new runtime_exception('ASS_ERROR_ILLEGAL_CHARS'); + } + + $target = $this->get_path($directory, true, $folder); + + if ($this->filesystem->exists($target)) + { + throw new runtime_exception('ASS_ERROR_NOT_UNIQUE', [$folder]); + } + + $this->filesystem->mkdir($target); + } + + /** + * Upload a shop file from an element. + * + * @param string $directory The shop file directory + * @param string $input_name The name of the element + * @return void + * @access public + */ + public function upload($directory, $input_name) + { + /** @var \phpbb\files\upload $upload */ + $upload = $this->factory->get('files.upload'); + + if (!$upload->is_valid($input_name)) + { + throw new runtime_exception('FORM_INVALID'); + } + + $file = $upload + ->set_allowed_extensions($this->get_extensions()) + ->handle_upload('files.types.form', $input_name); + + $upload->common_checks($file); + + if ($file->error) + { + throw new runtime_exception(implode('
                ', array_unique($file->error))); + } + + if ($this->exists($directory, $file->get('realname'))) + { + throw new runtime_exception('ASS_ERROR_NOT_UNIQUE', [$file->get('realname')]); + } + + $file->move_file($this->get_path($directory, false)); + } + + /** + * Check if a shop file exists. + * + * @param string $directory The shop file directory + * @param string $file The shop file + * @return bool Whether or not the file exists + * @access public + */ + public function exists($directory, $file = '') + { + return (bool) $this->filesystem->exists($this->get_path($directory, true, $file)); + } + + /** + * Delete a shop file from a given path. + * + * @param string $path Path to a shop file + * @return void + * @access public + */ + public function delete($path) + { + $this->filesystem->remove($this->get_path($path)); + } + + /** + * Get a path to a shop file. + * + * @param string $directory The shop file directory + * @param bool $root Whether or not to include the phpBB root path + * @param string $file The shop file + * @return string The shop file path + * @access public + */ + public function get_path($directory, $root = true, $file = '') + { + $root_path = $root ? $this->root_path : ''; + + $directory = $directory ? "/{$directory}" : ''; + $file = $file ? "/{$file}" : ''; + + return "{$root_path}{$this->mode}/aps{$directory}{$file}"; + } + + /** + * Get the file extensions for a mode. + * + * @return array The file extensions + * @access public + */ + public function get_extensions() + { + if ($this->mode === 'images') + { + return (array) $this->extensions[$this->mode]; + } + else + { + $extensions = []; + + foreach ($this->extensions as $array) + { + $extensions = array_merge($extensions, $array); + } + + return (array) $extensions; + } + } + + /** + * Clean the needle from a path. + * + * @param string $path The path + * @param string $needle The needle + * @return string + * @access protected + */ + protected function clean_path($path, $needle) + { + return trim(str_replace($needle, '', $path), '/'); + } + + /** + * Check if a path is nested. + * + * @param string $path The path + * @return bool Whether or not the path is nested + * @access protected + */ + protected function is_not_nested($path) + { + return strpos($path, '/') === false; + } + + /** + * Check if a file is the ".htaccess" file. + * + * @param string $file The file + * @return bool + * @access public + */ + protected function is_htaccess($file) + { + return $this->mode === 'files' && $file === '.htaccess'; + } + + /** + * Create a ".htaccess" file. + * + * @return void + * @access protected + */ + protected function create_htaccess() + { + $htaccess = $this->root_path . $this->mode . '/aps/.htaccess'; + + if (!$this->filesystem->exists($htaccess)) + { + $this->filesystem->dump_file($htaccess, +" + Order Allow,Deny + Deny from All +"); + } + } +} diff --git a/ext/phpbbstudio/ass/helper/log.php b/ext/phpbbstudio/ass/helper/log.php new file mode 100644 index 0000000..7af29cb --- /dev/null +++ b/ext/phpbbstudio/ass/helper/log.php @@ -0,0 +1,213 @@ +db = $db; + $this->user = $user; + + $this->categories_table = $categories_table; + $this->items_table = $items_table; + $this->logs_table = $logs_table; + $this->users_table = $users_table; + } + + /** + * Add a log entry to the shop logs table. + * + * @param item $item The shop item + * @param bool $purchase Whether it is a purchase/gift or an activation + * @param double $points_sum The points sum (cost of the purchase/gift) + * @param int $recipient_id The recipient identifier + * @param double $points_old The user's old points total + * @param double $points_new The user's new points total + * @param int $time The log time + * @return bool Whether or not the log was successfully added + * @access public + */ + public function add(item $item, $purchase, $points_sum = 0.00, $recipient_id = 0, $points_old = 0.00, $points_new = 0.00, $time = 0) + { + $time = $time ? $time : time(); + + if ($purchase && $points_sum) + { + $points_old = $points_old ? $points_old : $this->user->data['user_points']; + $points_new = $points_new ? $points_new : $points_old - $points_sum; + } + + $data = [ + 'log_ip' => (string) $this->user->ip, + 'log_time' => (int) $time, + 'points_old' => (double) $points_old, + 'points_sum' => (double) $points_sum, + 'points_new' => (double) $points_new, + 'item_purchase' => (bool) $purchase, + 'item_id' => (int) $item->get_id(), + 'category_id' => (int) $item->get_category(), + 'user_id' => (int) $this->user->data['user_id'], + 'recipient_id' => (int) $recipient_id, + ]; + + $sql = 'INSERT INTO ' . $this->logs_table . ' ' . $this->db->sql_build_array('INSERT', $data); + $this->db->sql_query($sql); + + return (bool) $this->db->sql_affectedrows(); + } + + /** + * Get the shop log entries for a specific user. + * + * @param string $sql_where The SQL WHERE statement + * @param string $sql_order The SQL ORDER BY statement + * @param string $sql_dir The SQL ORDER BY direction + * @param int $limit The amount of entries to return + * @param int $start The offset from where to return entries + * @param int $user_id The user identifier + * @return array The shop log entries + * @access public + */ + public function get_user_logs($sql_where, $sql_order, $sql_dir, $limit, $start, $user_id = 0) + { + if ($user_id) + { + $user_where = '(l.user_id = ' . (int) $user_id . ' OR l.recipient_id = ' . (int) $user_id . ')'; + + $sql_where = $sql_where ? $user_where . ' AND ' . $sql_where : $user_where; + } + + $sql_array = [ + 'SELECT' => 'l.*, i.item_title, c.category_title, + u.username, u.user_colour, + r.username as recipient_name, r.user_colour as recipient_colour', + 'FROM' => [$this->logs_table => 'l'], + 'LEFT_JOIN' => [ + [ + 'FROM' => [$this->categories_table => 'c'], + 'ON' => 'l.category_id = c.category_id', + ], + [ + 'FROM' => [$this->items_table => 'i'], + 'ON' => 'l.item_id = i.item_id', + ], + [ + 'FROM' => [$this->users_table => 'r'], + 'ON' => 'l.recipient_id = r.user_id', + ], + [ + 'FROM' => [$this->users_table => 'u'], + 'ON' => 'l.user_id = u.user_id', + ], + ], + 'WHERE' => $sql_where, + 'ORDER_BY' => "{$sql_order} {$sql_dir}", + ]; + + $sql = $this->db->sql_build_query('SELECT', $sql_array); + $result = $this->db->sql_query_limit($sql, $limit, $start); + $rowset = $this->db->sql_fetchrowset($result); + $this->db->sql_freeresult($result); + + return (array) $rowset; + } + + /** + * Get the shop log entries count for a specific user. + * + * @param string $sql_where The SQL WHERE statement + * @param int $user_id The user identifier + * @return int The shop log entries count + * @access public + */ + public function get_user_logs_count($sql_where, $user_id = 0) + { + if ($user_id) + { + $user_where = 'l.user_id = ' . (int) $user_id; + + $sql_where = $sql_where ? $user_where . ' AND ' . $sql_where : $user_where; + } + + $sql_array = [ + 'SELECT' => 'COUNT(l.log_id) as count', + 'FROM' => [$this->logs_table => 'l'], + 'WHERE' => $sql_where, + ]; + + $sql = $this->db->sql_build_query('SELECT', $sql_array); + $result = $this->db->sql_query($sql); + $count = $this->db->sql_fetchfield('count'); + $this->db->sql_freeresult($result); + + return (int) $count; + } + + /** + * Delete shop log entries. + * + * @param bool $all Delete all log entries + * @param array $ids The log entry identifiers + * @return bool Whether or not any entries were deleted + * @access public + */ + public function delete($all, array $ids) + { + $sql = 'DELETE FROM ' . $this->logs_table + . (!$all ? ' WHERE ' . $this->db->sql_in_set('log_id', $ids) : ''); + $this->db->sql_query($sql); + + return (bool) $this->db->sql_affectedrows(); + } +} diff --git a/ext/phpbbstudio/ass/helper/router.php b/ext/phpbbstudio/ass/helper/router.php new file mode 100644 index 0000000..b08db5a --- /dev/null +++ b/ext/phpbbstudio/ass/helper/router.php @@ -0,0 +1,137 @@ +helper = $helper; + + $this->root_path = $root_path; + $this->php_ext = $php_ext; + } + + /** + * Get the URL for a category. + * + * @param string $category_slug The category slug + * @param string $mode The mode + * @return string The URL + * @access public + */ + public function category($category_slug, $mode = 'category') + { + $mode = $mode === 'inventory' ? $mode : 'category'; + + return $this->helper->route("phpbbstudio_ass_{$mode}", ['category_slug' => $category_slug]); + } + + /** + * Get the URL for the inventory. + * + * @param string $category_slug The category slug + * @param string $item_slug The item slug + * @param int $index The item index + * @param string $action The action + * @param array $params Additional parameters + * @return string The URL + * @access public + */ + public function inventory($category_slug = '', $item_slug = '', $index = 1, $action = '', array $params = []) + { + $params = array_merge(['category_slug' => $category_slug, 'item_slug' => $item_slug, 'index' => $index, 'action' => $action], $params); + + return $this->helper->route('phpbbstudio_ass_inventory', $params); + } + + /** + * Get the URL for the gift action. + * + * @param string $category_slug The category slug + * @param string $item_slug The item slug + * @return string The URL + * @access public + */ + public function gift($category_slug, $item_slug) + { + return $this->helper->route('phpbbstudio_ass_gift', ['category_slug' => $category_slug, 'item_slug' => $item_slug]); + } + + /** + * Get the URL for an item. + * + * @param string $category_slug The category slug + * @param string $item_slug The item slug + * @return string The URL + * @access public + */ + public function item($category_slug, $item_slug) + { + return $this->helper->route('phpbbstudio_ass_item', ['category_slug' => $category_slug, 'item_slug' => $item_slug]); + } + + /** + * Get the URL for the purchase action. + * + * @param string $category_slug The category slug + * @param string $item_slug The item slug + * @return string The URL + * @access public + */ + public function purchase($category_slug, $item_slug) + { + return $this->helper->route('phpbbstudio_ass_purchase', ['category_slug' => $category_slug, 'item_slug' => $item_slug]); + } + + /** + * Get the URL for a 'regular' phpBB page (viewtopic, viewforum, etc..). + * + * @param string $page The phpBB page + * @param string|array $params The parameters + * @param bool $is_amp Whether it is & or & + * @param bool $ajax Whether the request is AJAX or not + * @return string The URL + * @access public + */ + public function regular($page, $params = '', $is_amp = true, $ajax = false) + { + if ($ajax) + { + return append_sid(generate_board_url() . "/{$page}.{$this->php_ext}", $params, $is_amp, false, false); + } + else + { + return append_sid("{$this->root_path}{$page}.{$this->php_ext}", $params, $is_amp, false, false); + } + } +} diff --git a/ext/phpbbstudio/ass/helper/time.php b/ext/phpbbstudio/ass/helper/time.php new file mode 100644 index 0000000..e35a934 --- /dev/null +++ b/ext/phpbbstudio/ass/helper/time.php @@ -0,0 +1,183 @@ +language = $language; + $this->timezone = $config['board_timezone']; + } + + /** + * Get the board's default timezone. + * + * @return string The board's default timezone + * @access public + */ + public function get_timezone() + { + return $this->timezone; + } + + /** + * Get the default datetime format. + * + * @return string The default datetime format + * @access public + */ + public function get_format() + { + return $this->format; + } + + /** + * Create and get the UNIX timestamp from a formatted string. + * + * @param string $string The formatted string + * @return int + * @access public + */ + public function create_from_format($string) + { + $tz = date_default_timezone_get(); + + date_default_timezone_set($this->timezone); + + $unix = date_timestamp_get(\DateTime::createFromFormat($this->format, $string)); + + date_default_timezone_set($tz); + + return (int) $unix; + } + + /** + * Turn an amount of seconds into a readable string. + * + * For example "1 day, 2 hours and 5 minutes" + * + * @param int $seconds The amount of seconds + * @return string + * @access public + */ + public function seconds_to_string($seconds) + { + $time = [ + 'ASS_DAYS' => 0, + 'ASS_HOURS' => 0, + 'ASS_MINUTES' => 0, + 'ASS_SECONDS' => 0, + ]; + + $time['ASS_DAYS'] = floor($seconds / self::DAY); + + $seconds = ($seconds % self::DAY); + $time['ASS_HOURS'] = floor($seconds / self::HOUR); + + $seconds %= self::HOUR; + $time['ASS_MINUTES'] = floor($seconds / self::MINUTE); + + $seconds %= self::MINUTE; + $time['ASS_SECONDS'] = $seconds; + + $time = array_filter($time); + $count = count($time); + $index = 1; + $string = ''; + + foreach ($time as $key => $value) + { + if ($index !== 1) + { + $string .= $this->language->lang($index === $count ? 'ASS_AND' : 'COMMA_SEPARATOR'); + } + + $string .= $this->language->lang($key, $value); + + $index++; + } + + return $string; + } + + /** + * Check if the current time is within two UNIX timestamps. + * + * @param int $start The first UNIX timestamp + * @param int $until The second UNIX timestamp + * @return bool + * @access public + */ + public function within($start, $until) + { + return (bool) ($start < time() && time() < $until); + } + + /** + * Check if the current time is after a provided UNIX timestamp + additional seconds. + * + * An example would be that the provided timestamp is a item purchase time + * and the additional seconds are the amount of seconds before the item is non-refundable. + * + * If the additional seconds is 0, it means the item is already non-refundable. + * + * @param int $time The provided UNIX timestamp + * @param int $seconds The additional seconds + * @return bool Whether or not the time has expired + * @access public + */ + public function has_expired($time, $seconds) + { + return (bool) (!empty($seconds) && $time + $seconds <= time()); + } + + /** + * Check if the current time is within a week of a provided UNIX timestamp + additional seconds. + * + * @param int $time The provided UNIX timestamp + * @param int $seconds The additional seconds + * @return bool Whether or not the time will expire within a week + * @access public + */ + public function will_expire($time, $seconds) + { + return (bool) (!empty($seconds) && ($time + $seconds) >= (time() - self::WEEK)); + } +} diff --git a/ext/phpbbstudio/ass/items/manager.php b/ext/phpbbstudio/ass/items/manager.php new file mode 100644 index 0000000..85c0928 --- /dev/null +++ b/ext/phpbbstudio/ass/items/manager.php @@ -0,0 +1,83 @@ +language = $language; + $this->template = $template; + $this->types = $types; + } + + /** + * Get an item type. + * + * @param string $type The item type + * @return item_type|null + * @access public + */ + public function get_type($type) + { + return isset($this->types[$type]) ? $this->types[$type] : null; + } + + /** + * Set and assign the item types for a + + +
                + +
                +
                + +
                + +
                + +
                +
                + +
                + +
                + +
                +
                + +
                + +
                + +
                +
                +
                + + + +
                +
                + +
                + +
                +
                +
                + + + +
                +
                + +
                +
                +
                + +
                + +
                +
                + +
                + +
                + +
                +
                + +
                + +
                + +
                +
                + +
                + +
                +
                + + +
                + + + + + + + +
                + {% if shop_pagination|length %} + {{ include('@phpbbstudio_ass/ass_pagination.html') }} + {% endif %} +
                +
                +
                + {{ ITEMS_COUNT }} +
                +
                + +{% endblock %} diff --git a/ext/phpbbstudio/ass/styles/all/template/ass_confirm_body.html b/ext/phpbbstudio/ass/styles/all/template/ass_confirm_body.html new file mode 100644 index 0000000..78ba994 --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/ass_confirm_body.html @@ -0,0 +1,3 @@ +
                +

                {{ MESSAGE_TEXT }}

                +
                diff --git a/ext/phpbbstudio/ass/styles/all/template/ass_exception.html b/ext/phpbbstudio/ass/styles/all/template/ass_exception.html new file mode 100644 index 0000000..c28cdc7 --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/ass_exception.html @@ -0,0 +1,70 @@ +{% extends '@phpbbstudio_aps/aps_main.html' %} + +{% block includes %} + {% INCLUDECSS '@phpbbstudio_ass/ass_exception.css' %} +{% endblock %} + +{% block nav %} + {% for category in ass_shop_categories %} +
              • + + {% if category.ICON %}{% endif %} + {{ category.TITLE }} + +
              • + {% endfor %} +{% endblock %} + +{% block main %} +
                +
                +
                +

                {{ lang('ASS_ERROR_TITLE', aps_name()) ~ lang('ELLIPSIS') }}

                +
                +
                + {{ EXCEPTION_CODE }} +
                +
                + +
                +
                +

                {{ EXCEPTION_TEXT }}

                +
                + {% if EXCEPTION_DESC %} +
                +

                {{ EXCEPTION_DESC }}

                +
                + {% endif %} + +
                + +
                {{ include('@phpbbstudio_ass/images/gold-pot.svg') }}
                +
                {% for i in 1..5 %}{% endfor %}
                +
                {{ include('@phpbbstudio_ass/images/tree.svg') }}
                +
                {{ include('@phpbbstudio_ass/images/trees.svg') }}
                +
                {{ include('@phpbbstudio_ass/images/mountain-summit.svg') }}
                +
                {{ include('@phpbbstudio_ass/images/mountain-summit.svg') }}
                +
                {{ include('@phpbbstudio_ass/images/rising-graph.svg') }}
                + + + + + + + + + + + + + + + + + + +
                +{% endblock %} diff --git a/ext/phpbbstudio/ass/styles/all/template/ass_history.html b/ext/phpbbstudio/ass/styles/all/template/ass_history.html new file mode 100644 index 0000000..87526ef --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/ass_history.html @@ -0,0 +1,156 @@ +{% extends '@phpbbstudio_aps/aps_main.html' %} + +{% block includes %} + {% INCLUDECSS '@phpbbstudio_ass/ass_common.css' %} +{% endblock %} + +{% block main %} +
                +

                {{ lang('ASS_HISTORY') }}

                +
                + + {% for log in ass_logs %} +
                +
                +
                +
                + {% if log.U_CATEGORY %} + + {{ log.CATEGORY_TITLE }} + + {% else %} + {{ log.CATEGORY_TITLE }} + {% endif %} + + {% if log.U_ITEM %} + + {{ log.ITEM_TITLE }} + + {% else %} + + {{ log.ITEM_TITLE }} + + {% endif %} +
                +
                + {{ log.LOG_TIME }} +
                +
                +
                + +
                +
                +
                + {% if log.S_PURCHASE %} + {% if log.RECIPIENT %} + {% if log.S_GIFT_RECEIVED %} + {{ lang('ASS_LOG_ITEM_RECEIVED', log.USER) }} + {% else %} + {{ lang('ASS_LOG_ITEM_GIFTED', log.RECIPIENT) }} + {% endif %} + {% else %} + {{ lang('ASS_LOG_ITEM_PURCHASED') }} + {% endif %} + {% else %} + {{ lang('ASS_LOG_ITEM_USED') ~ lang('COLON') }} {{ log.LOG_ACTION }} + {% endif %} +
                +
                {{ aps_display(log.POINTS_OLD, false) }}
                +
                {{ aps_display(log.POINTS_SUM, false) }}
                +
                {{ aps_display(log.POINTS_NEW, false) }}
                +
                +
                +
                + {% else %} +
                +
                +

                {{ lang('ASS_HISTORY_EMPTY') ~ lang('ELLIPSIS') }}

                +
                + +
                + {% endfor %} + + {% if ass_logs|length %} +
                +
                +
                + +
                +
                + +
                + {% if shop_pagination|length %} + {{ include('@phpbbstudio_ass/ass_pagination.html') }} + {% endif %} +
                + +
                +
                + {{ TOTAL_LOGS }} +
                +
                +
                + {% endif %} +{% endblock %} diff --git a/ext/phpbbstudio/ass/styles/all/template/ass_inventory.html b/ext/phpbbstudio/ass/styles/all/template/ass_inventory.html new file mode 100644 index 0000000..48b1838 --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/ass_inventory.html @@ -0,0 +1,136 @@ +{% extends '@phpbbstudio_aps/aps_main.html' %} + +{% block includes %} + {% INCLUDECSS '@phpbbstudio_ass/jquery-ui.min.css' %} + {% INCLUDEJS '@phpbbstudio_ass/js/jquery-ui.min.js' %} + + {% INCLUDEJS '@phpbbstudio_ass/js/sketch.js' %} + + {% INCLUDECSS '@phpbbstudio_ass/ass_common.css' %} + {% INCLUDEJS '@phpbbstudio_ass/js/ass_common.js' %} + + {% EVENT phpbbstudio_ass_includes_inventory %} +{% endblock %} + +{% block nav %} + {% for category in ass_shop_categories %} +
              • + + {% if category.ICON %}{% endif %} + {{ category.TITLE }} + +
              • + {% endfor %} +{% endblock %} + +{% block nav_right %} +
              • + + + {{ lang('ASS_HISTORY') }} + +
              • +{% endblock %} + +{% block main %} + {% if ass_categories|length %} +
                + +
                +
                +
                + {% for category in ass_categories %} +
                +

                {{ category.TITLE }}

                +
                + {% for batch in category.items|batch(3) %} + + {% endfor %} + {% endfor %} +
                + +
                +
                +
                +
                +
                + {{ aps_icon(true) }} +
                +
                + + {% if ITEM_INFO %} + {{ include('@phpbbstudio_ass/ass_item_inventory.html', {item: ITEM_INFO, S_GIFT_ANIMATION: S_IS_GIFT, S_LOADED_ITEM: true}) }} + {% endif %} +
                +
                +
                + {% else %} +
                +
                +

                {{ lang('ASS_ITEMS_NONE_INVENTORY') ~ lang('ELLIPSIS') }}

                +
                + +
                + {% endif %} +{% endblock %} diff --git a/ext/phpbbstudio/ass/styles/all/template/ass_item.html b/ext/phpbbstudio/ass/styles/all/template/ass_item.html new file mode 100644 index 0000000..c1291a6 --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/ass_item.html @@ -0,0 +1,286 @@ +{% extends '@phpbbstudio_aps/aps_main.html' %} + +{% block includes %} + {% INCLUDEJS '@phpbbstudio_ass/js/slick.min.js' %} + {% INCLUDECSS '@phpbbstudio_ass/slick.css' %} + + {% INCLUDECSS '@phpbbstudio_ass/ass_item.css' %} + {% INCLUDECSS '@phpbbstudio_ass/ass_common.css' %} + {% INCLUDEJS '@phpbbstudio_ass/js/ass_common.js' %} +{% endblock %} + +{% block nav %} + {% for category in ass_shop_categories %} +
              • + + {% if category.ICON %}{% endif %} + {{ category.TITLE }} + +
              • + {% endfor %} +{% endblock %} + +{% block nav_right %} +
              • + +
              • +{% endblock %} + +{% block main %} + {{ include('@phpbbstudio_ass/ass_carousel.html') }} + + + + {% set delay = 3 %} + +
                +
                + {% if ICON %} +
                +
                + {{ lang('ASS_ITEM_ICON') }} + {% set delay = delay + 1 %} + +
                + +
                +
                +
                + {% endif %} + +
                +
                + {{ lang('ASS_ITEM_TITLE') }} + {% set delay = delay + 1 %} + +

                {{ TITLE }}

                +
                +
                + + {% if not S_ACTIVE %} +
                +
                + {{ lang('ASS_ITEM_INACTIVE') }} +
                +
                + {% endif %} + +
                +
                + {{ lang('ASS_ITEM_IMAGE') }} + {% set delay = delay + 1 %} + +
                + {% if S_FEATURED and S_SALE and SHOP_PANEL_FEATURED_SALE_ICON %} + + {% elseif S_FEATURED and SHOP_PANEL_FEATURED_ICON %} + + {% elseif S_SALE and SHOP_PANEL_SALE_ICON %} + + {% endif %} + + {% if not BACKGROUND_SRC %} + + {% endif %} +
                +
                +
                + +
                +
                +
                + {{ lang('ASS_ITEM_PRICE') }} + {% set delay = delay + 1 %} + +
                + {% if S_SALE %} + {{ aps_display(SALE_PRICE) }} + {% else %} + {{ aps_display(PRICE) }} + {% endif %} +
                +
                +
                +
                +
                + {{ lang('ASS_ITEM_STOCK') }} + {% set delay = delay + 1 %} + +
                + {% if S_STOCK_UNLIMITED %} + + {% elseif STOCK %} + {{ STOCK }} /{{ STOCK_INITIAL }} + {% endif %} +
                +
                +
                + + {% if not S_GIFT_ONLY %} + + {% endif %} + + {% if S_GIFT and S_CAN_GIFT %} + + {% endif %} + +
                +
                + {{ lang('ASS_ITEM_INFORMATION') }} + {% set delay = delay + 1 %} + +
                +
                  +
                • + + {% if COUNT %} + {{ lang('ASS_ITEM_USE_COUNT', COUNT) }} + {% else %} + {{ lang('ASS_ITEM_USE_UNLIMITED') }} + {% endif %} +
                • +
                • + + {% if EXPIRE_WITHIN %} + {{ lang('ASS_ITEM_EXPIRE_WITHIN', EXPIRE_WITHIN) }} + {% else %} + {{ lang('ASS_ITEM_EXPIRE_NEVER') }} + {% endif %} +
                • +
                • + + {% if REFUND_WITHIN %} + {{ lang('ASS_ITEM_REFUND_WITHIN', REFUND_WITHIN) }} + {% else %} + {{ lang('ASS_ITEM_REFUND_NEVER') }} + {% endif %} +
                • +
                +
                +
                +
                + +
                +
                +
                + {{ lang('ASS_ITEM_CREATE_TIME') }} + {% set delay = delay + 1 %} + +
                + + {{ user.format_date(CREATE_TIME) }} +
                +
                + {% if EDIT_TIME %} +
                + {{ lang('ASS_ITEM_EDIT_TIME') }} + {% set delay = delay + 1 %} + +
                + + {{ user.format_date(EDIT_TIME) }} +
                +
                + {% endif %} +
                +
                + + {% if S_SALE %} +
                + {{ lang('ASS_SALE_END_ON', user.format_date(SALE_UNTIL_UNIX)) }} +
                + {% endif %} + + {% if AVAILABLE_UNTIL_UNIX and S_AVAILABLE %} +
                + {{ lang('ASS_AVAILABILITY_END_ON', user.format_date(AVAILABLE_UNTIL_UNIX)) }} +
                + {% endif %} +
                + + {% if DESC_HTML %} +
                +
                + {{ lang('ASS_ITEM_DESCRIPTION') }} + {% set delay = delay + 1 %} + +
                + {{ DESC_HTML }} +
                +
                +
                + {% endif %} +
                + + {% if IMAGES_SRC %} +
                +
                + {{ lang('ASS_ITEM_IMAGES') }} + {% set delay = delay + 1 %} + +
                +
                +
                + {% for image in IMAGES_SRC %} +
                + {{ TITLE }} +
                + {% endfor %} +
                +
                + +
                +
                +
                + {% endif %} + + {% if ass_related|length %} +
                +
                +
                + {{ lang('ASS_RELATED_ITEMS') }} + {% set delay = delay + 1 %} + +
                +

                {{ lang('ASS_RELATED_ITEMS') }}

                +
                +
                +
                + {% for item in ass_related %} +
                + {{ include('@phpbbstudio_ass/ass_item_panel.html') }} +
                + {% endfor %} +
                +
                +
                +
                +
                + {% endif %} +
                +{% endblock %} diff --git a/ext/phpbbstudio/ass/styles/all/template/ass_item_carousel.html b/ext/phpbbstudio/ass/styles/all/template/ass_item_carousel.html new file mode 100644 index 0000000..01d6a0a --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/ass_item_carousel.html @@ -0,0 +1,37 @@ +
                + {% if not item.BACKGROUND_SRC %} + + {% endif %} + +
                + {% if S_CAN_PURCHASE and not item.S_GIFT_ONLY %} + {% if item.S_OUT_OF_STOCK %} + + + {{ lang('ASS_PURCHASE') }} + + {% else %} + + + {{ lang('ASS_PURCHASE') }} + + {% endif %} + {% endif %} + {% if S_CAN_GIFT and item.S_GIFT %} + + + {{ lang('ASS_GIFT') }} + + {% endif %} + + {{ item.TITLE }} + + {% if item.S_OUT_OF_STOCK %} + {{ aps_display(item.PRICE) }} + {% elseif item.S_SALE %} + {{ aps_display(item.SALE_PRICE) }} + {% else %} + {{ aps_display(item.PRICE) }} + {% endif %} +
                +
                diff --git a/ext/phpbbstudio/ass/styles/all/template/ass_item_inventory.html b/ext/phpbbstudio/ass/styles/all/template/ass_item_inventory.html new file mode 100644 index 0000000..57493fc --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/ass_item_inventory.html @@ -0,0 +1,185 @@ +
                + {% if S_GIFT_ANIMATION %} +
                +
                + +
                + + + {% for i in 1..5 %} + + {% endfor %} + + + {% for i in 1..5 %} + + {% endfor %} + + + + + + + + + + + + +
                + {% endif %} + +
                +
                + {% if item.U_STACK_PREV %} + + + + {% endif %} +
                +
                +

                {{ item.TITLE }}

                +
                +
                + {% if item.U_STACK_NEXT %} + + + + {% endif %} +
                + + {% if U_DOWNLOAD_FILE and S_LOADED_ITEM %} +
                +
                +
                + + {{ lang('ASS_TYPE_FILE_START') }} +
                +
                + {{ lang('ASS_TYPE_FILE_START_NOT') }} +
                + +
                +
                + {% endif %} + + {% if item.S_TYPE_ERROR %} +
                +
                +
                + + {{ lang('ERROR') }} +
                +
                + {{ lang('ASS_ITEM_TYPE_NOT_EXIST') }} +
                +
                + {{ lang('ASS_ERROR_LOGGED') }} +
                +
                +
                + {% endif %} + + {% if item.S_LIMIT or item.S_HAS_EXPIRED or item.S_WILL_EXPIRE %} +
                +
                + {{ lang(item.S_LIMIT ? 'ASS_ITEM_USE_REACHED' : (item.S_HAS_EXPIRED ? 'ASS_ITEM_EXPIRED' : 'ASS_ITEM_EXPIRE_SOON')) }} +
                +
                + {% endif %} + + {% if item.S_GIFTED %} +
                +
                + + +
                + {{ lang('ASS_GIFTED_BY', item.GIFTER_NAME) }} +
                +
                +
                + {% endif %} + +
                +
                +
                + {% if not item.BACKGROUND_SRC %} + + {% endif %} +
                +
                +
                + +
                +
                +
                +
                  +
                • + + {{ item.USE_COUNT }} /{{ item.COUNT ?: '∞' }} +
                • +
                +
                +
                +
                + +
                + {% if not item.S_HAS_EXPIRED and not item.S_LIMIT and not item.S_TYPE_ERROR %} + + {{ item.ACTIVATE }} + + {% endif %} +
                + + {% if item.S_REFUND %} + + {% endif %} + + + + {% if item.DESC_HTML %} +
                +
                +
                + {{ item.DESC_HTML }} +
                +
                +
                + {% endif %} +
                +
                diff --git a/ext/phpbbstudio/ass/styles/all/template/ass_item_panel.html b/ext/phpbbstudio/ass/styles/all/template/ass_item_panel.html new file mode 100644 index 0000000..94ceb95 --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/ass_item_panel.html @@ -0,0 +1,47 @@ +
                +
                + {% if ITEM_PANEL_ICON %} + + {% elseif item.S_FEATURED and item.S_SALE and SHOP_PANEL_FEATURED_SALE_ICON %} + + {% elseif item.S_FEATURED and SHOP_PANEL_FEATURED_ICON %} + + {% elseif item.S_SALE and SHOP_PANEL_SALE_ICON %} + + {% endif %} + +

                {{ item.TITLE }}

                +
                +
                + {% if not item.BACKGROUND_SRC %} + + {% endif %} +
                + +
                diff --git a/ext/phpbbstudio/ass/styles/all/template/ass_message.html b/ext/phpbbstudio/ass/styles/all/template/ass_message.html new file mode 100644 index 0000000..655d649 --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/ass_message.html @@ -0,0 +1 @@ +

                {{ MESSAGE_TEXT }}

                diff --git a/ext/phpbbstudio/ass/styles/all/template/ass_pagination.html b/ext/phpbbstudio/ass/styles/all/template/ass_pagination.html new file mode 100644 index 0000000..b79171e --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/ass_pagination.html @@ -0,0 +1,45 @@ + diff --git a/ext/phpbbstudio/ass/styles/all/template/ass_purchase.html b/ext/phpbbstudio/ass/styles/all/template/ass_purchase.html new file mode 100644 index 0000000..1afa448 --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/ass_purchase.html @@ -0,0 +1,191 @@ +{% extends '@phpbbstudio_aps/aps_main.html' %} + +{% block includes %} + {% INCLUDECSS '@phpbbstudio_ass/ass_common.css' %} +{% endblock %} + +{% block nav %} + {% for category in ass_shop_categories %} +
              • + + {% if category.ICON %}{% endif %} + {{ category.TITLE }} + +
              • + {% endfor %} +{% endblock %} + +{% block main %} +
                + {% if ICON %} +
                +
                +
                + +
                +
                +
                + {% endif %} + +
                +
                +

                {{ TITLE }}

                +
                +
                + +
                +
                +
                + {% if S_FEATURED and S_SALE and SHOP_PANEL_FEATURED_SALE_ICON %} + + {% elseif S_FEATURED and SHOP_PANEL_FEATURED_ICON %} + + {% elseif S_SALE and SHOP_PANEL_SALE_ICON %} + + {% endif %} + + {% if not BACKGROUND_SRC %} + + {% endif %} +
                +
                +
                + +
                + {% if S_CONFIRM_ACTION %} +
                +
                +
                + {% if S_SALE %} + {{ aps_display(ASS_PURCHASE_PRICE) }} + {% else %} + {{ aps_display(ASS_PURCHASE_PRICE) }} + {% endif %} +
                +
                +
                +
                +
                +
                + {% if S_STOCK_UNLIMITED %} + + {% elseif STOCK %} + {{ STOCK }} /{{ STOCK_INITIAL }} + {% endif %} +
                +
                +
                + {% endif %} + +
                +
                +
                +
                  +
                • + + {% if COUNT %} + {{ lang('ASS_ITEM_USE_COUNT', COUNT) }} + {% else %} + {{ lang('ASS_ITEM_USE_UNLIMITED') }} + {% endif %} +
                • +
                • + + {% if EXPIRE_WITHIN %} + {{ lang('ASS_ITEM_EXPIRE_WITHIN', EXPIRE_WITHIN) }} + {% else %} + {{ lang('ASS_ITEM_EXPIRE_NEVER') }} + {% endif %} +
                • +
                • + + {% if REFUND_WITHIN %} + {{ lang('ASS_ITEM_REFUND_WITHIN', REFUND_WITHIN) }} + {% else %} + {{ lang('ASS_ITEM_REFUND_NEVER') }} + {% endif %} +
                • +
                +
                +
                +
                + + {% if S_CONFIRM_ACTION %} +
                +
                +
                +
                +

                {{ MESSAGE_TITLE }}

                +
                + + {% if not S_ASS_PURCHASE %} +
                + + +
                + {% endif %} + +
                + {{ MESSAGE_TEXT }} +
                + + +
                +
                +
                + {% else %} +
                +
                +
                +

                {{ lang(S_ASS_PURCHASE ? 'ASS_PURCHASE' : 'ASS_GIFT') }}

                +
                +
                +

                + {% if S_ASS_PURCHASE %} + {{ lang('ASS_PURCHASE_SUCCESS') }} + {{ lang('ASS_INVENTORY_ADDED') }} + {% else %} + {{ lang('ASS_GIFT_SUCCESS') }} + {{ lang('ASS_INVENTORY_ADDED_USER', RECIPIENT_NAME) }} + {% endif %} +

                + +

                + + {{ lang('ASS_POINTS_DEDUCTED') ~ lang('COLON') }} + {{ aps_display(ASS_PURCHASE_PRICE) }} + + + {{ lang('ASS_POINTS_BALANCE', aps_name()) ~ lang('COLON') }} + {{ aps_display(NEW_USER_POINTS) }} + +

                + + {% if EXPIRE_UNIX %} +

                + {{ lang('ASS_ITEM_USE_BEFORE') }} + {{ user.format_date(EXPIRE_UNIX) }} +

                + {% endif %} + +

                + {% if S_ASS_PURCHASE %} + {{ lang('ASS_INVENTORY_GO') }} + {% else %} + {{ lang('ASS_SHOP_INDEX') }} + {% if U_SEND_PM %} + {{ lang('SEND_PRIVATE_MESSAGE') }} + {% endif %} + {% endif %} +

                +
                +
                +
                + {% endif %} +
                +
                +{% endblock %} diff --git a/ext/phpbbstudio/ass/styles/all/template/ass_purchase_ajax.html b/ext/phpbbstudio/ass/styles/all/template/ass_purchase_ajax.html new file mode 100644 index 0000000..e3a2f2d --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/ass_purchase_ajax.html @@ -0,0 +1,144 @@ +
                +
                +
                +
                +
                +

                {{ TITLE }}

                +
                +
                + + {% if not S_PURCHASE_SUCCESS %} +
                +
                +
                + {% if S_SALE %} + {{ aps_display(ASS_PURCHASE_PRICE) }} + {% else %} + {{ aps_display(ASS_PURCHASE_PRICE) }} + {% endif %} +
                +
                +
                +
                +
                +
                + {% if S_STOCK_UNLIMITED %} + + {% elseif STOCK %} + {{ STOCK }} /{{ STOCK_INITIAL }} + {% endif %} +
                +
                +
                + {% endif %} + +
                +
                +
                +
                  +
                • + + {% if COUNT %} + {{ lang('ASS_ITEM_USE_COUNT', COUNT) }} + {% else %} + {{ lang('ASS_ITEM_USE_UNLIMITED') }} + {% endif %} +
                • +
                • + + {% if EXPIRE_WITHIN %} + {{ lang('ASS_ITEM_EXPIRE_WITHIN', EXPIRE_WITHIN) }} + {% else %} + {{ lang('ASS_ITEM_EXPIRE_NEVER') }} + {% endif %} +
                • +
                • + + {% if REFUND_WITHIN %} + {{ lang('ASS_ITEM_REFUND_WITHIN', REFUND_WITHIN) }} + {% else %} + {{ lang('ASS_ITEM_REFUND_NEVER') }} + {% endif %} +
                • +
                +
                +
                +
                + + {% if not S_PURCHASE_SUCCESS and not S_ASS_PURCHASE %} +
                +
                +
                +
                + + +
                +
                +
                +
                + {% endif %} + + {% if S_ASS_PURCHASE and not S_PURCHASE_SUCCESS %} +
                +
                +
                + {% if ASS_ITEM_STACK %} + {{ lang('ASS_WARNING_STACK', ASS_ITEM_STACK) }} + {% else %} + {{ lang('ASS_ERROR_NOT_OWNED') }} + {% endif %} +
                +
                +
                + {% endif %} + +
                +
                +
                + {% if not S_PURCHASE_SUCCESS %} + {{ MESSAGE_TEXT }} + {% else %} +

                + {% if S_ASS_PURCHASE %} + {{ lang('ASS_PURCHASE_SUCCESS') }} + {{ lang('ASS_INVENTORY_ADDED') }} + {% else %} + {{ lang('ASS_GIFT_SUCCESS') }} + {{ lang('ASS_INVENTORY_ADDED_USER', RECIPIENT_NAME) }} + {% endif %} +

                +

                + + {{ lang('ASS_POINTS_DEDUCTED') ~ lang('COLON') }} + {{ aps_display(ASS_PURCHASE_PRICE) }} + + + {{ lang('ASS_POINTS_BALANCE', aps_name()) ~ lang('COLON') }} + {{ aps_display(NEW_USER_POINTS) }} + +

                + + {% if EXPIRE_UNIX %} +

                + {{ lang('ASS_ITEM_USE_BEFORE') }} + {{ user.format_date(EXPIRE_UNIX) }} +

                + {% endif %} + +

                + {% if S_ASS_PURCHASE %} + {{ lang('ASS_INVENTORY_GO') }} + {% else %} + {{ lang('ASS_SHOP_INDEX') }} + {% if U_SEND_PM %} + {{ lang('SEND_PRIVATE_MESSAGE') }} + {% endif %} + {% endif %} +

                + {% endif %} +
                +
                +
                +
                +
                +
                diff --git a/ext/phpbbstudio/ass/styles/all/template/ass_shop.html b/ext/phpbbstudio/ass/styles/all/template/ass_shop.html new file mode 100644 index 0000000..28cace9 --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/ass_shop.html @@ -0,0 +1,64 @@ +{% extends '@phpbbstudio_aps/aps_main.html' %} + +{% block includes %} + {% INCLUDEJS '@phpbbstudio_ass/js/slick.min.js' %} + {% INCLUDECSS '@phpbbstudio_ass/slick.css' %} + + {% INCLUDEJS '@phpbbstudio_ass/js/ass_common.js' %} + {% INCLUDECSS '@phpbbstudio_ass/ass_common.css' %} +{% endblock %} + +{% block nav %} + {% for category in ass_shop_categories %} +
              • + + {% if category.ICON %}{% endif %} + {{ category.TITLE }} + +
              • + {% endfor %} +{% endblock %} + +{% block main %} + {{ include('@phpbbstudio_ass/ass_carousel.html') }} + +
                + {% for panel, data in ass_panels %} + {% if attribute(loops, 'ass_' ~ panel) is defined %} + {% set items = attribute(loops, 'ass_' ~ panel) %} + {% if data.carousel %} +
                +
                +
                + {% set icon = attribute(_context, 'SHOP_PANEL_' ~ panel|upper ~ '_ICON') %} + {% if icon %} + + {% endif %} + +

                {{ lang(data.title) }}

                +
                +
                +
                + {% for item in items %} + {{ include('@phpbbstudio_ass/ass_item_carousel.html') }} + {% else %} +
                + {{ lang('ASS_ITEMS_NONE') }} +
                + {% endfor %} +
                +
                + +
                +
                + {% else %} + {% for item in items %} +
                + {{ include('@phpbbstudio_ass/ass_item_panel.html', { ITEM_PANEL_ICON: attribute(_context, 'SHOP_PANEL_' ~ panel|upper ~ '_ICON') }) }} +
                + {% endfor %} + {% endif %} + {% endif %} + {% endfor %} +
                +{% endblock %} diff --git a/ext/phpbbstudio/ass/styles/all/template/blocks/available.html b/ext/phpbbstudio/ass/styles/all/template/blocks/available.html new file mode 100644 index 0000000..0aa1b1b --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/blocks/available.html @@ -0,0 +1,27 @@ +{% block width %}s12 m6{% endblock %} + +{% block content %} + {% if available|length %} +
                + {% for item in available %} +
                + {% if item.BACKGROUND_SRC %} + {{ item.TITLE }} + {% else %} + + {% endif %} + {{ item.TITLE }}
                + + + {{ user.format_date(item.AVAILABLE_UNTIL_UNIX) }} + + + + +
                + {% endfor %} +
                + {% else %} +
                {{ lang('ASS_ITEMS_NONE') }}
                + {% endif %} +{% endblock %} diff --git a/ext/phpbbstudio/ass/styles/all/template/blocks/buyers.html b/ext/phpbbstudio/ass/styles/all/template/blocks/buyers.html new file mode 100644 index 0000000..abe6d95 --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/blocks/buyers.html @@ -0,0 +1,13 @@ +{% block width %}s12 m4{% endblock %} + +{% block content %} +
                + {% for user in buyers %} +
                + {{ user.AVATAR ? user.AVATAR }} + {{ user.NAME }} +

                {{ user.COUNT }}

                +
                + {% endfor %} +
                +{% endblock %} diff --git a/ext/phpbbstudio/ass/styles/all/template/blocks/charts.html b/ext/phpbbstudio/ass/styles/all/template/blocks/charts.html new file mode 100644 index 0000000..6076cf4 --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/blocks/charts.html @@ -0,0 +1,18 @@ +{% block width %}s12 m6 aps-js{% endblock %} + +{% block content %} + +

                {{ lang('APS_POINTS_PER_FORUM', aps_name()) }}

                +
                + +{% endblock %} diff --git a/ext/phpbbstudio/ass/styles/all/template/blocks/featured.html b/ext/phpbbstudio/ass/styles/all/template/blocks/featured.html new file mode 100644 index 0000000..635199d --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/blocks/featured.html @@ -0,0 +1,27 @@ +{% block width %}s12 m6{% endblock %} + +{% block content %} + {% if featured|length %} +
                + {% for item in featured %} +
                + {% if item.BACKGROUND_SRC %} + {{ item.TITLE }} + {% else %} + + {% endif %} + {{ item.TITLE }}
                + + + {{ user.format_date(item.FEATURED_UNTIL_UNIX) }} + + + + +
                + {% endfor %} +
                + {% else %} +
                {{ lang('ASS_ITEMS_NONE') }}
                + {% endif %} +{% endblock %} diff --git a/ext/phpbbstudio/ass/styles/all/template/blocks/gifters.html b/ext/phpbbstudio/ass/styles/all/template/blocks/gifters.html new file mode 100644 index 0000000..4a00c1e --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/blocks/gifters.html @@ -0,0 +1,13 @@ +{% block width %}s12 m4{% endblock %} + +{% block content %} +
                + {% for user in gifters %} +
                + {{ user.AVATAR ? user.AVATAR }} + {{ user.NAME }} +

                {{ user.COUNT }}

                +
                + {% endfor %} +
                +{% endblock %} diff --git a/ext/phpbbstudio/ass/styles/all/template/blocks/limited.html b/ext/phpbbstudio/ass/styles/all/template/blocks/limited.html new file mode 100644 index 0000000..c9c4e30 --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/blocks/limited.html @@ -0,0 +1,23 @@ +{% block width %}s12 m6{% endblock %} + +{% block content %} + {% if limited|length %} +
                + {% for item in limited %} +
                + {% if item.BACKGROUND_SRC %} + {{ item.TITLE }} + {% else %} + + {% endif %} + {{ item.TITLE }} + + {{ item.STOCK }} + +
                + {% endfor %} +
                + {% else %} +
                {{ lang('ASS_ITEMS_NONE') }}
                + {% endif %} +{% endblock %} diff --git a/ext/phpbbstudio/ass/styles/all/template/blocks/purchases.html b/ext/phpbbstudio/ass/styles/all/template/blocks/purchases.html new file mode 100644 index 0000000..ce74788 --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/blocks/purchases.html @@ -0,0 +1,23 @@ +{% block width %}s12 m6{% endblock %} + +{% block content %} + {% if purchases|length %} +
                + {% for item in purchases %} +
                + {% if item.BACKGROUND_SRC %} + {{ item.TITLE }} + {% else %} + + {% endif %} + {{ item.TITLE }}
                + + {{ item.PURCHASES }} + +
                + {% endfor %} +
                + {% else %} +
                {{ lang('ASS_ITEMS_NONE') }}
                + {% endif %} +{% endblock %} diff --git a/ext/phpbbstudio/ass/styles/all/template/blocks/purchases_category.html b/ext/phpbbstudio/ass/styles/all/template/blocks/purchases_category.html new file mode 100644 index 0000000..4713c63 --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/blocks/purchases_category.html @@ -0,0 +1,18 @@ +{% block width %}s12 m6 aps-js{% endblock %} + +{% block content %} + +

                {{ lang('APS_PURCHASES_PER_GROUP', aps_name()) }}

                +
                + +{% endblock %} diff --git a/ext/phpbbstudio/ass/styles/all/template/blocks/purchases_group.html b/ext/phpbbstudio/ass/styles/all/template/blocks/purchases_group.html new file mode 100644 index 0000000..1ceec6e --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/blocks/purchases_group.html @@ -0,0 +1,18 @@ +{% block width %}s12 m6 aps-js{% endblock %} + +{% block content %} + +

                {{ lang('APS_PURCHASES_PER_GROUP', aps_name()) }}

                +
                + +{% endblock %} diff --git a/ext/phpbbstudio/ass/styles/all/template/blocks/recent.html b/ext/phpbbstudio/ass/styles/all/template/blocks/recent.html new file mode 100644 index 0000000..c7df7f0 --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/blocks/recent.html @@ -0,0 +1,27 @@ +{% block width %}s12 m6{% endblock %} + +{% block content %} + {% if recent|length %} +
                + {% for item in recent %} +
                + {% if item.BACKGROUND_SRC %} + {{ item.TITLE }} + {% else %} + + {% endif %} + {{ item.TITLE }}
                + + + {{ user.format_date(item.CREATE_TIME) }} + + + + +
                + {% endfor %} +
                + {% else %} +
                {{ lang('ASS_ITEMS_NONE') }}
                + {% endif %} +{% endblock %} diff --git a/ext/phpbbstudio/ass/styles/all/template/blocks/sale.html b/ext/phpbbstudio/ass/styles/all/template/blocks/sale.html new file mode 100644 index 0000000..c9ed044 --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/blocks/sale.html @@ -0,0 +1,27 @@ +{% block width %}s12 m6{% endblock %} + +{% block content %} + {% if sale|length %} +
                + {% for item in sale %} +
                + {% if item.BACKGROUND_SRC %} + {{ item.TITLE }} + {% else %} + + {% endif %} + {{ item.TITLE }}
                + + + {{ user.format_date(item.SALE_UNTIL_UNIX) }} + + + + +
                + {% endfor %} +
                + {% else %} +
                {{ lang('ASS_ITEMS_NONE') }}
                + {% endif %} +{% endblock %} diff --git a/ext/phpbbstudio/ass/styles/all/template/blocks/spenders.html b/ext/phpbbstudio/ass/styles/all/template/blocks/spenders.html new file mode 100644 index 0000000..f4c64f7 --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/blocks/spenders.html @@ -0,0 +1,13 @@ +{% block width %}s12 m4{% endblock %} + +{% block content %} +
                + {% for user in spenders %} +
                + {{ user.AVATAR ? user.AVATAR }} + {{ user.NAME }} +

                {{ aps_display(user.COUNT) }}

                +
                + {% endfor %} +
                +{% endblock %} diff --git a/ext/phpbbstudio/ass/styles/all/template/event/navbar_header_profile_list_after.html b/ext/phpbbstudio/ass/styles/all/template/event/navbar_header_profile_list_after.html new file mode 100644 index 0000000..1424dd1 --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/event/navbar_header_profile_list_after.html @@ -0,0 +1,7 @@ +{% if S_ASS_ENABLED and S_ASS_NAVBAR_HEADER_PROFILE_LIST_AFTER %} +
              • + + {{ lang('ASS_SHOP') }} + +
              • +{% endif %} diff --git a/ext/phpbbstudio/ass/styles/all/template/event/navbar_header_profile_list_before.html b/ext/phpbbstudio/ass/styles/all/template/event/navbar_header_profile_list_before.html new file mode 100644 index 0000000..1d9f4ac --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/event/navbar_header_profile_list_before.html @@ -0,0 +1,7 @@ +{% if S_ASS_ENABLED and S_ASS_NAVBAR_HEADER_PROFILE_LIST_BEFORE %} +
              • + + {{ lang('ASS_SHOP') }} + +
              • +{% endif %} diff --git a/ext/phpbbstudio/ass/styles/all/template/event/navbar_header_quick_links_after.html b/ext/phpbbstudio/ass/styles/all/template/event/navbar_header_quick_links_after.html new file mode 100644 index 0000000..5388dc1 --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/event/navbar_header_quick_links_after.html @@ -0,0 +1,7 @@ +{% if S_ASS_ENABLED and S_ASS_NAVBAR_HEADER_QUICK_LINKS_AFTER %} +
              • + + {{ lang('ASS_SHOP') }} + +
              • +{% endif %} diff --git a/ext/phpbbstudio/ass/styles/all/template/event/navbar_header_quick_links_before.html b/ext/phpbbstudio/ass/styles/all/template/event/navbar_header_quick_links_before.html new file mode 100644 index 0000000..92f537d --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/event/navbar_header_quick_links_before.html @@ -0,0 +1,7 @@ +{% if S_ASS_ENABLED and S_ASS_NAVBAR_HEADER_QUICK_LINKS_BEFORE %} +
              • + + {{ lang('ASS_SHOP') }} + +
              • +{% endif %} diff --git a/ext/phpbbstudio/ass/styles/all/template/event/navbar_header_user_profile_append.html b/ext/phpbbstudio/ass/styles/all/template/event/navbar_header_user_profile_append.html new file mode 100644 index 0000000..8ff41ba --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/event/navbar_header_user_profile_append.html @@ -0,0 +1,7 @@ +{% if S_ASS_ENABLED and S_ASS_NAVBAR_HEADER_USER_PROFILE_APPEND %} +
              • + + {{ lang('ASS_SHOP') }} + +
              • +{% endif %} diff --git a/ext/phpbbstudio/ass/styles/all/template/event/navbar_header_user_profile_prepend.html b/ext/phpbbstudio/ass/styles/all/template/event/navbar_header_user_profile_prepend.html new file mode 100644 index 0000000..2f45b95 --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/event/navbar_header_user_profile_prepend.html @@ -0,0 +1,7 @@ +{% if S_ASS_ENABLED and S_ASS_NAVBAR_HEADER_USER_PROFILE_PREPEND %} +
              • + + {{ lang('ASS_SHOP') }} + +
              • +{% endif %} diff --git a/ext/phpbbstudio/ass/styles/all/template/event/overall_footer_after.html b/ext/phpbbstudio/ass/styles/all/template/event/overall_footer_after.html new file mode 100644 index 0000000..226b084 --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/event/overall_footer_after.html @@ -0,0 +1,5 @@ +{% if S_ASS_SHOP %} +
                + {% for i in 1..8 %}{% endfor %} +
                +{% endif %} diff --git a/ext/phpbbstudio/ass/styles/all/template/event/overall_footer_breadcrumb_append.html b/ext/phpbbstudio/ass/styles/all/template/event/overall_footer_breadcrumb_append.html new file mode 100644 index 0000000..17f0aaf --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/event/overall_footer_breadcrumb_append.html @@ -0,0 +1,7 @@ +{% if S_ASS_ENABLED and S_ASS_OVERALL_FOOTER_BREADCRUMB_APPEND %} +
              • + + {{ lang('ASS_SHOP') }} + +
              • +{% endif %} diff --git a/ext/phpbbstudio/ass/styles/all/template/event/overall_footer_teamlink_after.html b/ext/phpbbstudio/ass/styles/all/template/event/overall_footer_teamlink_after.html new file mode 100644 index 0000000..b4a23cb --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/event/overall_footer_teamlink_after.html @@ -0,0 +1,7 @@ +{% if S_ASS_ENABLED and S_ASS_OVERALL_FOOTER_TEAMLINK_AFTER %} +
              • + + {{ lang('ASS_SHOP') }} + +
              • +{% endif %} diff --git a/ext/phpbbstudio/ass/styles/all/template/event/overall_footer_teamlink_before.html b/ext/phpbbstudio/ass/styles/all/template/event/overall_footer_teamlink_before.html new file mode 100644 index 0000000..f41cf07 --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/event/overall_footer_teamlink_before.html @@ -0,0 +1,7 @@ +{% if S_ASS_ENABLED and S_ASS_OVERALL_FOOTER_TEAMLINK_BEFORE %} +
              • + + {{ lang('ASS_SHOP') }} + +
              • +{% endif %} diff --git a/ext/phpbbstudio/ass/styles/all/template/event/overall_footer_timezone_after.html b/ext/phpbbstudio/ass/styles/all/template/event/overall_footer_timezone_after.html new file mode 100644 index 0000000..1709eeb --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/event/overall_footer_timezone_after.html @@ -0,0 +1,7 @@ +{% if S_ASS_ENABLED and S_ASS_OVERALL_FOOTER_TIMEZONE_AFTER %} +
              • + + {{ lang('ASS_SHOP') }} + +
              • +{% endif %} diff --git a/ext/phpbbstudio/ass/styles/all/template/event/overall_footer_timezone_before.html b/ext/phpbbstudio/ass/styles/all/template/event/overall_footer_timezone_before.html new file mode 100644 index 0000000..95f61ae --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/event/overall_footer_timezone_before.html @@ -0,0 +1,7 @@ +{% if S_ASS_ENABLED and S_ASS_OVERALL_FOOTER_TIMEZONE_BEFORE %} +
              • + + {{ lang('ASS_SHOP') }} + +
              • +{% endif %} diff --git a/ext/phpbbstudio/ass/styles/all/template/event/overall_header_navigation_append.html b/ext/phpbbstudio/ass/styles/all/template/event/overall_header_navigation_append.html new file mode 100644 index 0000000..e051153 --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/event/overall_header_navigation_append.html @@ -0,0 +1,7 @@ +{% if S_ASS_ENABLED and S_ASS_OVERALL_HEADER_NAVIGATION_APPEND %} +
              • + + {{ lang('ASS_SHOP') }} + +
              • +{% endif %} diff --git a/ext/phpbbstudio/ass/styles/all/template/event/overall_header_navigation_prepend.html b/ext/phpbbstudio/ass/styles/all/template/event/overall_header_navigation_prepend.html new file mode 100644 index 0000000..acffbbb --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/event/overall_header_navigation_prepend.html @@ -0,0 +1,7 @@ +{% if S_ASS_ENABLED and S_ASS_OVERALL_HEADER_NAVIGATION_PREPEND %} +
              • + + {{ lang('ASS_SHOP') }} + +
              • +{% endif %} diff --git a/ext/phpbbstudio/ass/styles/all/template/event/phpbbstudio_aps_menu_append.html b/ext/phpbbstudio/ass/styles/all/template/event/phpbbstudio_aps_menu_append.html new file mode 100644 index 0000000..b713163 --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/event/phpbbstudio_aps_menu_append.html @@ -0,0 +1,16 @@ +{% if S_ASS_ENABLED %} +
              • + + + {{ lang('ASS_SHOP') }} + +
              • + {% if S_REGISTERED_USER %} +
              • + + + {{ lang('ASS_INVENTORY') }} + +
              • + {% endif %} +{% endif %} diff --git a/ext/phpbbstudio/ass/styles/all/template/js/ass_common.js b/ext/phpbbstudio/ass/styles/all/template/js/ass_common.js new file mode 100644 index 0000000..8fca1b9 --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/js/ass_common.js @@ -0,0 +1,640 @@ +jQuery(function($) { + let studio = { + body: '.aps-body', + link: '[data-shop-link]', + ajax: { + alert: $('#phpbb_alert'), + data: $('#darkenwrapper'), + spinner: $('#studio_spinner'), + timer: null + }, + carousel: { + items: $('[data-shop-carousel]'), + item: $('[data-shop-carousel-data]'), + data: { + dotsClass: 'shop-carousel-dots', + nextArrow: '', + prevArrow: '', + draggable: true + } + }, + inventory: { + card: $('.shop-inventory-card'), + info: $('.shop-inventory-info'), + items: $('.shop-inventory-list .shop-inventory-panel'), + proxy: $('.shop-inventory-proxy'), + classes: { + card: '.shop-inventory-card', + cardActive: 'shop-inventory-card-active', + cardPaused: 'shop-inventory-card-paused', + item: '.shop-inventory-item', + itemAnimate: 'shop-inventory-item-animate', + itemCount: '.shop-inventory-item-count', + itemTime: '.shop-inventory-item-time', + link: '.shop-inventory-link', + panel: '.shop-inventory-panel', + refund: '.shop-inventory-refund', + row: '.shop-inventory-row', + stack: '.shop-inventory-stack' + } + }, + sketch: { + dots: [], + pool: [], + limit: 280, + timer: null, + timeout: 1500, + colours: ['#12a3eb', '#12a3eb', '#2172b8', '#18a39b', '#82c545', '#f8b739', '#f06045', '#ed2861', '#c12680', '#5d3191'], + drawing: null, + overlay: $('.shop-inventory-overlay'), + } + }; + + /** + * Set up carousel items. + * + * @return {void} + */ + studio.carousel.setup = function() { + let none = false, + data = studio.carousel.item.data(); + + if (data) { + none = !data.dots && !data.arrows; + + delete data['shopCarouselData']; + } + + // Register all carousel items + studio.carousel.items.each(function() { + let $footer = $(this).parent().next('.aps-panel-footer'); + + let slickData = $.extend({}, data, studio.carousel.data); + + if ($(this).data('shop-carousel') === 'images') { + $.extend(slickData, { + arrows: false, + asNavFor: $footer, + dots: false, + fade: true, + slidesToScroll: 1, + slidesToShow: 1 + }); + } else { + $.extend(slickData, { + appendDots: $footer, + appendArrows: $footer + }); + } + + // Initiate a Slick instance + $(this).slick(slickData); + + if ($(this).data('shop-carousel') === 'images') { + $footer.slick({ + arrows: false, + asNavFor: this, + centerMode: true, + dots: false, + focusOnSelect: true, + slidesToScroll: 1, + slidesToShow: 3 + }); + } + + // If there are no navigation arrows or dots, remove the footer + if (none) { + $footer.remove(); + } + }); + + /** + * Remove the carousel data element. + */ + studio.carousel.item.remove(); + }; + + studio.carousel.setup(); + + /** + * Show the loader for Shop AJAX requests. + * + * @return {void} + */ + studio.ajax.loading = function() { + if (!studio.ajax.spinner.is(':visible')) { + studio.ajax.spinner.fadeIn(100); + + studio.ajax.clear(); + studio.ajax.timer = setTimeout(studio.ajax.timeout, 60000) + } + }; + + /** + * Show the time out message for Shop AJAX requests. + * + * @return {void} + */ + studio.ajax.timeout = function() { + studio.ajax.message({ + title: studio.ajax.alert.attr('data-l-err'), + html: studio.ajax.alert.attr('data-l-timeout-processing-req'), + type: 'error' + }); + }; + + /** + * Get the localised message for Shop AJAX request errors. + * + * @param {string} attribute + * @return {string} + */ + studio.ajax.data.get = function(attribute) { + return $(this).attr(`data-ajax-error-${attribute}`); + }; + + /** + * Clear the timer for Shop AJAX requests. + * + * @return {void} + */ + studio.ajax.clear = function() { + if (studio.ajax.timer !== null) { + clearTimeout(studio.ajax.timer); + + studio.ajax.timer = null; + } + }; + + /** + * The error handler for Shop AJAX requests. + * + * @param {Object} jqXHR + * @param {Object} jqXHR.responseJSON + * @param {String} jqXHR.responseText + * @param {string} textStatus + * @param {string} errorThrown + * @return {void} + */ + studio.ajax.error = function(jqXHR, textStatus, errorThrown) { + if (typeof console !== 'undefined' && console.log) { + console.log(`AJAX error. status: ${textStatus}, message: ${errorThrown}`); + } + + studio.ajax.clear(); + + let responseText, errorText = ''; + + 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 = studio.ajax.data.get(`text-${textStatus}`); + + if (typeof errorText !== 'string' || !errorText.length) { + errorText = studio.ajax.data.get('text'); + } + } + + studio.ajax.message({ + title: typeof jqXHR.responseJSON !== 'undefined' ? jqXHR.responseJSON.title : studio.ajax.data.get('title', false), + html: errorText, + type: 'error' + }); + }; + + /** + * The success handler for Shop AJAX requests. + * + * @param {Object} r + * @param {function} callback + * @return {void} + */ + studio.ajax.success = function(r, callback) { + studio.ajax.clear(); + + /** + * @param {string} r.MESSAGE_BODY The message template body + * @param {string} r.YES_VALUE The "yes" language string + * @param {string} r.S_CONFIRM_ACTION The confirm_box() action + * @param {string} r.S_HIDDEN_FIELDS The confirm_box() hidden fields + */ + + // If there is no confirm action (not a confirm_box()) + if (typeof r.S_CONFIRM_ACTION === 'undefined') { + // Call the callback + if (typeof phpbb.ajaxCallbacks[callback] === 'function') { + phpbb.ajaxCallbacks[callback].call(this, r); + } + + // Show the message + studio.ajax.message({ + title: r.MESSAGE_TITLE, + html: r.MESSAGE_TEXT + }); + } else { + // Show the confirm box + studio.ajax.message({ + title: r.MESSAGE_TITLE, + html: r.MESSAGE_BODY, + type: 'question', + showCancelButton: true, + confirmButtonText: r.YES_VALUE + }).then((result) => { + // A button was pressed, was it the Confirm button? + if (result.value) { + let data = $('form', swal.getContent()).serialize(); + + data = data ? `${data}&` : ''; + data = data + $(`
                ${r.S_HIDDEN_FIELDS}
                `).serialize() + `&confirm=${r.YES_VALUE}`; + + // Make the request + studio.ajax.request(r.S_CONFIRM_ACTION, callback, 'POST', data); + } + }); + + $('input', swal.getContent()).on('keydown', function(e) { + if (e.which === 13) { + swal.clickConfirm(); + + e.preventDefault(); + + return false; + } + }); + } + }; + + /** + * Show a message for Shop AJAX requests. + * + * @param {Object} options + * @return {swal} + */ + studio.ajax.message = function(options) { + return swal.fire($.extend({ + type: 'success', + cancelButtonClass: 'aps-button-alert aps-button-red', + confirmButtonClass: 'aps-button-alert aps-button-green shop-button-active', + showCloseButton: true, + buttonsStyling: false, + }, options)); + }; + + /** + * Make a Shop AJAX request. + * + * @param {string} url + * @param {function} callback + * @param {string=} type + * @param {Object=} data + * @return {void} + */ + studio.ajax.request = function(url, callback, type, data) { + // Start the loading function + studio.ajax.loading(); + + // Make the request + let request = $.ajax({ + url: url, + type: type || 'GET', + data: data || '', + error: studio.ajax.error, + success: function(r) { + studio.ajax.success(r, callback) + }, + }); + + // No matter what the request returns, always stop the spinner + request.always(function() { + if (studio.ajax.spinner.is(':visible')){ + studio.ajax.spinner.fadeOut(100); + } + }); + }; + + /** + * Register all shop links for Shop AJAX requests. + */ + $(studio.body).on('click', studio.link, function(e) { + studio.ajax.request($(this).attr('href'), $(this).data('shop-link')); + + e.preventDefault(); + }); + + /** + * Remove an inventory item. + * + * @param {Object} r + * @return {void} + */ + studio.inventory.remove = function(r) { + let $items = $(`[data-shop-item="${r.id}"]`), + $section = $items.parents(studio.inventory.classes.panel), + $column = $section.parent('.aps-col'), + $row = $column.parent(studio.inventory.classes.row); + + let $stack = $section.find(studio.inventory.classes.stack), + stack = typeof $stack !== 'undefined' ? parseInt($stack.text()) : false; + + if (stack === false) { + $items.remove(); + $section.remove(); + $column.remove(); + + if ($row.children().length === 0) { + $row.remove(); + } + } else { + $items.not($section.find($items)).remove(); + + if (r.index > 1) { + let replaceUrl = location.href.slice(0, -r.index.toString().length) + '1'; + + $section.find(studio.inventory.classes.link).attr('href', replaceUrl); + phpbb.history.replaceUrl(replaceUrl); + } + + if (stack === 2) { + $stack.parent().remove(); + } else { + $stack.text(stack - 1); + } + + if (r.item) { + $items.replaceWith(r.item); + } else { + $section.draggable('destroy'); + $section.addClass('shop-cursor-pointer'); + } + } + }; + + /** + * Add AJAX callback for purchasing a Shop item. + * + * @param {Object} r The response object + * @param {string} r.points The new points value + * @param {number|bool} r.stock The new stock value + * @return {void} + */ + phpbb.addAjaxCallback('shop_purchase', function(r) { + $('.aps-menu > .aps-list-right > :first-child > span').text(r.points); + + if (r.stock !== false) { + let $item = $(`[data-shop-item="${r.id}"]`); + + $item.find('.shop-item-stock').text(r.stock); + + if (r.stock === 0) { + $item.find('[data-shop-link="shop_purchase"]').remove(); + } + } + }); + + /** + * Add AJAX callback for deleting a Shop item. + * + * @return {void} + */ + phpbb.addAjaxCallback('shop_inventory_delete', studio.inventory.remove); + + /** + * Add AJAX callback for using a Shop item. + * + * @param {Object} r The response object + * @param {number} r.id The item identifier + * @param {Object} r.data The item data + * @param {number} r.data.use_count The item use count + * @param {string} r.data.use_time The item use time + * @param {bool} r.delete The delete indicator + * @param {bool} r.success The success indicator + * @param {string} r.file A window location for downloading a file + * @param {int} r.index The item stack index + * @return {void} + */ + phpbb.addAjaxCallback('shop_inventory_use', function(r) { + if (r.success) { + if (r.delete) { + studio.inventory.remove(r); + } else { + let $item = $(`[data-shop-item="${r.id}"]`), + $refund = $item.find(studio.inventory.classes.refund).remove(); + + $refund.remove(); + $refund.next().removeClass('s2').addClass('s4'); + + if (r.data) { + $item.find(studio.inventory.classes.itemCount).text(r.data['use_count']); + $item.find(studio.inventory.classes.itemTime).text(r.data['use_time']).parent().show(); + } + + if (r.limit) { + let notice = '
                ' + r.limit + '
                '; + + $item.find('> .aps-row > :first-child').after(notice); + + $item.find('[data-shop-link="shop_inventory_use"]').remove(); + } + } + + if (r.file) { + setTimeout(function() { + window.location = r.file; + }, 1500); + } + } + }); + + /** + * Register the inventory items as draggables. + * + * @return {void} + */ + studio.inventory.items.each(function() { + $(this).draggable({ + appendTo: studio.body, + containment: studio.body, + helper: 'clone', + scope: 'inventory', + snap: studio.inventory.classes.card, + snapMode: 'inner', + start: function(e, ui) { + studio.inventory.info.children().not(studio.inventory.proxy).remove(); + + $(this).draggable('instance').offset.click = { + left: Math.floor(ui.helper.width() / 2), + top: Math.floor(ui.helper.height() / 2) + }; + + studio.sketch.create(ui.helper); + }, + stop: function() { + studio.sketch.timer = setTimeout(studio.sketch.destroy, studio.sketch.timeout); + } + }); + }); + + /** + * Register the inventory 'placeholder card' as droppable. + * + * @return {void} + */ + studio.inventory.card.each(function() { + $(this).droppable({ + activeClass: studio.inventory.classes.cardActive, + hoverClass: studio.inventory.classes.cardPaused, + scope: 'inventory', + drop: function(e, ui) { + let clone = ui.draggable.clone(false), + link = clone.find(studio.inventory.classes.link), + item = clone.find(studio.inventory.classes.item); + + studio.inventory.info.append(clone); + + clone.removeClass('ui-draggable ui-draggable-handle'); + + link.remove(); + + item.addClass(studio.inventory.classes.itemAnimate); + }, + }) + }); + + studio.sketch.dot = function(x, y, radius) { + this.init(x, y, radius); + }; + + studio.sketch.dot.prototype = { + init: function(x, y, radius) { + this.alive = true; + + this.radius = radius || 10; + this.wander = 0.15; + this.theta = random(TWO_PI); + this.drag = 0.92; + this.color = '#12a3eb'; + + this.x = x || 0.0; + this.y = y || 0.0; + + this.vx = 0.0; + this.vy = 0.0; + }, + + move: function() { + this.x += this.vx; + this.y += this.vy; + + this.vx *= this.drag; + this.vy *= this.drag; + + this.theta += random(-0.5, 0.5) * this.wander; + this.vx += Math.sin(this.theta) * 0.1; + this.vy += Math.cos(this.theta) * 0.1; + + this.radius *= 0.96; + this.alive = this.radius > 0.5; + }, + + draw: function( ctx ) { + ctx.beginPath(); + ctx.arc(this.x, this.y, this.radius, 0, TWO_PI); + ctx.fillStyle = this.color; + ctx.fill(); + } + }; + + studio.sketch.create = function(helper) { + studio.sketch.clear(); + + studio.sketch.drawing = Sketch.create({ + container: studio.sketch.overlay[0], + eventTarget: helper[0], + retina: 'auto' + }); + + studio.sketch.drawing.spawn = function(x, y) { + let dot, theta, force; + + if (studio.sketch.dots.length >= studio.sketch.limit) { + studio.sketch.pool.push(studio.sketch.dots.shift()); + } + + dot = studio.sketch.pool.length ? studio.sketch.pool.pop() : new studio.sketch.dot(); + dot.init( x, y, random(5, 40)); + + dot.wander = random(0.5, 2.0); + dot.color = random(studio.sketch.colours); + dot.drag = random(0.9, 0.99); + + theta = random(TWO_PI); + force = random(2, 8); + + dot.vx = Math.sin(theta) * force; + dot.vy = Math.cos(theta) * force; + + studio.sketch.dots.push(dot); + }; + + studio.sketch.drawing.update = function() { + let i, dot; + + for (i = studio.sketch.dots.length - 1; i >= 0; i--) { + dot = studio.sketch.dots[i]; + + if (dot.alive) { + dot.move(); + } else { + studio.sketch.pool.push(studio.sketch.dots.splice(i, 1)[0]); + } + } + }; + + studio.sketch.drawing.draw = function() { + studio.sketch.drawing.globalCompositeOperation = 'lighter'; + + for (let i = studio.sketch.dots.length - 1; i >= 0; i--) { + studio.sketch.dots[i].draw(studio.sketch.drawing); + } + }; + + studio.sketch.drawing.mousemove = function() { + let touch, max, i, j, n; + + for (i = 0, n = studio.sketch.drawing.touches.length; i < n; i++) { + touch = studio.sketch.drawing.touches[i]; + max = random(1, 4); + + for (j = 0; j < max; j++) { + studio.sketch.drawing.spawn(touch.x, touch.y); + } + } + }; + }; + + studio.sketch.clear = function() { + if (studio.sketch.timer !== null) { + clearTimeout(studio.sketch.timer); + + studio.sketch.timer = null; + } + + studio.sketch.destroy(); + }; + + studio.sketch.destroy = function() { + if (studio.sketch.drawing !== null) { + studio.sketch.drawing.clear(); + studio.sketch.drawing.destroy(); + + studio.sketch.drawing = null; + } + }; +}); diff --git a/ext/phpbbstudio/ass/styles/all/template/js/jquery-ui.js b/ext/phpbbstudio/ass/styles/all/template/js/jquery-ui.js new file mode 100644 index 0000000..1cc47dd --- /dev/null +++ b/ext/phpbbstudio/ass/styles/all/template/js/jquery-ui.js @@ -0,0 +1,2814 @@ +/*! jQuery UI - v1.12.1 - 2019-07-19 +* http://jqueryui.com +* Includes: widget.js, data.js, scroll-parent.js, widgets/draggable.js, widgets/droppable.js, widgets/mouse.js +* Copyright jQuery Foundation and other contributors; Licensed MIT */ + +(function( factory ) { + if ( typeof define === "function" && define.amd ) { + + // AMD. Register as an anonymous module. + define([ "jquery" ], factory ); + } else { + + // Browser globals + factory( jQuery ); + } +}(function( $ ) { + +$.ui = $.ui || {}; + +var version = $.ui.version = "1.12.1"; + + +/*! + * jQuery UI Widget 1.12.1 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + */ + +//>>label: Widget +//>>group: Core +//>>description: Provides a factory for creating stateful widgets with a common API. +//>>docs: http://api.jqueryui.com/jQuery.widget/ +//>>demos: http://jqueryui.com/widget/ + + + +var widgetUuid = 0; +var widgetSlice = Array.prototype.slice; + +$.cleanData = ( function( orig ) { + return function( elems ) { + var events, elem, i; + for ( i = 0; ( elem = elems[ i ] ) != null; i++ ) { + try { + + // Only trigger remove when necessary to save time + events = $._data( elem, "events" ); + if ( events && events.remove ) { + $( elem ).triggerHandler( "remove" ); + } + + // Http://bugs.jquery.com/ticket/8235 + } catch ( e ) {} + } + orig( elems ); + }; +} )( $.cleanData ); + +$.widget = function( name, base, prototype ) { + var existingConstructor, constructor, basePrototype; + + // ProxiedPrototype allows the provided prototype to remain unmodified + // so that it can be used as a mixin for multiple widgets (#8876) + var proxiedPrototype = {}; + + var namespace = name.split( "." )[ 0 ]; + name = name.split( "." )[ 1 ]; + var fullName = namespace + "-" + name; + + if ( !prototype ) { + prototype = base; + base = $.Widget; + } + + if ( $.isArray( prototype ) ) { + prototype = $.extend.apply( null, [ {} ].concat( prototype ) ); + } + + // Create selector for plugin + $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) { + return !!$.data( elem, fullName ); + }; + + $[ namespace ] = $[ namespace ] || {}; + existingConstructor = $[ namespace ][ name ]; + constructor = $[ namespace ][ name ] = function( options, element ) { + + // Allow instantiation without "new" keyword + if ( !this._createWidget ) { + return new constructor( options, element ); + } + + // Allow instantiation without initializing for simple inheritance + // must use "new" keyword (the code above always passes args) + if ( arguments.length ) { + this._createWidget( options, element ); + } + }; + + // Extend with the existing constructor to carry over any static properties + $.extend( constructor, existingConstructor, { + version: prototype.version, + + // Copy the object used to create the prototype in case we need to + // redefine the widget later + _proto: $.extend( {}, prototype ), + + // Track widgets that inherit from this widget in case this widget is + // redefined after a widget inherits from it + _childConstructors: [] + } ); + + basePrototype = new base(); + + // We need to make the options hash a property directly on the new instance + // otherwise we'll modify the options hash on the prototype that we're + // inheriting from + basePrototype.options = $.widget.extend( {}, basePrototype.options ); + $.each( prototype, function( prop, value ) { + if ( !$.isFunction( value ) ) { + proxiedPrototype[ prop ] = value; + return; + } + proxiedPrototype[ prop ] = ( function() { + function _super() { + return base.prototype[ prop ].apply( this, arguments ); + } + + function _superApply( args ) { + return base.prototype[ prop ].apply( this, args ); + } + + return function() { + var __super = this._super; + var __superApply = this._superApply; + var returnValue; + + this._super = _super; + this._superApply = _superApply; + + returnValue = value.apply( this, arguments ); + + this._super = __super; + this._superApply = __superApply; + + return returnValue; + }; + } )(); + } ); + constructor.prototype = $.widget.extend( basePrototype, { + + // TODO: remove support for widgetEventPrefix + // always use the name + a colon as the prefix, e.g., draggable:start + // don't prefix for widgets that aren't DOM-based + widgetEventPrefix: existingConstructor ? ( basePrototype.widgetEventPrefix || name ) : name + }, proxiedPrototype, { + constructor: constructor, + namespace: namespace, + widgetName: name, + widgetFullName: fullName + } ); + + // If this widget is being redefined then we need to find all widgets that + // are inheriting from it and redefine all of them so that they inherit from + // the new version of this widget. We're essentially trying to replace one + // level in the prototype chain. + if ( existingConstructor ) { + $.each( existingConstructor._childConstructors, function( i, child ) { + var childPrototype = child.prototype; + + // Redefine the child widget using the same prototype that was + // originally used, but inherit from the new version of the base + $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, + child._proto ); + } ); + + // Remove the list of existing child constructors from the old constructor + // so the old child constructors can be garbage collected + delete existingConstructor._childConstructors; + } else { + base._childConstructors.push( constructor ); + } + + $.widget.bridge( name, constructor ); + + return constructor; +}; + +$.widget.extend = function( target ) { + var input = widgetSlice.call( arguments, 1 ); + var inputIndex = 0; + var inputLength = input.length; + var key; + var value; + + for ( ; inputIndex < inputLength; inputIndex++ ) { + for ( key in input[ inputIndex ] ) { + value = input[ inputIndex ][ key ]; + if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) { + + // Clone objects + if ( $.isPlainObject( value ) ) { + target[ key ] = $.isPlainObject( target[ key ] ) ? + $.widget.extend( {}, target[ key ], value ) : + + // Don't extend strings, arrays, etc. with objects + $.widget.extend( {}, value ); + + // Copy everything else by reference + } else { + target[ key ] = value; + } + } + } + } + return target; +}; + +$.widget.bridge = function( name, object ) { + var fullName = object.prototype.widgetFullName || name; + $.fn[ name ] = function( options ) { + var isMethodCall = typeof options === "string"; + var args = widgetSlice.call( arguments, 1 ); + var returnValue = this; + + if ( isMethodCall ) { + + // If this is an empty collection, we need to have the instance method + // return undefined instead of the jQuery instance + if ( !this.length && options === "instance" ) { + returnValue = undefined; + } else { + this.each( function() { + var methodValue; + var instance = $.data( this, fullName ); + + if ( options === "instance" ) { + returnValue = instance; + return false; + } + + if ( !instance ) { + return $.error( "cannot call methods on " + name + + " prior to initialization; " + + "attempted to call method '" + options + "'" ); + } + + if ( !$.isFunction( instance[ options ] ) || options.charAt( 0 ) === "_" ) { + return $.error( "no such method '" + options + "' for " + name + + " widget instance" ); + } + + methodValue = instance[ options ].apply( instance, args ); + + if ( methodValue !== instance && methodValue !== undefined ) { + returnValue = methodValue && methodValue.jquery ? + returnValue.pushStack( methodValue.get() ) : + methodValue; + return false; + } + } ); + } + } else { + + // Allow multiple hashes to be passed on init + if ( args.length ) { + options = $.widget.extend.apply( null, [ options ].concat( args ) ); + } + + this.each( function() { + var instance = $.data( this, fullName ); + if ( instance ) { + instance.option( options || {} ); + if ( instance._init ) { + instance._init(); + } + } else { + $.data( this, fullName, new object( options, this ) ); + } + } ); + } + + return returnValue; + }; +}; + +$.Widget = function( /* options, element */ ) {}; +$.Widget._childConstructors = []; + +$.Widget.prototype = { + widgetName: "widget", + widgetEventPrefix: "", + defaultElement: "
                ", + + options: { + classes: {}, + disabled: false, + + // Callbacks + create: null + }, + + _createWidget: function( options, element ) { + element = $( element || this.defaultElement || this )[ 0 ]; + this.element = $( element ); + this.uuid = widgetUuid++; + this.eventNamespace = "." + this.widgetName + this.uuid; + + this.bindings = $(); + this.hoverable = $(); + this.focusable = $(); + this.classesElementLookup = {}; + + if ( element !== this ) { + $.data( element, this.widgetFullName, this ); + this._on( true, this.element, { + remove: function( event ) { + if ( event.target === element ) { + this.destroy(); + } + } + } ); + this.document = $( element.style ? + + // Element within the document + element.ownerDocument : + + // Element is window or document + element.document || element ); + this.window = $( this.document[ 0 ].defaultView || this.document[ 0 ].parentWindow ); + } + + this.options = $.widget.extend( {}, + this.options, + this._getCreateOptions(), + options ); + + this._create(); + + if ( this.options.disabled ) { + this._setOptionDisabled( this.options.disabled ); + } + + this._trigger( "create", null, this._getCreateEventData() ); + this._init(); + }, + + _getCreateOptions: function() { + return {}; + }, + + _getCreateEventData: $.noop, + + _create: $.noop, + + _init: $.noop, + + destroy: function() { + var that = this; + + this._destroy(); + $.each( this.classesElementLookup, function( key, value ) { + that._removeClass( value, key ); + } ); + + // We can probably remove the unbind calls in 2.0 + // all event bindings should go through this._on() + this.element + .off( this.eventNamespace ) + .removeData( this.widgetFullName ); + this.widget() + .off( this.eventNamespace ) + .removeAttr( "aria-disabled" ); + + // Clean up events and states + this.bindings.off( this.eventNamespace ); + }, + + _destroy: $.noop, + + widget: function() { + return this.element; + }, + + option: function( key, value ) { + var options = key; + var parts; + var curOption; + var i; + + if ( arguments.length === 0 ) { + + // Don't return a reference to the internal hash + return $.widget.extend( {}, this.options ); + } + + if ( typeof key === "string" ) { + + // Handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } + options = {}; + parts = key.split( "." ); + key = parts.shift(); + if ( parts.length ) { + curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); + for ( i = 0; i < parts.length - 1; i++ ) { + curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; + curOption = curOption[ parts[ i ] ]; + } + key = parts.pop(); + if ( arguments.length === 1 ) { + return curOption[ key ] === undefined ? null : curOption[ key ]; + } + curOption[ key ] = value; + } else { + if ( arguments.length === 1 ) { + return this.options[ key ] === undefined ? null : this.options[ key ]; + } + options[ key ] = value; + } + } + + this._setOptions( options ); + + return this; + }, + + _setOptions: function( options ) { + var key; + + for ( key in options ) { + this._setOption( key, options[ key ] ); + } + + return this; + }, + + _setOption: function( key, value ) { + if ( key === "classes" ) { + this._setOptionClasses( value ); + } + + this.options[ key ] = value; + + if ( key === "disabled" ) { + this._setOptionDisabled( value ); + } + + return this; + }, + + _setOptionClasses: function( value ) { + var classKey, elements, currentElements; + + for ( classKey in value ) { + currentElements = this.classesElementLookup[ classKey ]; + if ( value[ classKey ] === this.options.classes[ classKey ] || + !currentElements || + !currentElements.length ) { + continue; + } + + // We are doing this to create a new jQuery object because the _removeClass() call + // on the next line is going to destroy the reference to the current elements being + // tracked. We need to save a copy of this collection so that we can add the new classes + // below. + elements = $( currentElements.get() ); + this._removeClass( currentElements, classKey ); + + // We don't use _addClass() here, because that uses this.options.classes + // for generating the string of classes. We want to use the value passed in from + // _setOption(), this is the new value of the classes option which was passed to + // _setOption(). We pass this value directly to _classes(). + elements.addClass( this._classes( { + element: elements, + keys: classKey, + classes: value, + add: true + } ) ); + } + }, + + _setOptionDisabled: function( value ) { + this._toggleClass( this.widget(), this.widgetFullName + "-disabled", null, !!value ); + + // If the widget is becoming disabled, then nothing is interactive + if ( value ) { + this._removeClass( this.hoverable, null, "ui-state-hover" ); + this._removeClass( this.focusable, null, "ui-state-focus" ); + } + }, + + enable: function() { + return this._setOptions( { disabled: false } ); + }, + + disable: function() { + return this._setOptions( { disabled: true } ); + }, + + _classes: function( options ) { + var full = []; + var that = this; + + options = $.extend( { + element: this.element, + classes: this.options.classes || {} + }, options ); + + function processClassString( classes, checkOption ) { + var current, i; + for ( i = 0; i < classes.length; i++ ) { + current = that.classesElementLookup[ classes[ i ] ] || $(); + if ( options.add ) { + current = $( $.unique( current.get().concat( options.element.get() ) ) ); + } else { + current = $( current.not( options.element ).get() ); + } + that.classesElementLookup[ classes[ i ] ] = current; + full.push( classes[ i ] ); + if ( checkOption && options.classes[ classes[ i ] ] ) { + full.push( options.classes[ classes[ i ] ] ); + } + } + } + + this._on( options.element, { + "remove": "_untrackClassesElement" + } ); + + if ( options.keys ) { + processClassString( options.keys.match( /\S+/g ) || [], true ); + } + if ( options.extra ) { + processClassString( options.extra.match( /\S+/g ) || [] ); + } + + return full.join( " " ); + }, + + _untrackClassesElement: function( event ) { + var that = this; + $.each( that.classesElementLookup, function( key, value ) { + if ( $.inArray( event.target, value ) !== -1 ) { + that.classesElementLookup[ key ] = $( value.not( event.target ).get() ); + } + } ); + }, + + _removeClass: function( element, keys, extra ) { + return this._toggleClass( element, keys, extra, false ); + }, + + _addClass: function( element, keys, extra ) { + return this._toggleClass( element, keys, extra, true ); + }, + + _toggleClass: function( element, keys, extra, add ) { + add = ( typeof add === "boolean" ) ? add : extra; + var shift = ( typeof element === "string" || element === null ), + options = { + extra: shift ? keys : extra, + keys: shift ? element : keys, + element: shift ? this.element : element, + add: add + }; + options.element.toggleClass( this._classes( options ), add ); + return this; + }, + + _on: function( suppressDisabledCheck, element, handlers ) { + var delegateElement; + var instance = this; + + // No suppressDisabledCheck flag, shuffle arguments + if ( typeof suppressDisabledCheck !== "boolean" ) { + handlers = element; + element = suppressDisabledCheck; + suppressDisabledCheck = false; + } + + // No element argument, shuffle and use this.element + if ( !handlers ) { + handlers = element; + element = this.element; + delegateElement = this.widget(); + } else { + element = delegateElement = $( element ); + this.bindings = this.bindings.add( element ); + } + + $.each( handlers, function( event, handler ) { + function handlerProxy() { + + // Allow widgets to customize the disabled handling + // - disabled as an array instead of boolean + // - disabled class as method for disabling individual parts + if ( !suppressDisabledCheck && + ( instance.options.disabled === true || + $( this ).hasClass( "ui-state-disabled" ) ) ) { + return; + } + return ( typeof handler === "string" ? instance[ handler ] : handler ) + .apply( instance, arguments ); + } + + // Copy the guid so direct unbinding works + if ( typeof handler !== "string" ) { + handlerProxy.guid = handler.guid = + handler.guid || handlerProxy.guid || $.guid++; + } + + var match = event.match( /^([\w:-]*)\s*(.*)$/ ); + var eventName = match[ 1 ] + instance.eventNamespace; + var selector = match[ 2 ]; + + if ( selector ) { + delegateElement.on( eventName, selector, handlerProxy ); + } else { + element.on( eventName, handlerProxy ); + } + } ); + }, + + _off: function( element, eventName ) { + eventName = ( eventName || "" ).split( " " ).join( this.eventNamespace + " " ) + + this.eventNamespace; + element.off( eventName ).off( eventName ); + + // Clear the stack to avoid memory leaks (#10056) + this.bindings = $( this.bindings.not( element ).get() ); + this.focusable = $( this.focusable.not( element ).get() ); + this.hoverable = $( this.hoverable.not( element ).get() ); + }, + + _delay: function( handler, delay ) { + function handlerProxy() { + return ( typeof handler === "string" ? instance[ handler ] : handler ) + .apply( instance, arguments ); + } + var instance = this; + return setTimeout( handlerProxy, delay || 0 ); + }, + + _hoverable: function( element ) { + this.hoverable = this.hoverable.add( element ); + this._on( element, { + mouseenter: function( event ) { + this._addClass( $( event.currentTarget ), null, "ui-state-hover" ); + }, + mouseleave: function( event ) { + this._removeClass( $( event.currentTarget ), null, "ui-state-hover" ); + } + } ); + }, + + _focusable: function( element ) { + this.focusable = this.focusable.add( element ); + this._on( element, { + focusin: function( event ) { + this._addClass( $( event.currentTarget ), null, "ui-state-focus" ); + }, + focusout: function( event ) { + this._removeClass( $( event.currentTarget ), null, "ui-state-focus" ); + } + } ); + }, + + _trigger: function( type, event, data ) { + var prop, orig; + var callback = this.options[ type ]; + + data = data || {}; + event = $.Event( event ); + event.type = ( type === this.widgetEventPrefix ? + type : + this.widgetEventPrefix + type ).toLowerCase(); + + // The original event may come from any element + // so we need to reset the target on the new event + event.target = this.element[ 0 ]; + + // Copy original event properties over to the new event + orig = event.originalEvent; + if ( orig ) { + for ( prop in orig ) { + if ( !( prop in event ) ) { + event[ prop ] = orig[ prop ]; + } + } + } + + this.element.trigger( event, data ); + return !( $.isFunction( callback ) && + callback.apply( this.element[ 0 ], [ event ].concat( data ) ) === false || + event.isDefaultPrevented() ); + } +}; + +$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { + $.Widget.prototype[ "_" + method ] = function( element, options, callback ) { + if ( typeof options === "string" ) { + options = { effect: options }; + } + + var hasOptions; + var effectName = !options ? + method : + options === true || typeof options === "number" ? + defaultEffect : + options.effect || defaultEffect; + + options = options || {}; + if ( typeof options === "number" ) { + options = { duration: options }; + } + + hasOptions = !$.isEmptyObject( options ); + options.complete = callback; + + if ( options.delay ) { + element.delay( options.delay ); + } + + if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) { + element[ method ]( options ); + } else if ( effectName !== method && element[ effectName ] ) { + element[ effectName ]( options.duration, options.easing, callback ); + } else { + element.queue( function( next ) { + $( this )[ method ](); + if ( callback ) { + callback.call( element[ 0 ] ); + } + next(); + } ); + } + }; +} ); + +var widget = $.widget; + + +/*! + * jQuery UI :data 1.12.1 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + */ + +//>>label: :data Selector +//>>group: Core +//>>description: Selects elements which have data stored under the specified key. +//>>docs: http://api.jqueryui.com/data-selector/ + + +var data = $.extend( $.expr[ ":" ], { + data: $.expr.createPseudo ? + $.expr.createPseudo( function( dataName ) { + return function( elem ) { + return !!$.data( elem, dataName ); + }; + } ) : + + // Support: jQuery <1.8 + function( elem, i, match ) { + return !!$.data( elem, match[ 3 ] ); + } +} ); + +/*! + * jQuery UI Scroll Parent 1.12.1 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + */ + +//>>label: scrollParent +//>>group: Core +//>>description: Get the closest ancestor element that is scrollable. +//>>docs: http://api.jqueryui.com/scrollParent/ + + + +var scrollParent = $.fn.scrollParent = function( includeHidden ) { + var position = this.css( "position" ), + excludeStaticParent = position === "absolute", + overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/, + scrollParent = this.parents().filter( function() { + var parent = $( this ); + if ( excludeStaticParent && parent.css( "position" ) === "static" ) { + return false; + } + return overflowRegex.test( parent.css( "overflow" ) + parent.css( "overflow-y" ) + + parent.css( "overflow-x" ) ); + } ).eq( 0 ); + + return position === "fixed" || !scrollParent.length ? + $( this[ 0 ].ownerDocument || document ) : + scrollParent; +}; + + + + +// This file is deprecated +var ie = $.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() ); + +/*! + * jQuery UI Mouse 1.12.1 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + */ + +//>>label: Mouse +//>>group: Widgets +//>>description: Abstracts mouse-based interactions to assist in creating certain widgets. +//>>docs: http://api.jqueryui.com/mouse/ + + + +var mouseHandled = false; +$( document ).on( "mouseup", function() { + mouseHandled = false; +} ); + +var widgetsMouse = $.widget( "ui.mouse", { + version: "1.12.1", + options: { + cancel: "input, textarea, button, select, option", + distance: 1, + delay: 0 + }, + _mouseInit: function() { + var that = this; + + this.element + .on( "mousedown." + this.widgetName, function( event ) { + return that._mouseDown( event ); + } ) + .on( "click." + this.widgetName, function( event ) { + if ( true === $.data( event.target, that.widgetName + ".preventClickEvent" ) ) { + $.removeData( event.target, that.widgetName + ".preventClickEvent" ); + event.stopImmediatePropagation(); + return false; + } + } ); + + this.started = false; + }, + + // TODO: make sure destroying one instance of mouse doesn't mess with + // other instances of mouse + _mouseDestroy: function() { + this.element.off( "." + this.widgetName ); + if ( this._mouseMoveDelegate ) { + this.document + .off( "mousemove." + this.widgetName, this._mouseMoveDelegate ) + .off( "mouseup." + this.widgetName, this._mouseUpDelegate ); + } + }, + + _mouseDown: function( event ) { + + // don't let more than one widget handle mouseStart + if ( mouseHandled ) { + return; + } + + this._mouseMoved = false; + + // We may have missed mouseup (out of window) + ( this._mouseStarted && this._mouseUp( event ) ); + + this._mouseDownEvent = event; + + var that = this, + btnIsLeft = ( event.which === 1 ), + + // event.target.nodeName works around a bug in IE 8 with + // disabled inputs (#7620) + elIsCancel = ( typeof this.options.cancel === "string" && event.target.nodeName ? + $( event.target ).closest( this.options.cancel ).length : false ); + if ( !btnIsLeft || elIsCancel || !this._mouseCapture( event ) ) { + return true; + } + + this.mouseDelayMet = !this.options.delay; + if ( !this.mouseDelayMet ) { + this._mouseDelayTimer = setTimeout( function() { + that.mouseDelayMet = true; + }, this.options.delay ); + } + + if ( this._mouseDistanceMet( event ) && this._mouseDelayMet( event ) ) { + this._mouseStarted = ( this._mouseStart( event ) !== false ); + if ( !this._mouseStarted ) { + event.preventDefault(); + return true; + } + } + + // Click event may never have fired (Gecko & Opera) + if ( true === $.data( event.target, this.widgetName + ".preventClickEvent" ) ) { + $.removeData( event.target, this.widgetName + ".preventClickEvent" ); + } + + // These delegates are required to keep context + this._mouseMoveDelegate = function( event ) { + return that._mouseMove( event ); + }; + this._mouseUpDelegate = function( event ) { + return that._mouseUp( event ); + }; + + this.document + .on( "mousemove." + this.widgetName, this._mouseMoveDelegate ) + .on( "mouseup." + this.widgetName, this._mouseUpDelegate ); + + event.preventDefault(); + + mouseHandled = true; + return true; + }, + + _mouseMove: function( event ) { + + // Only check for mouseups outside the document if you've moved inside the document + // at least once. This prevents the firing of mouseup in the case of IE<9, which will + // fire a mousemove event if content is placed under the cursor. See #7778 + // Support: IE <9 + if ( this._mouseMoved ) { + + // IE mouseup check - mouseup happened when mouse was out of window + if ( $.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && + !event.button ) { + return this._mouseUp( event ); + + // Iframe mouseup check - mouseup occurred in another document + } else if ( !event.which ) { + + // Support: Safari <=8 - 9 + // Safari sets which to 0 if you press any of the following keys + // during a drag (#14461) + if ( event.originalEvent.altKey || event.originalEvent.ctrlKey || + event.originalEvent.metaKey || event.originalEvent.shiftKey ) { + this.ignoreMissingWhich = true; + } else if ( !this.ignoreMissingWhich ) { + return this._mouseUp( event ); + } + } + } + + if ( event.which || event.button ) { + this._mouseMoved = true; + } + + if ( this._mouseStarted ) { + this._mouseDrag( event ); + return event.preventDefault(); + } + + if ( this._mouseDistanceMet( event ) && this._mouseDelayMet( event ) ) { + this._mouseStarted = + ( this._mouseStart( this._mouseDownEvent, event ) !== false ); + ( this._mouseStarted ? this._mouseDrag( event ) : this._mouseUp( event ) ); + } + + return !this._mouseStarted; + }, + + _mouseUp: function( event ) { + this.document + .off( "mousemove." + this.widgetName, this._mouseMoveDelegate ) + .off( "mouseup." + this.widgetName, this._mouseUpDelegate ); + + if ( this._mouseStarted ) { + this._mouseStarted = false; + + if ( event.target === this._mouseDownEvent.target ) { + $.data( event.target, this.widgetName + ".preventClickEvent", true ); + } + + this._mouseStop( event ); + } + + if ( this._mouseDelayTimer ) { + clearTimeout( this._mouseDelayTimer ); + delete this._mouseDelayTimer; + } + + this.ignoreMissingWhich = false; + mouseHandled = false; + event.preventDefault(); + }, + + _mouseDistanceMet: function( event ) { + return ( Math.max( + Math.abs( this._mouseDownEvent.pageX - event.pageX ), + Math.abs( this._mouseDownEvent.pageY - event.pageY ) + ) >= this.options.distance + ); + }, + + _mouseDelayMet: function( /* event */ ) { + return this.mouseDelayMet; + }, + + // These are placeholder methods, to be overriden by extending plugin + _mouseStart: function( /* event */ ) {}, + _mouseDrag: function( /* event */ ) {}, + _mouseStop: function( /* event */ ) {}, + _mouseCapture: function( /* event */ ) { return true; } +} ); + + + + +// $.ui.plugin is deprecated. Use $.widget() extensions instead. +var plugin = $.ui.plugin = { + add: function( module, option, set ) { + var i, + proto = $.ui[ module ].prototype; + for ( i in set ) { + proto.plugins[ i ] = proto.plugins[ i ] || []; + proto.plugins[ i ].push( [ option, set[ i ] ] ); + } + }, + call: function( instance, name, args, allowDisconnected ) { + var i, + set = instance.plugins[ name ]; + + if ( !set ) { + return; + } + + if ( !allowDisconnected && ( !instance.element[ 0 ].parentNode || + instance.element[ 0 ].parentNode.nodeType === 11 ) ) { + return; + } + + for ( i = 0; i < set.length; i++ ) { + if ( instance.options[ set[ i ][ 0 ] ] ) { + set[ i ][ 1 ].apply( instance.element, args ); + } + } + } +}; + + + +var safeActiveElement = $.ui.safeActiveElement = function( document ) { + var activeElement; + + // Support: IE 9 only + // IE9 throws an "Unspecified error" accessing document.activeElement from an