PK!tp!!models/model-table.phpnu[} { * @type int $last_id Last table ID that was given to a new table. * @type array $table_post Connections between table ID and post ID (key: table ID, value: post ID). * } */ protected array $default_tables = array( 'last_id' => 0, 'table_post' => array(), ); /** * Instance of WP_Option class for the list of tables. * * @since 1.0.0 */ protected \TablePress_WP_Option $tables; /** * Init the Table model by instantiating a Post model and loading the list of tables option. * * @since 1.0.0 */ public function __construct() { parent::__construct(); $this->model_post = TablePress::load_model( 'post' ); $params = array( 'option_name' => 'tablepress_tables', 'default_value' => $this->default_tables, ); $this->tables = TablePress::load_class( 'TablePress_WP_Option', 'class-wp_option.php', 'classes', $params ); } /** * Get the tables option, which holds the connection between table ID and post ID. * * @since 1.0.0 * * @return mixed[] Current set of tables. */ public function _debug_get_tables(): array { return $this->tables->get(); } /** * Update the tables option, which holds the connection between table ID and post ID. * * @since 1.0.0 * * @param mixed[] $tables New set of tables. */ public function _debug_update_tables( array $tables ): void { $this->tables->update( $tables ); } /** * Convert a table to a post, which can be stored in the database. * * @since 1.0.0 * * @param array $table Table. * @param int $post_id Post ID of an existing table, or -1 for a new table. * @return array Post. */ protected function _table_to_post( array $table, int $post_id ): array { // Sanitize each cell, table name, and table description, if the user is not allowed to work with unfiltered HTML. if ( ! current_user_can( 'unfiltered_html' ) ) { $table = $this->sanitize( $table ); } // New posts have a post ID of false in WordPress. if ( -1 === $post_id ) { $post_id = false; } $post = array( 'ID' => $post_id, 'post_title' => $table['name'], 'post_excerpt' => $table['description'], 'post_content' => wp_json_encode( $table['data'], TABLEPRESS_JSON_OPTIONS ), 'post_mime_type' => 'application/json', ); return $post; } /** * Convert a post (from the database) to a table. * * @since 1.0.0 * * @param WP_Post $post Post. * @param string $table_id Table ID. * @param bool $load_data Whether the table data shall be loaded. * @return array Table. */ protected function _post_to_table( WP_Post $post, string $table_id, bool $load_data ): array { $table = array( 'id' => $table_id, 'name' => $post->post_title, 'description' => $post->post_excerpt, 'author' => $post->post_author, // 'created' => $post->post_date, 'last_modified' => $post->post_modified, ); if ( ! $load_data ) { return $table; } $table['data'] = json_decode( $post->post_content, true ); // Check if JSON could be decoded. if ( is_null( $table['data'] ) ) { $table['data'] = array( array( "The internal data of table {$table_id} is corrupted." ) ); // Set a single cell as the cell content. $table['is_corrupted'] = true; $table['json_error'] = json_last_error_msg(); $table['description'] = "[ERROR] TABLE IS CORRUPTED (JSON error: {$table['json_error']})! DO NOT EDIT THIS TABLE NOW!\nInstead, please see https://tablepress.org/faq/corrupted-tables/ for instructions.\n-\n{$table['description']}"; } else { // Specifically cast to an array again. $table['data'] = (array) $table['data']; } return $table; } /** * Load a table. * * @since 1.0.0 * * @param string $table_id Table ID. * @param bool $load_data Whether the table data shall be loaded. * @param bool $load_options_visibility Whether the table options and table visibility shall be loaded. * @return array|WP_Error Table as an array on success, WP_Error on error. */ public function load( string $table_id, bool $load_data = true, bool $load_options_visibility = true ) /* : array|WP_Error */ { if ( empty( $table_id ) ) { return new WP_Error( 'table_load_empty_table_id' ); } $post_id = $this->_get_post_id( $table_id ); if ( false === $post_id ) { return new WP_Error( 'table_load_no_post_id_for_table_id', '', $table_id ); } $post = $this->model_post->get( $post_id ); if ( false === $post ) { return new WP_Error( 'table_load_no_post_for_post_id', '', $post_id ); } $table = $this->_post_to_table( $post, $table_id, $load_data ); if ( $load_options_visibility ) { $table['options'] = $this->_get_table_options( $post_id ); $table['visibility'] = $this->_get_table_visibility( $post_id ); } return $table; } /** * Load the IDs of all tables that can be loaded from the database. * * @since 1.0.0 * * @param bool $prime_meta_cache Optional. Whether the prime the post meta cache when loading the posts. * @param bool $run_filter Optional. Whether to run a filter on the list of table IDs. * @return string[] Array of table IDs. */ public function load_all( bool $prime_meta_cache = true, bool $run_filter = true ): array { $table_post = $this->tables->get( 'table_post' ); if ( empty( $table_post ) ) { return array(); } // Load all table posts with one query, to prime the cache. $this->model_post->load_posts( array_values( $table_post ), $prime_meta_cache ); // This loop now uses the WP cache. $table_ids = array(); foreach ( $table_post as $table_id => $post_id ) { $table_id = (string) $table_id; // Ensure that the table ID is a string, as it comes from an array key where numeric strings are converted to integers. // Load table without data and options to save memory. $table = $this->load( $table_id, false, false ); // Skip tables that could not be loaded properly. if ( ! is_wp_error( $table ) ) { $table_ids[] = $table_id; } } if ( $run_filter ) { /** * Filters all table IDs that are loaded. * * @since 1.4.0 * * @param string[] $table_ids The table IDs that are loaded. */ $table_ids = apply_filters( 'tablepress_load_all_tables', $table_ids ); } return $table_ids; } /** * Sanitize the table to remove undesired HTML code using KSES. * * @since 1.8.0 * * @param array $table Table. * @return array Sanitized table. */ public function sanitize( array $table ): array { // Sanitize the table name and description. $fields = array( 'name', 'description' ); foreach ( $fields as $field ) { $table[ $field ] = wp_kses_post( $table[ $field ] ); } // Sanitize each cell. foreach ( $table['data'] as $row_idx => $row ) { foreach ( $row as $column_idx => $cell_content ) { $table['data'][ $row_idx ][ $column_idx ] = wp_kses_post( $cell_content ); // Equals wp_filter_post_kses(), but without the unnecessary slashes handling. } } return $table; } /** * Save a table. * * @since 1.0.0 * * @param array $table Table (needs to have $table['id']!). * @return string|WP_Error WP_Error on error, string table ID on success. */ public function save( array $table ) /* : string|WP_Error */ { if ( empty( $table['id'] ) ) { return new WP_Error( 'table_save_empty_table_id' ); } /** * Fires before a table is saved. * * @since 3.1.0 * * @param string $table_id ID of the table to be saved. */ do_action( 'tablepress_event_pre_save_table', $table['id'] ); $post_id = $this->_get_post_id( $table['id'] ); if ( false === $post_id ) { return new WP_Error( 'table_save_no_post_id_for_table_id', '', $table['id'] ); } $post = $this->_table_to_post( $table, $post_id ); $new_post_id = $this->model_post->update( $post ); if ( is_wp_error( $new_post_id ) ) { $error = new WP_Error( 'table_save_post_update', '', $post_id ); $error->merge_from( $new_post_id ); return $error; } if ( $post_id !== $new_post_id ) { return new WP_Error( 'table_save_new_post_id_does_not_match', '', $new_post_id ); } $options_saved = $this->_update_table_options( $new_post_id, $table['options'] ); if ( ! $options_saved ) { return new WP_Error( 'table_save_update_table_options_failed', '', $new_post_id ); } $visibility_saved = $this->_update_table_visibility( $new_post_id, $table['visibility'] ); if ( ! $visibility_saved ) { return new WP_Error( 'table_save_update_table_visibility_failed', '', $new_post_id ); } // At this point, post was successfully added. // Invalidate table output caches that belong to this table. $this->invalidate_table_output_cache( $table['id'] ); // Flush caching plugins' caches. $this->_flush_caching_plugins_caches(); /** * Fires after a table has been saved. * * @since 1.5.0 * * @param string $table_id ID of the saved table. */ do_action( 'tablepress_event_saved_table', $table['id'] ); return $table['id']; } /** * Add a new table. * * @since 1.0.0 * * @param array $table Table ($table['id'] is not necessary). * @param string $copy_or_add Optional. 'copy' if the table is copied, 'add' if it is a new table. Default 'add'. * @return string|WP_Error WP_Error on error, string table ID of the new table on success. */ public function add( array $table, string $copy_or_add = 'add' ) /* : string|WP_Error */ { $post_id = -1; // To insert table. $post = $this->_table_to_post( $table, $post_id ); $new_post_id = $this->model_post->insert( $post ); if ( is_wp_error( $new_post_id ) ) { $error = new WP_Error( 'table_add_post_insert', '' ); $error->merge_from( $new_post_id ); return $error; } $options_saved = $this->_add_table_options( $new_post_id, $table['options'] ); if ( ! $options_saved ) { return new WP_Error( 'table_add_update_table_options_failed', '', $new_post_id ); } $visibility_saved = $this->_add_table_visibility( $new_post_id, $table['visibility'] ); if ( ! $visibility_saved ) { return new WP_Error( 'table_add_update_table_visibility_failed', '', $new_post_id ); } // At this point, post was successfully added, now get an unused table ID. $table_id = $this->_get_new_table_id(); $this->_update_post_id( $table_id, $new_post_id ); if ( 'add' === $copy_or_add ) { /** * Fires after a new table has been added. * * @since 1.1.0 * * @param string $table_id ID of the added table. */ do_action( 'tablepress_event_added_table', $table_id ); } return $table_id; } /** * Create a copy of a table and add it. * * @since 1.0.0 * * @param string $table_id ID of the table to be copied. * @return string|WP_Error WP_Error on error, string table ID of the new table on success. */ public function copy( string $table_id ) /* : string|WP_Error */ { $table = $this->load( $table_id, true, true ); if ( is_wp_error( $table ) ) { $error = new WP_Error( 'table_copy_table_load', '', $table_id ); $error->merge_from( $table ); return $error; } // Adjust name of copied table. if ( '' === trim( $table['name'] ) ) { $table['name'] = __( '(no name)', 'tablepress' ); } $table['name'] = sprintf( __( 'Copy of %s', 'tablepress' ), $table['name'] ); // Merge this data into an empty table template. $table = $this->prepare_table( $this->get_table_template(), $table, false ); if ( is_wp_error( $table ) ) { $error = new WP_Error( 'table_copy_table_prepare', '', $table_id ); $error->merge_from( $table ); return $error; } // Add the copied table. $new_table_id = $this->add( $table, 'copy' ); if ( is_wp_error( $new_table_id ) ) { $error = new WP_Error( 'table_copy_table_add', '', $table_id ); $error->merge_from( $new_table_id ); return $error; } /** * Fires after an existing table has been copied. * * @since 1.1.0 * * @param string $new_table_id ID of the copy of the table. * @param string $table_id ID of the existing table that is copied. */ do_action( 'tablepress_event_copied_table', $new_table_id, $table_id ); return $new_table_id; } /** * Delete a table (and its options). * * @since 1.0.0 * * @param string $table_id ID of the table to be deleted. * @return bool|WP_Error WP_Error on error, true on success. */ public function delete( string $table_id ) /* : true|WP_Error */ { if ( ! $this->table_exists( $table_id ) ) { return new WP_Error( 'table_delete_table_does_not_exist', '', $table_id ); } /** * Fires before a table is deleted. * * @since 3.1.0 * * @param string $table_id ID of the table to be deleted. */ do_action( 'tablepress_event_pre_delete_table', $table_id ); $post_id = $this->_get_post_id( $table_id ); // No ! false check necessary, as this is covered by table_exists() check above. // @phpstan-ignore argument.type $deleted = $this->model_post->delete( $post_id ); // Post Meta fields will be deleted automatically by that function. if ( false === $deleted ) { return new WP_Error( 'table_delete_post_could_not_be_deleted', '', $post_id ); } // If post was deleted successfully, remove the table ID from the list of tables. $this->_remove_post_id( $table_id ); // Invalidate table output caches that belong to this table. $this->invalidate_table_output_cache( $table_id ); // Flush caching plugins' caches. $this->_flush_caching_plugins_caches(); /** * Fires after a table has been deleted. * * @since 1.1.0 * * @param string $table_id ID of the deleted table. */ do_action( 'tablepress_event_deleted_table', $table_id ); return true; } /** * Delete all tables. * * @since 1.0.0 */ public function delete_all(): void { $tables = $this->tables->get(); if ( empty( $tables['table_post'] ) ) { return; } foreach ( $tables['table_post'] as $table_id => $post_id ) { $table_id = (string) $table_id; // Ensure that the table ID is a string, as it comes from an array key where numeric strings are converted to integers. $this->model_post->delete( $post_id ); // Post Meta fields will be deleted automatically by that function. unset( $tables['table_post'][ $table_id ] ); // Invalidate table output caches that belong to this table. $this->invalidate_table_output_cache( $table_id ); } $this->tables->update( $tables ); // Flush caching plugins' caches. $this->_flush_caching_plugins_caches(); /** * Fires after all tables have been deleted. * * @since 1.1.0 */ do_action( 'tablepress_event_deleted_all_tables' ); } /** * Check if a table ID exists in the list of tables (this does not guarantee that the post with the table data exists!). * * @since 1.0.0 * * @param string $table_id Table ID. * @return bool Whether the table ID exists. */ public function table_exists( string $table_id ): bool { $table_post = $this->tables->get( 'table_post' ); return isset( $table_post[ $table_id ] ); } /** * Count the number of tables from either just the list, or by also counting the posts in the database. * * @since 1.0.0 * * @param bool $single_value Optional. Whether to return just the number of tables from the list, or also count in the database. * @return int|array{list: int, db: int} Number of Tables (if $single_value), or array of Numbers from list/DB (if ! $single_value). */ public function count_tables( bool $single_value = true ) /* : int|array */ { $count_list = count( $this->tables->get( 'table_post' ) ); if ( $single_value ) { return $count_list; } $count_db = $this->model_post->count_posts(); return array( 'list' => $count_list, 'db' => $count_db, ); } /** * Delete all transients used for output caching of a table (e.g. when the table is updated or deleted). * * @since 1.0.0 * @since 1.8.0 Renamed from _invalidate_table_output_cache to invalidate_table_output_cache and made public. * * @param string $table_id Table ID. */ public function invalidate_table_output_cache( string $table_id ): void { $caches_list_transient_name = 'tablepress_c_' . md5( $table_id ); $caches_list = get_transient( $caches_list_transient_name ); if ( false !== $caches_list ) { $caches_list = (array) json_decode( $caches_list, true ); foreach ( $caches_list as $cache_transient_name ) { delete_transient( $cache_transient_name ); } } delete_transient( $caches_list_transient_name ); } /** * Flush the caches of common caching plugins. * * @since 1.0.0 */ public function _flush_caching_plugins_caches(): void { /** * Filters whether the caches of common caching plugins shall be flushed. * * @since 1.0.0 * * @param bool $flush Whether caches of caching plugins shall be flushed. Default true. */ if ( ! apply_filters( 'tablepress_flush_caching_plugins_caches', true ) ) { return; } // Common cache flush callback. $cache_flush_callbacks = array( array( 'Breeze_PurgeCache', 'breeze_cache_flush' ), // Breeze. array( 'comet_cache', 'clear' ), // Comet Cache. 'pantheon_wp_clear_edge_all', // Pantheon. 'sg_cachepress_purge_cache', // SG Optimizer. array( 'Swift_Performance_Cache', 'clear_all_cache' ), // Swift Performance. 'w3tc_pgcache_flush', // W3 Total Cache. array( 'WpeCommon', 'purge_memcached' ), // WP Engine. array( 'WpeCommon', 'clear_maxcdn_cache' ), // WP Engine. array( 'WpeCommon', 'purge_varnish_cache' ), // WP Engine. 'wpfc_clear_all_cache', // WP Fastest Cache. 'rocket_clean_domain', // WP Rocket. 'wp_cache_clear_cache', // WP Super Cache. array( 'zencache', 'clear' ), // Zen Cache. ); foreach ( $cache_flush_callbacks as $cache_flush_callback ) { if ( is_callable( $cache_flush_callback ) ) { call_user_func( $cache_flush_callback ); } } // Common cache flush hooks. $cache_flush_hooks = array( 'ce_clear_cache', // Cache Enabler. 'cachify_flush_cache', // Cachify. 'autoptimize_action_cachepurged', // Hyper Cache. ); foreach ( $cache_flush_hooks as $cache_flush_hook ) { do_action( $cache_flush_hook ); } // Kinsta. if ( isset( $GLOBALS['kinsta_cache'] ) && ! empty( $GLOBALS['kinsta_cache']->kinsta_cache_purge ) && is_callable( array( $GLOBALS['kinsta_cache']->kinsta_cache_purge, 'purge_complete_caches' ) ) ) { $GLOBALS['kinsta_cache']->kinsta_cache_purge->purge_complete_caches(); // @phpstan-ignore method.nonObject } // LiteSpeed Cache. if ( is_callable( array( 'LiteSpeed_Cache_Tags', 'add_purge_tag' ) ) ) { LiteSpeed_Cache_Tags::add_purge_tag( '*' ); // @phpstan-ignore class.notFound } // Pagely. if ( class_exists( 'PagelyCachePurge' ) ) { $_pagely = new PagelyCachePurge(); if ( is_callable( array( $_pagely, 'purgeAll' ) ) ) { $_pagely->purgeAll(); } } // Pressidum. if ( is_callable( array( 'Ninukis_Plugin', 'get_instance' ) ) ) { $_pressidum = Ninukis_Plugin::get_instance(); // @phpstan-ignore class.notFound if ( is_callable( array( $_pressidum, 'purgeAllCaches' ) ) ) { $_pressidum->purgeAllCaches(); // @phpstan-ignore method.nonObject } } // Savvii. if ( defined( '\Savvii\CacheFlusherPlugin::NAME_DOMAINFLUSH_NOW' ) ) { $_savvii = new \Savvii\CacheFlusherPlugin(); // @phpstan-ignore class.notFound if ( is_callable( array( $_savvii, 'domainflush' ) ) ) { $_savvii->domainflush(); // @phpstan-ignore class.notFound } } // WP Fastest Cache. if ( isset( $GLOBALS['wp_fastest_cache'] ) && is_callable( array( $GLOBALS['wp_fastest_cache'], 'deleteCache' ) ) ) { $GLOBALS['wp_fastest_cache']->deleteCache( true ); // @phpstan-ignore method.nonObject } // WP-Optimize. if ( function_exists( 'WP_Optimize' ) ) { WP_Optimize()->get_page_cache()->purge(); } } /** * Get the post ID of a given table ID (if the table ID exists). * * @since 1.0.0 * * @param string $table_id Table ID. * @return int|false Post ID on success, false on error. */ protected function _get_post_id( string $table_id ) /* : int|false */ { $table_post = $this->tables->get( 'table_post' ); if ( ! isset( $table_post[ $table_id ] ) ) { return false; } return $table_post[ $table_id ]; } /** * Update/Add a post ID for a given table ID, and sort the list of tables by their key in natural sort order. * * @since 1.0.0 * * @param string $table_id Table ID. * @param int $post_id Post ID. */ protected function _update_post_id( string $table_id, int $post_id ): void { $tables = $this->tables->get(); $tables['table_post'][ $table_id ] = $post_id; uksort( $tables['table_post'], 'strnatcasecmp' ); $this->tables->update( $tables ); } /** * Remove a table ID/post ID connection from the list of tables. * * @since 1.0.0 * * @param string $table_id Table ID. */ protected function _remove_post_id( string $table_id ): void { $tables = $this->tables->get(); unset( $tables['table_post'][ $table_id ] ); $this->tables->update( $tables ); } /** * Change the table ID of a table. * * @since 1.0.0 * * @param string $old_id Old table ID. * @param string $new_id New table ID. * @return bool|WP_Error True on success, WP_Error on error. */ public function change_table_id( string $old_id, string $new_id ) /* : true|WP_Error */ { $post_id = $this->_get_post_id( $old_id ); if ( false === $post_id ) { return new WP_Error( 'table_change_id_no_post_id_for_table_id', '', $old_id ); } // Check new ID for correct format (string from letters, numbers, -, and _ only, except the '0' string). if ( empty( $new_id ) || 0 !== preg_match( '/[^a-zA-Z0-9_-]/', $new_id ) ) { return new WP_Error( 'table_change_id_new_id_is_invalid', '', $new_id ); } if ( $this->table_exists( $new_id ) ) { return new WP_Error( 'table_change_table_new_id_exists', '', $new_id ); } $this->_update_post_id( $new_id, $post_id ); $this->_remove_post_id( $old_id ); /** * Fires after the ID of a table has been changed. * * @since 1.1.0 * * @param string $new_id New ID of the table. * @param string $old_id Old ID of the table. */ do_action( 'tablepress_event_changed_table_id', $new_id, $old_id ); return true; } /** * Get an unused table ID (e.g. for a new table). * * @since 1.0.0 * * @return string Unused table ID (e.g. for a new table). */ protected function _get_new_table_id(): string { $tables = $this->tables->get(); // Need to check new ID candidate in a loop, because a higher ID might already be in use, if a table ID was changed manually. do { ++$tables['last_id']; $last_id_string = (string) $tables['last_id']; } while ( $this->table_exists( $last_id_string ) ); $this->tables->update( $tables ); return $last_id_string; } /** * Get the template for an empty table. * * Important: This scheme is versioned via TablePress::table_scheme_version; changes likely need a version update! * * @since 1.0.0 * * @return array Empty table. */ public function get_table_template(): array { // Attention: Array keys have to be lowercase, to make it possible to match them with Shortcode attributes! $table = array( 'id' => false, 'name' => '', 'description' => '', 'data' => array( array( '' ) ), // One empty cell. 'last_modified' => wp_date( 'Y-m-d H:i:s' ), 'author' => get_current_user_id(), 'options' => array( 'last_editor' => get_current_user_id(), 'table_head' => 1, 'table_foot' => 0, 'alternating_row_colors' => true, 'row_hover' => true, 'print_name' => false, 'print_name_position' => 'above', 'print_description' => false, 'print_description_position' => 'below', 'extra_css_classes' => '', // DataTables JavaScript library. 'use_datatables' => true, 'datatables_sort' => true, 'datatables_filter' => true, 'datatables_paginate' => true, 'datatables_lengthchange' => true, 'datatables_paginate_entries' => 10, 'datatables_info' => true, 'datatables_scrollx' => false, 'datatables_custom_commands' => '', ), 'visibility' => array( 'rows' => array( 1 ), // One visbile row. 'columns' => array( 1 ), // One visible column. ), ); /** * Filters the default template/structure of an empty table. * * @since 1.0.0 * * @param array $table Default template/structure of an empty table. */ return apply_filters( 'tablepress_table_template', $table ); } /** * Combine two tables (e.g. an existing one with the updated data, or an empty one with new data). * * Performs consistency checks on data and visibility settings. * * @since 1.0.0 * * @param array $table Table to merge into. * @param array $new_table Table to merge. * @param bool $table_size_check Optional. Whether to check the number of rows and columns (e.g. not necessary for added or copied tables). * @return array|WP_Error Merged table on success, WP_Error on error. */ public function prepare_table( array $table, array $new_table, bool $table_size_check = true ) /* : array|WP_Error */ { // Table ID must be the same (if there was an ID already). if ( false !== $table['id'] && $table['id'] !== $new_table['id'] ) { return new WP_Error( 'table_prepare_no_id_match', '', $new_table['id'] ); } // Name, description, and data array need to exist, data must not be empty, the others could be ''. if ( ! isset( $new_table['name'], $new_table['description'] ) || empty( $new_table['data'] ) || empty( $new_table['data'][0] ) ) { return new WP_Error( 'table_prepare_name_description_or_data_not_set' ); } // Visibility needs to exist. if ( ! isset( $new_table['visibility']['rows'], $new_table['visibility']['columns'] ) ) { return new WP_Error( 'table_prepare_visibility_not_set' ); } $new_table['visibility']['rows'] = array_map( 'intval', $new_table['visibility']['rows'] ); $new_table['visibility']['columns'] = array_map( 'intval', $new_table['visibility']['columns'] ); // Check dimensions of table data array (not done for newly added, copied, or imported tables). if ( $table_size_check ) { if ( ! isset( $new_table['number']['rows'], $new_table['number']['columns'] ) ) { return new WP_Error( 'table_prepare_size_check_numbers_not_set' ); } // Table data needs to be ok, and have the correct number of rows and columns. $new_table['number']['rows'] = (int) $new_table['number']['rows']; $new_table['number']['columns'] = (int) $new_table['number']['columns']; if ( 0 === $new_table['number']['rows'] || 0 === $new_table['number']['columns'] || count( $new_table['data'] ) !== $new_table['number']['rows'] || count( $new_table['data'][0] ) !== $new_table['number']['columns'] ) { return new WP_Error( 'table_prepare_size_check_numbers_dont_match' ); } // Visibility also needs to have correct dimensions. if ( count( $new_table['visibility']['rows'] ) !== $new_table['number']['rows'] || count( $new_table['visibility']['columns'] ) !== $new_table['number']['columns'] ) { return new WP_Error( 'table_prepare_size_check_visibility_doesnt_match' ); } } // All checks were successful, replace original values with new ones. // $table['id'] is either false (and remains false) or already equal to $new_table['id']. $table['new_id'] = $new_table['new_id'] ?? $table['id']; $table['name'] = $new_table['name']; $table['description'] = $new_table['description']; $table['data'] = $new_table['data']; // Make sure that cells are stored as strings. array_walk_recursive( $table['data'], static function ( /* string|int|float|bool|null */ &$cell_content, int $col_idx ): void { $cell_content = (string) $cell_content; }, ); // Table Options. if ( isset( $new_table['options'] ) ) { // Options are for example not set for newly added tables. // Specials check for certain options. if ( isset( $new_table['options']['extra_css_classes'] ) ) { $new_table['options']['extra_css_classes'] = explode( ' ', $new_table['options']['extra_css_classes'] ); $new_table['options']['extra_css_classes'] = array_map( array( 'TablePress', 'sanitize_css_class' ), $new_table['options']['extra_css_classes'] ); $new_table['options']['extra_css_classes'] = array_unique( $new_table['options']['extra_css_classes'] ); $new_table['options']['extra_css_classes'] = trim( implode( ' ', $new_table['options']['extra_css_classes'] ) ); } if ( isset( $new_table['options']['datatables_paginate_entries'] ) ) { $new_table['options']['datatables_paginate_entries'] = (int) $new_table['options']['datatables_paginate_entries']; if ( $new_table['options']['datatables_paginate_entries'] < 1 ) { $new_table['options']['datatables_paginate_entries'] = 10; // Default value. } } // Backward compatibility: Convert boolean or numeric string "table_head" and "table_foot" options to integer. if ( isset( $new_table['options']['table_head'] ) ) { $new_table['options']['table_head'] = absint( $new_table['options']['table_head'] ); } if ( isset( $new_table['options']['table_foot'] ) ) { $new_table['options']['table_foot'] = absint( $new_table['options']['table_foot'] ); } // Merge new options. $default_table = $this->get_table_template(); $table['options'] = array_intersect_key( $table['options'], $default_table['options'] ); $new_table['options'] = array_intersect_key( $new_table['options'], $default_table['options'] ); $table['options'] = array_merge( $table['options'], $new_table['options'] ); } // Table Visibility. $table['visibility']['rows'] = $new_table['visibility']['rows']; $table['visibility']['columns'] = $new_table['visibility']['columns']; // $table['author'] = get_current_user_id(); // We don't want this, as it would override the original author. // $table['created'] = wp_date( 'Y-m-d H:i:s' ); // We don't want this, as it would override the original datetime. $table['last_modified'] = wp_date( 'Y-m-d H:i:s' ); $table['options']['last_editor'] = get_current_user_id(); // Prevent issues if the "Custom Commands" field is not set, e.g. when non-admins have previously edited the table. if ( ! isset( $table['options']['datatables_custom_commands'] ) ) { $table['options']['datatables_custom_commands'] = ''; } // Convert CSS classes and some DataTables 1.x parameters to the DataTables 2 variants. if ( '' !== $table['options']['datatables_custom_commands'] ) { $table['options']['datatables_custom_commands'] = TablePress::convert_datatables_api_data( $table['options']['datatables_custom_commands'] ); } return $table; } /** * Save the table options of a table (in a post meta field of the table's post). * * @since 1.0.0 * * @param int $post_id Post ID. * @param array $options Table options. * @return bool True on success, false on error. */ protected function _add_table_options( int $post_id, array $options ): bool { $options = wp_json_encode( $options, TABLEPRESS_JSON_OPTIONS ); return $this->model_post->add_meta_field( $post_id, $this->table_options_field_name, $options ); // @phpstan-ignore argument.type } /** * Update the table options of a table (in a post meta field in the table's post). * * @since 1.0.0 * * @param int $post_id Post ID. * @param array $options Table options. * @return bool True on success, false on error. */ protected function _update_table_options( int $post_id, array $options ): bool { $options = wp_json_encode( $options, TABLEPRESS_JSON_OPTIONS ); return $this->model_post->update_meta_field( $post_id, $this->table_options_field_name, $options ); // @phpstan-ignore argument.type } /** * Get the table options of a table (from a post meta field of the table's post). * * @since 1.0.0 * * @param int $post_id Post ID. * @return array Table options on success, empty array on error. */ protected function _get_table_options( int $post_id ): array { $options = $this->model_post->get_meta_field( $post_id, $this->table_options_field_name ); if ( empty( $options ) ) { return array(); } $options = (array) json_decode( $options, true ); // Backward compatibility: Convert boolean "table_head" and "table_foot" options to integer. $options['table_head'] = absint( $options['table_head'] ); $options['table_foot'] = absint( $options['table_foot'] ); return $options; } /** * Save the table visibility of a table (in a post meta field of the table's post). * * @since 1.0.0 * * @param int $post_id Post ID. * @param array{rows: int[], columns: int[]} $visibility Table visibility. * @return bool True on success, false on error. */ protected function _add_table_visibility( int $post_id, array $visibility ): bool { $visibility = wp_json_encode( $visibility, TABLEPRESS_JSON_OPTIONS ); return $this->model_post->add_meta_field( $post_id, $this->table_visibility_field_name, $visibility ); // @phpstan-ignore argument.type } /** * Update the table visibility of a table (in a post meta field in the table's post). * * @since 1.0.0 * * @param int $post_id Post ID. * @param array{rows: int[], columns: int[]} $visibility Table visibility. * @return bool True on success, false on error. */ protected function _update_table_visibility( int $post_id, array $visibility ): bool { $visibility = wp_json_encode( $visibility, TABLEPRESS_JSON_OPTIONS ); return $this->model_post->update_meta_field( $post_id, $this->table_visibility_field_name, $visibility ); // @phpstan-ignore argument.type } /** * Get the table visibility of a table (from a post meta field of the table's post). * * @since 1.0.0 * * @param int $post_id Post ID. * @return array{rows: int[], columns: int[]} Table visibility on success, empty array on error. */ protected function _get_table_visibility( int $post_id ): array { $visibility = $this->model_post->get_meta_field( $post_id, $this->table_visibility_field_name ); if ( empty( $visibility ) ) { return array( 'rows' => array(), 'columns' => array(), ); } return json_decode( $visibility, true ); } /** * Merge existing Table Options with default Table Options, * remove (no longer) existing options, after a table scheme change, * for all tables. * * @since 1.0.0 * @since 2.0.0 Add optional $remove_old_options parameter. * * @param bool $remove_old_options Optional. Whether old table options should be removed from the database. Default true. */ public function merge_table_options_defaults( bool $remove_old_options = true ): void { $table_post = $this->tables->get( 'table_post' ); if ( empty( $table_post ) ) { return; } // Prime the meta cache with the table options of all tables. update_meta_cache( 'post', array_values( $table_post ) ); // Get default Table with default Table Options. $default_table = $this->get_table_template(); // Go through all tables (this loop now uses the WP cache). foreach ( $table_post as $post_id ) { $table_options = $this->_get_table_options( $post_id ); /** * Filters the Table Options before they are merged with the default Table Options. * * @since 3.0.0 * * @param array $table_options Table Options. * @param array $default_table_options Default Table Options. * @param bool $remove_old_options Whether old table options should be removed from the database. * @param int $post_id Post ID of the table. */ $table_options = apply_filters( 'tablepress_table_options_before_merge', $table_options, $default_table['options'], $remove_old_options, $post_id ); if ( $remove_old_options ) { // Remove old (i.e. no longer existing) Table Options. $table_options = array_intersect_key( $table_options, $default_table['options'] ); } // Merge current into new Table Options. $table_options = array_merge( $default_table['options'], $table_options ); $this->_update_table_options( $post_id, $table_options ); } } /** * Updates all tables' "Custom Commands" to use DataTables 2 variants instead of old DataTables 1.x CSS classes and parameters * * @since 3.0.0 */ public function update_custom_commands_datatables_tp30(): void { $table_post = $this->tables->get( 'table_post' ); if ( empty( $table_post ) ) { return; } // Prime the meta cache with the table options of all tables. update_meta_cache( 'post', array_values( $table_post ) ); foreach ( $table_post as $table_id => $post_id ) { $table_options = $this->_get_table_options( $post_id ); // Fix tables where the "Custom Commands" entry is missing entirely. if ( ! isset( $table_options['datatables_custom_commands'] ) ) { $table_options['datatables_custom_commands'] = ''; $this->_update_table_options( $post_id, $table_options ); continue; } // Nothing to do if there are no "Custom Commands". if ( '' === $table_options['datatables_custom_commands'] ) { continue; } // Run search/replace. $old_custom_commands = $table_options['datatables_custom_commands']; $table_options['datatables_custom_commands'] = TablePress::convert_datatables_api_data( $table_options['datatables_custom_commands'] ); // No need to save (which runs a DB query) if nothing was replaced in the "Custom Commands". if ( $old_custom_commands === $table_options['datatables_custom_commands'] ) { continue; } $this->_update_table_options( $post_id, $table_options ); } } /** * Invalidate all table output caches, e.g. after a plugin update. * * @since 1.0.0 */ public function invalidate_table_output_caches(): void { $table_post = $this->tables->get( 'table_post' ); if ( empty( $table_post ) ) { return; } foreach ( $table_post as $table_id => $post_id ) { $table_id = (string) $table_id; // Ensure that the table ID is a string, as it comes from an array key where numeric strings are converted to integers. $this->invalidate_table_output_cache( $table_id ); } } /** * Add mime type field to existing posts with the TablePress Custom Post Type, * so that other plugins know that they are not dealing with plain text. * * @since 1.5.0 * * @global wpdb $wpdb WordPress database abstraction object. */ public function add_mime_type_to_posts(): void { global $wpdb; $wpdb->update( $wpdb->posts, array( 'post_mime_type' => 'application/json' ), array( 'post_type' => $this->model_post->get_post_type() ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching } /** * Add a table ID for a post (table) that was imported through the WP WXR importer to the table ID to post ID map. * * @since 1.5.0 * * @param int|WP_Error $post_id Post ID of the imported post on success. 0 or WP_Error on failure. * @param int $original_post_id Original post ID that the post had on the site where it was exported from. * @param array $postdata Post data that was imported into the database. * @param array $post Original post data as it was exported. */ public function add_table_id_on_wp_import( /* int|WP_Error */ $post_id, int $original_post_id, array $postdata, array $post ): void { // Bail if the post could not be imported or if the post is not a TablePress table. if ( is_wp_error( $post_id ) || $this->model_post->get_post_type() !== $postdata['post_type'] ) { return; } // Extract the table IDs from the `_tablepress_export_table_id` post meta field. $table_ids = array(); if ( isset( $post['postmeta'] ) && is_array( $post['postmeta'] ) ) { foreach ( $post['postmeta'] as $postmeta ) { if ( '_tablepress_export_table_id' === $postmeta['key'] ) { $table_ids = $postmeta['value']; $table_ids = explode( ',', $table_ids ); break; } } } // Save the post ID for each of the table IDs. $post_id_saved = false; foreach ( $table_ids as $table_id ) { $table_id = (string) preg_replace( '/[^a-zA-Z0-9_-]/', '', $table_id ); if ( '' === $table_id || $this->table_exists( $table_id ) ) { continue; } $this->_update_post_id( $table_id, $post_id ); $post_id_saved = true; } // Save the post ID for a new table ID if it could not be saved for any of the imported table IDs. if ( ! $post_id_saved ) { $table_id = $this->_get_new_table_id(); $this->_update_post_id( $table_id, $post_id ); } } /** * Remove the `_tablepress_export_table_id` post meta field from the fields that are imported during a WP WXR import. * * @since 1.5.0 * * @param array $postmeta Post meta fields for the post. * @param int $post_id Post ID. * @param array $post Post. * @return array Modified post meta fields. */ public function prevent_table_id_post_meta_import_on_wp_import( array $postmeta, int $post_id, array $post ): array { // Bail if the post is not a TablePress table. if ( $this->model_post->get_post_type() !== $post['post_type'] ) { return $postmeta; } // Remove the `_tablepress_export_table_id` post meta field from the post meta fields. foreach ( $postmeta as $index => $meta ) { if ( '_tablepress_export_table_id' === $meta['key'] ) { unset( $postmeta[ $index ] ); } } return $postmeta; } /** * Add the table IDs for an exported post (table) to the WP WXR export file. * * The table IDs for a table are exported in a faked post meta field. * As there's no action for adding extra data to the WXR export file, we hijack the `wxr_export_skip_postmeta` filter hook. * * @since 1.5.0 * * @param bool $skip Whether to skip the current post meta. Default false. * @param string $meta_key Current meta key. * @param stdClass $meta Current meta object. * @return bool Whether to skip the current post meta (unchanged $skip parameter). */ public function add_table_id_to_wp_export( bool $skip, string $meta_key, stdClass $meta ): bool { // Bail if the exporter doesn't process a TablePress table right now. if ( $this->table_options_field_name !== $meta_key ) { return $skip; } // Find all table IDs that map to the post ID of the table that is currently being exported. $table_post = $this->tables->get( 'table_post' ); $table_ids = array_keys( $table_post, (int) $meta->post_id, true ); // Bail if no table IDs are mapped to this post ID. if ( empty( $table_ids ) ) { return $skip; } // Pretend that there is a `_tablepress_export_table_id` post meta field with the list of table IDs. $key = '_tablepress_export_table_id'; /** * Load WP export functions. */ require_once ABSPATH . 'wp-admin/includes/export.php'; // @phpstan-ignore requireOnce.fileNotFound (This is a WordPress core file that always exists.) $value = wxr_cdata( implode( ',', $table_ids ) ); // Hijack the filter and print extra XML code for our faked post meta field. // phpcs:disable WordPress.Security.EscapeOutput.HeredocOutputNotEscaped echo << {$key} {$value} \n WXR; // phpcs:enable return $skip; } /** * Delete the WP_Option of the model. * * @since 1.0.0 */ public function destroy(): void { $this->tables->delete(); } } // class TablePress_Table_Model PK!models/index.phpnu[ */ protected array $default_plugin_options = array( 'plugin_options_db_version' => 0, 'table_scheme_db_version' => 0, 'prev_tablepress_version' => '0', 'tablepress_version' => TablePress::version, 'first_activation' => 0, 'message_plugin_update' => false, 'message_donation_nag' => true, 'use_custom_css' => true, 'use_custom_css_file' => true, 'custom_css' => '', 'custom_css_minified' => '', 'custom_css_version' => 0, 'modules' => false, ); /** * Default User Options. * * @since 1.0.0 * @var array */ protected array $default_user_options = array( 'user_options_db_version' => TablePress::db_version, // To prevent saving on first load. 'admin_menu_parent_page' => 'middle', 'message_first_visit' => true, 'message_superseded_extensions' => true, 'table_editor_column_width' => '170', // Default column width of 170px of the table editor on the "Edit" screen. 'table_editor_line_clamp' => '5', // Maximum number of lines per cell in the table editor on the "Edit" screen. ); /** * Instance of WP_Option class for Plugin Options. * * @since 1.0.0 */ protected \TablePress_WP_Option $plugin_options; /** * Instance of WP_User_Option class for User Options. * * @since 1.0.0 */ protected \TablePress_WP_User_Option $user_options; /** * Init Options Model by creating the object instances for the Plugin and User Options. * * @since 1.0.0 */ public function __construct() { parent::__construct(); $params = array( 'option_name' => 'tablepress_plugin_options', 'default_value' => $this->default_plugin_options, ); $this->plugin_options = TablePress::load_class( 'TablePress_WP_Option', 'class-wp_option.php', 'classes', $params ); $params = array( 'option_name' => 'tablepress_user_options', 'default_value' => $this->default_user_options, ); $this->user_options = TablePress::load_class( 'TablePress_WP_User_Option', 'class-wp_user_option.php', 'classes', $params ); // Filter to map Meta capabilities to Primitive Capabilities. add_filter( 'map_meta_cap', array( $this, 'map_tablepress_meta_caps' ), 10, 4 ); } /** * Update a single option or an array of options with new values. * * @since 1.0.0 * * @param array|string $new_options Array of new options (name => value) or name of a single option. * @param mixed $single_value Optional. New value for a single option (only if $new_options is not an array). */ public function update( /* array|string */ $new_options, /* string|bool|int|float|null */ $single_value = null ): void { // Allow saving of single options that are not in an array. if ( ! is_array( $new_options ) ) { $new_options = array( $new_options => $single_value ); } $plugin_options = $this->plugin_options->get(); $user_options = $this->user_options->get(); $old_options = array_merge( $plugin_options, $user_options ); foreach ( $new_options as $name => $value ) { if ( isset( $this->default_plugin_options[ $name ] ) ) { $plugin_options[ $name ] = $value; } elseif ( isset( $this->default_user_options[ $name ] ) ) { $user_options[ $name ] = $value; } else { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedElse // No valid Plugin or User Option -> discard the name/value pair. } } $this->plugin_options->update( $plugin_options ); $this->user_options->update( $user_options ); /** * Fires after the options have been saved. * * @since 3.1.0 * * @param array $old_options Array of all options before the update. */ do_action( 'tablepress_event_updated_options', $old_options ); } /** * Get the value of a single option, or an array with all options. * * @since 1.0.0 * * @param string|false $name Optional. Name of a single option to get, or false for all options. * @param mixed $default_value Optional. Default value, if the option $name does not exist. * @return mixed Value of the retrieved option $name, or $default_value if it does not exist, or array of all options. */ public function get( /* string|false */ $name = false, /* string|bool|int|float|null */ $default_value = null ) /* : string|bool|int|float|array|null */ { if ( false === $name ) { return array_merge( $this->plugin_options->get(), $this->user_options->get() ); } // Single Option wanted. if ( $this->plugin_options->is_set( $name ) ) { return $this->plugin_options->get( $name ); } elseif ( $this->user_options->is_set( $name ) ) { return $this->user_options->get( $name ); } else { // No valid Plugin or User Option. return $default_value; } } /** * Get all Plugin Options (only used in Debug). * * @since 1.0.0 * * @return array Array of all Plugin Options. */ public function _debug_get_plugin_options(): array { return $this->plugin_options->get(); } /** * Get all User Options (only used in Debug). * * @since 1.0.0 * * @return array Array of all User Options. */ public function _debug_get_user_options(): array { return $this->user_options->get(); } /** * Merge existing Plugin Options with default Plugin Options, * remove (no longer) existing options, e.g. after a plugin update. * * @since 1.0.0 */ public function merge_plugin_options_defaults(): void { $plugin_options = $this->plugin_options->get(); // Remove old (i.e. no longer existing) Plugin Options. $plugin_options = array_intersect_key( $plugin_options, $this->default_plugin_options ); // Merge current into new Plugin Options. $plugin_options = array_merge( $this->default_plugin_options, $plugin_options ); $this->plugin_options->update( $plugin_options ); } /** * Merge existing User Options with default User Options, * remove (no longer) existing options, e.g. after a plugin update. * * @since 1.0.0 */ public function merge_user_options_defaults(): void { $user_options = $this->user_options->get(); // Remove old (i.e. no longer existing) User Options. $user_options = array_intersect_key( $user_options, $this->default_user_options ); // Merge current into new User Options. $user_options = array_merge( $this->default_user_options, $user_options ); $this->user_options->update( $user_options ); } /** * Adds the TablePress default capabilities to the "Administrator", "Editor", and "Author" user roles. * * @since 1.0.0 */ public function add_access_capabilities(): void { // Capabilities for all roles. $roles = array( 'administrator', 'editor', 'author' ); foreach ( $roles as $role ) { $role = get_role( $role ); if ( empty( $role ) ) { continue; } // From get_post_type_capabilities(). $role->add_cap( 'tablepress_edit_tables' ); // $role->add_cap( 'tablepress_edit_others_tables' ); $role->add_cap( 'tablepress_delete_tables' ); // $role->add_cap( 'tablepress_delete_others_tables' ); // Custom capabilities. $role->add_cap( 'tablepress_list_tables' ); $role->add_cap( 'tablepress_add_tables' ); $role->add_cap( 'tablepress_copy_tables' ); $role->add_cap( 'tablepress_import_tables' ); $role->add_cap( 'tablepress_export_tables' ); $role->add_cap( 'tablepress_access_options_screen' ); $role->add_cap( 'tablepress_access_about_screen' ); } // Capabilities for single roles. $role = get_role( 'administrator' ); if ( ! empty( $role ) ) { $role->add_cap( 'tablepress_edit_options' ); $role->add_cap( 'tablepress_import_tables_url' ); } $role = get_role( 'editor' ); if ( ! empty( $role ) ) { $role->add_cap( 'tablepress_import_tables_url' ); } // Refresh current set of capabilities of the user, to be able to directly use the new caps. $user = wp_get_current_user(); $user->get_role_caps(); } /** * Adds a custom "Import from URL" access capability to the "Editor" and "Administrator" user roles. * * @since 2.3.2 */ public function add_access_capabilities_tp232(): void { $roles = array( 'administrator', 'editor' ); foreach ( $roles as $role ) { $role = get_role( $role ); if ( ! empty( $role ) ) { $role->add_cap( 'tablepress_import_tables_url' ); } } // Refresh current set of capabilities of the user, to be able to directly use the new caps. $user = wp_get_current_user(); $user->get_role_caps(); } /** * Remove all TablePress capabilities from all roles. * * @since 1.1.0 * * @global WP_Roles $wp_roles WordPress User Roles abstraction object. * @see add_access_capabilities() */ public function remove_access_capabilities(): void { global $wp_roles; if ( ! isset( $wp_roles ) ) { $wp_roles = new WP_Roles(); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited } foreach ( $wp_roles->roles as $role => $details ) { $role = $wp_roles->get_role( $role ); if ( empty( $role ) ) { continue; } $role->remove_cap( 'tablepress_edit_tables' ); $role->remove_cap( 'tablepress_delete_tables' ); $role->remove_cap( 'tablepress_list_tables' ); $role->remove_cap( 'tablepress_add_tables' ); $role->remove_cap( 'tablepress_copy_tables' ); $role->remove_cap( 'tablepress_import_tables' ); $role->remove_cap( 'tablepress_export_tables' ); $role->remove_cap( 'tablepress_access_options_screen' ); $role->remove_cap( 'tablepress_access_about_screen' ); $role->remove_cap( 'tablepress_import_tables_wptr' ); // No longer used since 1.10, but might still be in the database. $role->remove_cap( 'tablepress_edit_options' ); } // Refresh current set of capabilities of the user, to be able to directly use the new caps. $user = wp_get_current_user(); $user->get_role_caps(); } /** * Map TablePress meta capabilities to primitive capabilities. * * @since 1.0.0 * * @param string[] $caps Current set of primitive caps. * @param string $cap Meta cap that is to be checked/mapped. * @param int $user_id User ID for which meta cap is to be checked. * @param mixed[] $args Arguments for the check, here e.g. the table ID. * @return string[] Modified set of primitive caps. */ public function map_tablepress_meta_caps( array $caps, /* string */ $cap, int $user_id, array $args ): array { // Don't use a type hint for the `$cap` argument as many WordPress plugins seem to be passing `null` to capability check or admin menu functions. // Protect against other plugins not supplying a string for the `$cap` argument. if ( ! is_string( $cap ) ) { // @phpstan-ignore function.alreadyNarrowedType (The `is_string()` check is needed as the input is coming from a filter hook.) return $caps; } if ( ! in_array( $cap, array( 'tablepress_edit_table', 'tablepress_edit_table_id', 'tablepress_copy_table', 'tablepress_delete_table', 'tablepress_export_table', 'tablepress_preview_table' ), true ) ) { return $caps; } // $table_id = ( ! empty( $args ) ) ? $args[0] : false; // Reset current set of primitive caps. $caps = array(); switch ( $cap ) { case 'tablepress_edit_table': $caps[] = 'tablepress_edit_tables'; break; case 'tablepress_edit_table_id': $caps[] = 'tablepress_edit_tables'; break; case 'tablepress_copy_table': $caps[] = 'tablepress_copy_tables'; break; case 'tablepress_delete_table': $caps[] = 'tablepress_delete_tables'; break; case 'tablepress_export_table': $caps[] = 'tablepress_export_tables'; break; case 'tablepress_preview_table': $caps[] = 'tablepress_edit_tables'; break; default: // Something went wrong, deny access to be on the safe side. $caps[] = 'do_not_allow'; break; } /** * Filters a user's TablePress capabilities. * * @since 1.0.0 * * @see map_meta_cap() * * @param string[] $caps The user's current TablePress capabilities. * @param string $cap Capability name. * @param int $user_id The user ID. * @param mixed[] $args Adds the context to the cap, typically the table ID. */ return apply_filters( 'tablepress_map_meta_caps', $caps, $cap, $user_id, $args ); } /** * Delete the WP_Option and the user option of the model. * * @since 1.0.0 */ public function destroy(): void { $this->plugin_options->delete(); $this->user_options->delete_for_all_users(); } } // class TablePress_Options_Model PK!&1&1models/model-post.phpnu[_register_post_type(); // We are on WP "init" hook already. } /** * Registers the Custom Post Type which the tables use. * * @since 1.0.0 */ protected function _register_post_type(): void { /** * Filters the "Custom Post Type" that TablePress uses for storing tables in the database. * * @since 1.0.0 * * @param string $post_type The "Custom Post Type" that TablePress uses. */ $this->post_type = apply_filters( 'tablepress_post_type', $this->post_type ); $post_type_args = array( 'labels' => array( 'name' => 'TablePress Tables', ), 'public' => false, 'show_ui' => false, 'query_var' => false, 'rewrite' => false, 'capability_type' => 'tablepress_table', // This ensures, that WP's regular CPT UI respects our capabilities. 'map_meta_cap' => false, // Integrated WP mapping does not fit our needs, therefore use our own in a filter. 'supports' => array( 'title', 'editor', 'excerpt', 'revisions' ), 'can_export' => true, ); /** * Filters the arguments for the registration of the "Custom Post Type" that TablePress uses. * * @since 1.0.0 * * @param array $post_type_args Arguments for the registration of the TablePress "Custom Post Type". */ $post_type_args = apply_filters( 'tablepress_post_type_args', $post_type_args ); register_post_type( $this->post_type, $post_type_args ); } /** * Inserts a post with the correct Custom Post Type and default values in the the wp_posts table in the database. * * @since 1.0.0 * * @param array $post Post to insert. * @return int|WP_Error Post ID of the inserted post on success, WP_Error on error. */ public function insert( array $post ) /* : int|WP_Error */ { $default_post = array( 'ID' => false, // false on new insert, but existing post ID on update. 'comment_status' => 'closed', 'ping_status' => 'closed', 'post_category' => false, 'post_content' => '', 'post_excerpt' => '', 'post_parent' => 0, 'post_password' => '', 'post_status' => 'publish', 'post_title' => '', 'post_type' => $this->post_type, 'tags_input' => '', 'to_ping' => '', ); $post = array_merge( $default_post, $post ); // WP expects everything to be slashed. $post = wp_slash( $post ); // Remove balanceTags() from sanitize_post(), as it can destroy the JSON when messing with HTML. remove_filter( 'content_save_pre', 'balanceTags', 50 ); remove_filter( 'excerpt_save_pre', 'balanceTags', 50 ); /* * Remove possible KSES filtering, as it can destroy the JSON when messing with HTML. * KSES filtering is done to table cells individually, when saving. */ $has_kses = ( false !== has_filter( 'content_save_pre', 'wp_filter_post_kses' ) ); if ( $has_kses ) { kses_remove_filters(); } // Remove filter that adds `rel="noopener" to HTML tags, but destroys JSON code. See https://core.trac.wordpress.org/ticket/46316. $has_targeted_link_rel_filters = ( false !== has_filter( 'content_save_pre', 'wp_targeted_link_rel' ) ); if ( $has_targeted_link_rel_filters ) { wp_remove_targeted_link_rel_filters(); } $post_id = wp_insert_post( $post, true ); // Restore removed content filters. add_filter( 'content_save_pre', 'balanceTags', 50 ); add_filter( 'excerpt_save_pre', 'balanceTags', 50 ); if ( $has_kses ) { kses_init_filters(); } if ( $has_targeted_link_rel_filters ) { wp_init_targeted_link_rel_filters(); } // In rare cases, `wp_insert_post()` returns 0 as the post ID, when an error happens, so it's converted to a WP_Error here. if ( 0 === $post_id ) { // @phpstan-ignore identical.alwaysFalse (False-positive in the PHPStan WordPress stubs.) return new WP_Error( 'post_insert', '' ); } return $post_id; } /** * Updates an existing post with the correct Custom Post Type and default values in the the wp_posts table in the database. * * @since 1.0.0 * * @param array $post Post. * @return int|WP_Error Post ID of the updated post on success, WP_Error on error. */ public function update( array $post ) /* : int|WP_Error */ { $default_post = array( 'ID' => false, // false on new insert, but existing post ID on update. 'comment_status' => 'closed', 'ping_status' => 'closed', 'post_category' => false, 'post_content' => '', 'post_excerpt' => '', 'post_parent' => 0, 'post_password' => '', 'post_status' => 'publish', 'post_title' => '', 'post_type' => $this->post_type, 'tags_input' => '', 'to_ping' => '', ); $post = array_merge( $default_post, $post ); // WP expects everything to be slashed. $post = wp_slash( $post ); // Remove balanceTags() from sanitize_post(), as it can destroy the JSON when messing with HTML. remove_filter( 'content_save_pre', 'balanceTags', 50 ); remove_filter( 'excerpt_save_pre', 'balanceTags', 50 ); /* * Remove possible KSES filtering, as it can destroy the JSON when messing with HTML. * KSES filtering is done to table cells individually, when saving. */ $has_kses = ( false !== has_filter( 'content_save_pre', 'wp_filter_post_kses' ) ); if ( $has_kses ) { kses_remove_filters(); } // Remove filter that adds `rel="noopener" to HTML tags, but destroys JSON code. See https://core.trac.wordpress.org/ticket/46316. $has_targeted_link_rel_filters = ( false !== has_filter( 'content_save_pre', 'wp_targeted_link_rel' ) ); if ( $has_targeted_link_rel_filters ) { wp_remove_targeted_link_rel_filters(); } $post_id = wp_update_post( $post, true ); // Restore removed content filters. add_filter( 'content_save_pre', 'balanceTags', 50 ); add_filter( 'excerpt_save_pre', 'balanceTags', 50 ); if ( $has_kses ) { kses_init_filters(); } if ( $has_targeted_link_rel_filters ) { wp_init_targeted_link_rel_filters(); } return $post_id; } /** * Gets a post from the wp_posts table in the database. * * @since 1.0.0 * * @param int $post_id Post ID. * @return WP_Post|false Post on success, false on error. */ public function get( int $post_id ) /* : WP_Post|false */ { $post = get_post( $post_id ); if ( is_null( $post ) ) { return false; } return $post; } /** * Deletes a post (and all revisions) from the wp_posts table in the database. * * @since 1.0.0 * * @param int $post_id Post ID. * @return WP_Post|false|null Post data on success, false or null on failure. */ public function delete( int $post_id ) /* : WP_Post|false|null */ { return wp_delete_post( $post_id, true ); // true means force delete, although for CPTs this is automatic in this function. } /** * Moves a post to the trash (if trash is globally enabled), instead of directly deleting the post. * (yet unused) * * @since 1.0.0 * * @param int $post_id Post ID. * @return WP_Post|false|null Post data on success, false or null on failure. */ public function trash( int $post_id ) /* : WP_Post|false|null */ { return wp_trash_post( $post_id ); } /** * Restores a post from the trash. * (yet unused) * * @since 1.0.0 * * @param int $post_id Post ID. * @return WP_Post|false|null Post on success, false or null on error. */ public function untrash( int $post_id ) /* : WP_Post|false */ { return wp_untrash_post( $post_id ); } /** * Loads all posts with one query, to prime the cache. * * @since 1.0.0 * * @global wpdb $wpdb WordPress database abstraction object. * @see get_post() * * @param int[] $all_post_ids List of Post IDs. * @param bool $update_meta_cache Optional. Whether to update the Post Meta Cache (for table options and visibility). */ public function load_posts( array $all_post_ids, bool $update_meta_cache = true ): void { global $wpdb; // Split post loading, to save memory. while ( ! empty( $all_post_ids ) ) { $post_ids = array_splice( $all_post_ids, 0, 100 ); // Extract 100 posts at a time. // Don't load posts that are in the cache already. $post_ids = _get_non_cached_ids( $post_ids, 'posts' ); if ( ! empty( $post_ids ) ) { $post_ids_list = implode( ',', $post_ids ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared $posts = $wpdb->get_results( "SELECT {$wpdb->posts}.* FROM {$wpdb->posts} WHERE ID IN ({$post_ids_list})" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared update_post_cache( $posts ); if ( $update_meta_cache ) { // Get all post meta data for all table posts, @see get_post_meta(). update_meta_cache( 'post', $post_ids ); } } } } /** * Counts the number of posts with the model's CPT in the wp_posts table in the database. * (currently for debug only) * * @since 1.0.0 * * @return int Number of posts. */ public function count_posts(): int { return array_sum( (array) wp_count_posts( $this->post_type ) ); // Original return value is object with the counts for each post_status. } /** * Adds a post meta field to a post. * * @since 1.0.0 * * @param int $post_id ID of the post for which the field shall be added. * @param string $field Name of the post meta field. * @param string $value Value of the post meta field (not slashed). * @return bool True on success, false on error. */ public function add_meta_field( int $post_id, string $field, string $value ): bool { // WP expects a slashed value. $value = wp_slash( $value ); $success = add_post_meta( $post_id, $field, $value, true ); // true means unique. // Make sure that $success is a boolean, as add_post_meta() returns an ID or false. $success = ( false === $success ) ? false : true; return $success; } /** * Updatse the value of a post meta field of a post. * * If the field does not yet exist, it is added. * * @since 1.0.0 * * @param int $post_id ID of the post for which the field shall be updated. * @param string $field Name of the post meta field. * @param string $value Value of the post meta field (not slashed). * @return bool True on success, false on error. */ public function update_meta_field( int $post_id, string $field, string $value ): bool { $prev_value = (string) get_post_meta( $post_id, $field, true ); // No need to update, if values are equal (also, update_post_meta() would return false for this). if ( $prev_value === $value ) { return true; } // WP expects a slashed value. $value = wp_slash( $value ); return (bool) update_post_meta( $post_id, $field, $value, $prev_value ); } /** * Gets the value of a post meta field of a post. * * @since 1.0.0 * * @param int $post_id ID of the post for which the field shall be retrieved. * @param string $field Name of the post meta field. * @return string Value of the meta field. */ public function get_meta_field( int $post_id, string $field ): string { return get_post_meta( $post_id, $field, true ); // true means single value. } /** * Deletes a post meta field of a post. * (yet unused) * * @since 1.0.0 * * @param int $post_id ID of the post of which the field shall be deleted. * @param string $field Name of the post meta field. * @return bool True on success, false on error. */ public function delete_meta_field( int $post_id, string $field ): bool { return delete_post_meta( $post_id, $field, true ); // true means single value. } /** * Returns the Custom Post Type that TablePress uses. * * @since 1.5.0 * * @return string The used Custom Post Type. */ public function get_post_type(): string { return $this->post_type; } } // class TablePress_Post_Model PK!++ readme.txtnu[=== TablePress - Tables in WordPress made easy === Contributors: TobiasBg Donate link: https://tablepress.org/premium/?utm_source=wordpress.org&utm_medium=textlink&utm_content=donate-link Tags: table, spreadsheet, csv, excel, tables Requires at least: 6.2 Requires PHP: 7.4 Tested up to: 6.8 Stable tag: 3.1.2 License: GPLv2 License URI: https://www.gnu.org/licenses/gpl-2.0.html Embed beautiful, accessible, and interactive tables into your WordPress website’s posts and pages, without having to write code! == Description == **Boost your website with feature-rich tables that your visitors will love!** TablePress is the most popular and highest-rated WordPress table plugin. * Easily create, edit, and manage **beautiful and modern** data tables, no matter if **small or large**! * Add live **sorting**, **pagination**, **searching**, and more interactivity for your site’s visitors! * Use any type of data, insert **images**, **links**, and even **math formulas**! * **Import** and **export** tables from/to Excel, CSV, HTML, and JSON files or URLs. * Embed tables into posts, pages, or other site areas using the block editor, an Elementor widget, or Shortcodes. * All with **no coding knowledge needed**! Even **more great features** for you and your site’s visitors and **priority email support** are **available** with a Premium license plan of TablePress. [Go check them out!](https://tablepress.org/premium/?utm_source=wordpress.org&utm_medium=textlink&utm_content=readme) = More information = Visit [tablepress.org](https://tablepress.org/) for more information, take a look at [example tables](https://tablepress.org/demo/), or [try TablePress on a free test site](https://tablepress.org/demo/#try). For latest news, [follow @TablePress](https://twitter.com/TablePress) on Twitter/X or subscribe to the [TablePress Newsletter](https://tablepress.org/#newsletter). == Screenshots == 1. "All Tables" screen 2. "Edit" screen 3. "Add new Table" screen 4. "Import" screen 5. "Export" screen 6. "Plugin Options" screen 7. "About" screen 8. The “TablePress table” block in the block editor 9. An example table (as it can be seen on the [TablePress website](https://tablepress.org/demo/)) == Installation == The easiest way to install TablePress is via your WordPress Dashboard: 1. Go to the "Plugins" screen, click "Add New", and search for "TablePress" in the WordPress Plugin Directory. 1. Click "Install Now" and after that's complete, click "Activate". 1. Create and manage tables by going to the "TablePress" screen in the admin menu. 1. To insert a table into a post or page, add a "TablePress table" block in the block editor or a widget in the Elementor page builder and select the desired table or use Shortcodes with other page builders. Manual installation works just as for other WordPress plugins: 1. [Download the TablePress ZIP file](https://downloads.wordpress.org/plugin/tablepress.latest-stable.zip). 1. Go to the “Plugins“ screen on your site and upload it by clicking “Add New” → “Upload Plugin“. 1. Or, extract the ZIP file and move the folder “tablepress“ to the “wp-content/plugins/“ directory of your WordPress installation, e.g. via FTP. 1. Activate "TablePress" on the "Plugins" screen of your WordPress Dashboard. 1. Create and manage tables by going to the "TablePress" screen in the admin menu. 1. To insert a table into a post or page, add a "TablePress table" block in the block editor or a widget in the Elementor page builder and select the desired table or use Shortcodes with other page builders. == Frequently Asked Questions == = Where can I find answers to Frequently Asked Questions? = Many questions, regarding different features or styling, have been answered on the [FAQ page](https://tablepress.org/faq/) and in the extensive [TablePress plugin documentation](https://tablepress.org/documentation/) on the TablePress website. = Support? = **Premium Support** Users with an active TablePress Premium license plan are eligible for Priority Email Support, directly from the plugin developer! [Find out more!](https://tablepress.org/premium/?utm_source=wordpress.org&utm_medium=textlink&utm_content=readme) **Community Support for users of the Free version** For support questions, bug reports, or feature requests, please use the [WordPress Support Forums](https://wordpress.org/support/plugin/tablepress/). Please search through the forums first, and only [create a new topic](https://wordpress.org/support/plugin/tablepress/#new-post) if you don't find an existing answer. Thank you! = Requirements? = In short: WordPress 6.2 or higher, while the latest version of WordPress is always recommended. In addition, the server must be running PHP 7.4 or newer. = Languages and Localization? = TablePress uses the ["Translate WordPress" platform](https://translate.wordpress.org/). Please see the sidebar on the TablePress page in the [WordPress Plugin Directory](https://wordpress.org/plugins/tablepress/) for available translations. To make TablePress available in your language, go to the [TablePress translations page](https://translate.wordpress.org/projects/wp-plugins/tablepress), log in with a free wordpress.org account and start translating. = Development = You can follow the development of TablePress more closely in its official [GitHub repository](https://github.com/TablePress/TablePress). = Where do I report security issues? = Please report security issues and bugs found in the source code of TablePress through the [Patchstack Vulnerability Disclosure Program](https://patchstack.com/database/vdp/tablepress). The Patchstack team will assist you with verification, CVE assignment, and notify the TablePress developer. = Where can I get more information? = Visit the plugin website at [tablepress.org](https://tablepress.org/) for the latest news on TablePress, [follow @TablePress](https://twitter.com/TablePress) on Twitter/X, or subscribe to the [TablePress Newsletter](https://tablepress.org/#newsletter). == How to use TablePress == After installing the plugin, you can create and manage tables on the "TablePress" screen in the WordPress Dashboard. To insert a table into a post or page, add a "TablePress table" block in the block editor or a widget in the Elementor page builder and select the desired table or use Shortcodes with other page builders. Beginner-friendly step-by-step [tutorials, guides, and how-tos](https://tablepress.org/tutorials/) show how to achieve common and popular tasks with TablePress. Examples for common styling changes via "Custom CSS" code can be found on the [TablePress FAQ page](https://tablepress.org/faq/). You may also add certain features (like sorting, pagination, filtering, alternating row colors, row highlighting, print name and/or description, ...) by enabling the corresponding checkboxes on a table's "Edit" screen. **Even more great features for you and your site’s visitors and priority email support are available with a Premium license plan of TablePress. [Go check them out!](https://tablepress.org/premium/?utm_source=wordpress.org&utm_medium=textlink&utm_content=readme)** == Changelog == Changes in recent versions are shown below. For earlier changes, please see the [changelog history](https://tablepress.org/info/#changelog). = Version 3.1.2 (April 29, 2025) = * “Responsive Tables“ module: Don’t require the “Search/Filtering“ feature to be activated in order to enable the “Collapse“ or “Modal“ modes. (TablePress Pro and Max only.) * “Individual Column Filtering“ module: Fix the table column widths when the “Fixed Header“ or “Fixed Columns“ features are used. (TablePress Pro and Max only.) * “Automatic Periodic Table Import“ module: Limit the number of revisions for automatically updated tables, to prevent database size and PHP memory issues. (TablePress Max only.) * “Advanced Pagination Settings“ module: Prevents problems when sites use HTML code minification. (TablePress Pro and Max only.) * Cleaned up and simplified code, for easier future maintenance, to follow WordPress Coding Standards, and to offer helpful inline documentation. * Updated external libraries to benefit from enhancements and bug fixes. = Version 3.1.1 (April 1, 2025) = * The CSS code for styling individual table rows will now be applied again. * Users of the Elementor page builder can now properly delete a “TablePress table“ widget again. = Version 3.1 (March 25, 2025) = TablePress 3.1 is a major feature, stability, maintenance, compatibility, and security update. Here are the highlights: **Improved Frontend Table Performance** * Users of the Elementor page builder plugin can now use a dedicated “TablePress table“ widget that makes embedding tables even easier! * Showing tables in tabs or accordions will no longer break their size or add visual glitches! * Tables and their interactivity features are more accessible for visitors with disabilities and users of assistive technologies, with improved labelling and easier-to-use keyboard navigation! **New Premium Feature Modules** * **Email Notifications** * Get email notifications when certain actions are performed on tables! **Many New Features and Enhancements for Existing Premium Features** * **Responsive Tables** * The styling and highlighting of rows and child rows when using the “Collapse“ mode has been improved to make it even easier to see which data belongs together! * **Advanced Pagination Settings** * Use a “Show more“ button instead of classical pagination for improved visitor engagement! * **Column Filter Dropdowns** * You can now turn on classical single-selection dropdown controls for a more solid user experience! * **Row Filtering** * A new syntax that understands complex logic expressions gives Row Filtering superpowers! * **Row Highlighting and Cell Highlighting** * Highlighting cells and rows is now possible with complex logic and math expressions, for even more control! **Behind the scenes** * **Security fix**: Authenticated Stored XSS (CVE-2025-2685). Thanks to SavPhill and the Wordfence team for following responsible disclosure policies when reporting this issue! * Several minor bugs and inconsistencies have been fixed and improved! * Cleaned up and simplified code, for easier future maintenance, to follow WordPress Coding Standards, and to offer helpful inline documentation. * Updated external libraries to benefit from enhancements and bug fixes. * Automated code compatibility checks and build tools simplify chores for easier development. * Improved support for PHP 8.4. **Premium versions** * Even more great features for you and your site’s visitors and priority email support are available with a Premium license plan of TablePress. [Go check them out!](https://tablepress.org/premium/?utm_source=wordpress.org&utm_medium=textlink&utm_content=readme) == Upgrade Notice == = 3.1.2 = This update is a stability, maintenance, and compatibility release. Updating is highly recommended. = 3.1 = This update is a major feature, stability, maintenance, and compatibility release. Updating is highly recommended! PK!)6,,tablepress.phpnu[set_basename( false, __FILE__ ); // @phpstan-ignore argument.type (Wrong variable type in Freemius function docblock.) } else { /** * Helper function for easier Freemius SDK access. * * @since 2.0.0 * * @return Freemius Freemius SDK instance. */ function tb_tp_fs() /* No return type declaration, due to required PHP compatibility of this file! */ { global $tb_tp_fs; if ( ! isset( $tb_tp_fs ) ) { // Include Freemius SDK. require_once __DIR__ . '/libraries/freemius/start.php'; $tb_tp_fs = fs_dynamic_init( array( 'id' => '10340', 'slug' => 'tablepress', 'type' => 'plugin', 'public_key' => 'pk_b215ca1bb4041cf43ed137ae7665b', 'is_premium' => false, 'has_addons' => false, 'has_paid_plans' => true, 'menu' => array( 'slug' => 'tablepress', 'contact' => false, 'support' => false, 'account' => false, ), 'opt_in_moderation' => array( 'new' => true, 'updates' => false, 'localhost' => false, ), 'is_live' => true, 'anonymous_mode' => TABLEPRESS_IS_PLAYGROUND_PREVIEW, ) ); } return $tb_tp_fs; } // Init Freemius. tb_tp_fs(); // Register Freemius plugin filter hooks. require_once __DIR__ . '/controllers/freemius-filters.php'; // Signal that the SDK was initiated. do_action( 'tb_tp_fs_loaded' ); /* * Define certain plugin variables as constants. */ if ( ! defined( 'TABLEPRESS_ABSPATH' ) ) { define( 'TABLEPRESS_ABSPATH', trailingslashit( __DIR__ ) ); } if ( ! defined( 'TABLEPRESS__FILE__' ) ) { define( 'TABLEPRESS__FILE__', __FILE__ ); } if ( ! defined( 'TABLEPRESS_BASENAME' ) ) { define( 'TABLEPRESS_BASENAME', plugin_basename( TABLEPRESS__FILE__ ) ); } if ( ! defined( 'TABLEPRESS_JSON_OPTIONS' ) ) { // JSON_UNESCAPED_SLASHES: Don't escape slashes, e.g. to make search/replace of URLs in the database easier. define( 'TABLEPRESS_JSON_OPTIONS', JSON_UNESCAPED_SLASHES ); } /* * Check if the site environment fulfills the minimum requirements. */ if ( ! require_once TABLEPRESS_ABSPATH . 'controllers/environment-checks.php' ) { return; // Exit early if the return value from the file is false. } /* * Load TablePress class, which holds common functions and variables. */ require_once TABLEPRESS_ABSPATH . 'classes/class-tablepress.php'; /* * Start up TablePress on WordPress's "init" action hook. */ add_action( 'init', array( 'TablePress', 'run' ) ); } PK!QQ'css/_default-datatables-pagination.scssnu[/** * TablePress Default CSS - DataTables-Pagination-related code. * * Attention: Do not modify this file directly, but use the "Custom CSS" textarea * on the "Plugin Options" screen of TablePress. * * @package TablePress * @subpackage Frontend CSS * @author Tobias Bäthge, Allan Jardine * @since 3.0.0 */ /* Default toggle variable for LTR and RTL CSS. */ $direction: "ltr" !default; /* Default variables for the LTR CSS. */ $align-side: left; /* Variables for the RTL CSS. */ @if "rtl" == $direction { $align-side: right; } /* Pagination. */ @mixin dt-paging { .dt-paging { .dt-paging-button { box-sizing: border-box; display: inline-block; min-width: 32px; height: 32px; padding: 0 5px; margin-#{$align-side}: 2px; text-align: center; vertical-align: middle; text-decoration: none !important; cursor: pointer; font-size: 1em; color: inherit !important; border: 1px solid transparent; border-radius: 2px; background: transparent; &.current, &:hover { border: 1px solid #111111; } &.disabled, &.disabled:hover, &.disabled:active { cursor: default; color: rgba(0, 0, 0, 0.3) !important; border: 1px solid transparent; outline: none; } } > .dt-paging-button:first-child { margin-#{$align-side}: 0; } .ellipsis { padding: 0 1em; } } } PK!Ecss/build/default-rtl.cssnu[.tablepress{--text-color:#111;--head-text-color:var(--text-color);--head-bg-color:#d9edf7;--odd-text-color:var(--text-color);--odd-bg-color:#fff;--even-text-color:var(--text-color);--even-bg-color:#f9f9f9;--hover-text-color:var(--text-color);--hover-bg-color:#f3f3f3;--border-color:#ddd;--padding:0.5rem;border:none;border-collapse:collapse;border-spacing:0;clear:both;margin:0 auto 1rem;table-layout:auto;width:100%}.tablepress>:not(caption)>*>*{background:none;border:none;box-sizing:border-box;float:none!important;padding:var(--padding);text-align:right;vertical-align:top}.tablepress>:where(thead)+tbody>:where(:not(.child))>*,.tablepress>tbody>*~:where(:not(.child))>*,.tablepress>tfoot>:where(:first-child)>*{border-top:1px solid var(--border-color)}.tablepress>:where(thead,tfoot)>tr>*{background-color:var(--head-bg-color);color:var(--head-text-color);font-weight:700;vertical-align:middle;word-break:normal}.tablepress>:where(tbody)>tr>*{color:var(--text-color)}.tablepress>:where(tbody.row-striping)>:nth-child(odd of :where(:not(.child,.dtrg-group)))+:where(.child)>*,.tablepress>:where(tbody.row-striping)>:nth-child(odd of :where(:not(.child,.dtrg-group)))>*{background-color:var(--odd-bg-color);color:var(--odd-text-color)}.tablepress>:where(tbody.row-striping)>:nth-child(even of :where(:not(.child,.dtrg-group)))+:where(.child)>*,.tablepress>:where(tbody.row-striping)>:nth-child(even of :where(:not(.child,.dtrg-group)))>*{background-color:var(--even-bg-color);color:var(--even-text-color)}.tablepress>.row-hover>tr:has(+.child:hover)>*,.tablepress>.row-hover>tr:hover+:where(.child)>*,.tablepress>.row-hover>tr:where(:not(.dtrg-group)):hover>*{background-color:var(--hover-bg-color);color:var(--hover-text-color)}.tablepress img{border:none;margin:0;max-width:none;padding:0}.tablepress-table-description{clear:both;display:block}.dt-scroll{width:100%}.dt-scroll .tablepress{width:100%!important}div.dt-scroll-body tfoot tr,div.dt-scroll-body thead tr{height:0}div.dt-scroll-body tfoot tr td,div.dt-scroll-body tfoot tr th,div.dt-scroll-body thead tr td,div.dt-scroll-body thead tr th{border-bottom-width:0!important;border-top-width:0!important;height:0!important;padding-bottom:0!important;padding-top:0!important}div.dt-scroll-body tfoot tr td div.dt-scroll-sizing,div.dt-scroll-body tfoot tr th div.dt-scroll-sizing,div.dt-scroll-body thead tr td div.dt-scroll-sizing,div.dt-scroll-body thead tr th div.dt-scroll-sizing{height:0!important;overflow:hidden!important}div.dt-scroll-body>table.dataTable>thead>tr>td,div.dt-scroll-body>table.dataTable>thead>tr>th{overflow:hidden}.tablepress{--head-active-bg-color:#049cdb;--head-active-text-color:var(--head-text-color);--head-sort-arrow-color:var(--head-active-text-color)}.tablepress thead th:active{outline:none}.tablepress thead .dt-orderable-asc .dt-column-order:before,.tablepress thead .dt-ordering-asc .dt-column-order:before{bottom:50%;content:"\25b2"/"";display:block;position:absolute}.tablepress thead .dt-orderable-desc .dt-column-order:after,.tablepress thead .dt-ordering-desc .dt-column-order:after{content:"\25bc"/"";display:block;position:absolute;top:50%}.tablepress thead .dt-orderable-asc,.tablepress thead .dt-orderable-desc,.tablepress thead .dt-ordering-asc,.tablepress thead .dt-ordering-desc{padding-left:24px;position:relative}.tablepress thead .dt-orderable-asc .dt-column-order,.tablepress thead .dt-orderable-desc .dt-column-order,.tablepress thead .dt-ordering-asc .dt-column-order,.tablepress thead .dt-ordering-desc .dt-column-order{bottom:0;color:var(--head-sort-arrow-color);left:6px;position:absolute;top:0;width:12px}.tablepress thead .dt-orderable-asc .dt-column-order:after,.tablepress thead .dt-orderable-asc .dt-column-order:before,.tablepress thead .dt-orderable-desc .dt-column-order:after,.tablepress thead .dt-orderable-desc .dt-column-order:before,.tablepress thead .dt-ordering-asc .dt-column-order:after,.tablepress thead .dt-ordering-asc .dt-column-order:before,.tablepress thead .dt-ordering-desc .dt-column-order:after,.tablepress thead .dt-ordering-desc .dt-column-order:before{font-family:sans-serif!important;font-size:12px;line-height:12px;opacity:.2}.tablepress thead .dt-orderable-asc,.tablepress thead .dt-orderable-desc{cursor:pointer;outline-offset:-2px}.tablepress thead .dt-orderable-asc:hover,.tablepress thead .dt-orderable-desc:hover,.tablepress thead .dt-ordering-asc,.tablepress thead .dt-ordering-desc{background-color:var(--head-active-bg-color);color:var(--head-active-text-color)}.tablepress thead .dt-ordering-asc .dt-column-order:before,.tablepress thead .dt-ordering-desc .dt-column-order:after{opacity:.8}.tablepress:where(.auto-type-alignment) .dt-right,.tablepress:where(.auto-type-alignment) .dt-type-date,.tablepress:where(.auto-type-alignment) .dt-type-numeric{text-align:left}.dt-container{clear:both;margin-bottom:1rem;position:relative}.dt-container .tablepress{margin-bottom:0}.dt-container .tablepress tfoot:empty{display:none}.dt-container .dt-layout-row{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between}.dt-container .dt-layout-row.dt-layout-table .dt-layout-cell{display:block;width:100%}.dt-container .dt-layout-cell{display:flex;flex-direction:row;flex-wrap:wrap;gap:1em;padding:5px 0}.dt-container .dt-layout-cell.dt-layout-full{align-items:center;justify-content:space-between;width:100%}.dt-container .dt-layout-cell.dt-layout-full>:only-child{margin:auto}.dt-container .dt-layout-cell.dt-layout-start{align-items:center;justify-content:flex-start;margin-right:auto}.dt-container .dt-layout-cell.dt-layout-end{align-items:center;justify-content:flex-end;margin-left:auto}.dt-container .dt-layout-cell:empty{display:none}.dt-container .dt-input,.dt-container label{display:inline;width:auto}.dt-container .dt-input{font-size:inherit;padding:5px}.dt-container .dt-length,.dt-container .dt-search{align-items:center;display:flex;flex-wrap:wrap;gap:5px;justify-content:center}.dt-container .dt-paging .dt-paging-button{background:#0000;border:1px solid #0000;border-radius:2px;box-sizing:border-box;color:inherit!important;cursor:pointer;display:inline-block;font-size:1em;height:32px;margin-right:2px;min-width:32px;padding:0 5px;text-align:center;text-decoration:none!important;vertical-align:middle}.dt-container .dt-paging .dt-paging-button.current,.dt-container .dt-paging .dt-paging-button:hover{border:1px solid #111}.dt-container .dt-paging .dt-paging-button.disabled,.dt-container .dt-paging .dt-paging-button.disabled:active,.dt-container .dt-paging .dt-paging-button.disabled:hover{border:1px solid #0000;color:#0000004d!important;cursor:default;outline:none}.dt-container .dt-paging>.dt-paging-button:first-child{margin-right:0}.dt-container .dt-paging .ellipsis{padding:0 1em}@media screen and (max-width:767px){.dt-container .dt-layout-row{flex-direction:column}.dt-container .dt-layout-cell{flex-direction:column;padding:.5em 0}} PK!css/build/index.phpnu[:not(caption)>*>*{background:none;border:none;box-sizing:border-box;float:none!important;padding:var(--padding);text-align:left;vertical-align:top}.tablepress>:where(thead)+tbody>:where(:not(.child))>*,.tablepress>tbody>*~:where(:not(.child))>*,.tablepress>tfoot>:where(:first-child)>*{border-top:1px solid var(--border-color)}.tablepress>:where(thead,tfoot)>tr>*{background-color:var(--head-bg-color);color:var(--head-text-color);font-weight:700;vertical-align:middle;word-break:normal}.tablepress>:where(tbody)>tr>*{color:var(--text-color)}.tablepress>:where(tbody.row-striping)>:nth-child(odd of :where(:not(.child,.dtrg-group)))+:where(.child)>*,.tablepress>:where(tbody.row-striping)>:nth-child(odd of :where(:not(.child,.dtrg-group)))>*{background-color:var(--odd-bg-color);color:var(--odd-text-color)}.tablepress>:where(tbody.row-striping)>:nth-child(even of :where(:not(.child,.dtrg-group)))+:where(.child)>*,.tablepress>:where(tbody.row-striping)>:nth-child(even of :where(:not(.child,.dtrg-group)))>*{background-color:var(--even-bg-color);color:var(--even-text-color)}.tablepress>.row-hover>tr:has(+.child:hover)>*,.tablepress>.row-hover>tr:hover+:where(.child)>*,.tablepress>.row-hover>tr:where(:not(.dtrg-group)):hover>*{background-color:var(--hover-bg-color);color:var(--hover-text-color)}.tablepress img{border:none;margin:0;max-width:none;padding:0}.tablepress-table-description{clear:both;display:block}.dt-scroll{width:100%}.dt-scroll .tablepress{width:100%!important}div.dt-scroll-body tfoot tr,div.dt-scroll-body thead tr{height:0}div.dt-scroll-body tfoot tr td,div.dt-scroll-body tfoot tr th,div.dt-scroll-body thead tr td,div.dt-scroll-body thead tr th{border-bottom-width:0!important;border-top-width:0!important;height:0!important;padding-bottom:0!important;padding-top:0!important}div.dt-scroll-body tfoot tr td div.dt-scroll-sizing,div.dt-scroll-body tfoot tr th div.dt-scroll-sizing,div.dt-scroll-body thead tr td div.dt-scroll-sizing,div.dt-scroll-body thead tr th div.dt-scroll-sizing{height:0!important;overflow:hidden!important}div.dt-scroll-body>table.dataTable>thead>tr>td,div.dt-scroll-body>table.dataTable>thead>tr>th{overflow:hidden}.tablepress{--head-active-bg-color:#049cdb;--head-active-text-color:var(--head-text-color);--head-sort-arrow-color:var(--head-active-text-color)}.tablepress thead th:active{outline:none}.tablepress thead .dt-orderable-asc .dt-column-order:before,.tablepress thead .dt-ordering-asc .dt-column-order:before{bottom:50%;content:"\25b2"/"";display:block;position:absolute}.tablepress thead .dt-orderable-desc .dt-column-order:after,.tablepress thead .dt-ordering-desc .dt-column-order:after{content:"\25bc"/"";display:block;position:absolute;top:50%}.tablepress thead .dt-orderable-asc,.tablepress thead .dt-orderable-desc,.tablepress thead .dt-ordering-asc,.tablepress thead .dt-ordering-desc{padding-right:24px;position:relative}.tablepress thead .dt-orderable-asc .dt-column-order,.tablepress thead .dt-orderable-desc .dt-column-order,.tablepress thead .dt-ordering-asc .dt-column-order,.tablepress thead .dt-ordering-desc .dt-column-order{bottom:0;color:var(--head-sort-arrow-color);position:absolute;right:6px;top:0;width:12px}.tablepress thead .dt-orderable-asc .dt-column-order:after,.tablepress thead .dt-orderable-asc .dt-column-order:before,.tablepress thead .dt-orderable-desc .dt-column-order:after,.tablepress thead .dt-orderable-desc .dt-column-order:before,.tablepress thead .dt-ordering-asc .dt-column-order:after,.tablepress thead .dt-ordering-asc .dt-column-order:before,.tablepress thead .dt-ordering-desc .dt-column-order:after,.tablepress thead .dt-ordering-desc .dt-column-order:before{font-family:sans-serif!important;font-size:12px;line-height:12px;opacity:.2}.tablepress thead .dt-orderable-asc,.tablepress thead .dt-orderable-desc{cursor:pointer;outline-offset:-2px}.tablepress thead .dt-orderable-asc:hover,.tablepress thead .dt-orderable-desc:hover,.tablepress thead .dt-ordering-asc,.tablepress thead .dt-ordering-desc{background-color:var(--head-active-bg-color);color:var(--head-active-text-color)}.tablepress thead .dt-ordering-asc .dt-column-order:before,.tablepress thead .dt-ordering-desc .dt-column-order:after{opacity:.8}.tablepress:where(.auto-type-alignment) .dt-right,.tablepress:where(.auto-type-alignment) .dt-type-date,.tablepress:where(.auto-type-alignment) .dt-type-numeric{text-align:right}.dt-container{clear:both;margin-bottom:1rem;position:relative}.dt-container .tablepress{margin-bottom:0}.dt-container .tablepress tfoot:empty{display:none}.dt-container .dt-layout-row{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between}.dt-container .dt-layout-row.dt-layout-table .dt-layout-cell{display:block;width:100%}.dt-container .dt-layout-cell{display:flex;flex-direction:row;flex-wrap:wrap;gap:1em;padding:5px 0}.dt-container .dt-layout-cell.dt-layout-full{align-items:center;justify-content:space-between;width:100%}.dt-container .dt-layout-cell.dt-layout-full>:only-child{margin:auto}.dt-container .dt-layout-cell.dt-layout-start{align-items:center;justify-content:flex-start;margin-right:auto}.dt-container .dt-layout-cell.dt-layout-end{align-items:center;justify-content:flex-end;margin-left:auto}.dt-container .dt-layout-cell:empty{display:none}.dt-container .dt-input,.dt-container label{display:inline;width:auto}.dt-container .dt-input{font-size:inherit;padding:5px}.dt-container .dt-length,.dt-container .dt-search{align-items:center;display:flex;flex-wrap:wrap;gap:5px;justify-content:center}.dt-container .dt-paging .dt-paging-button{background:#0000;border:1px solid #0000;border-radius:2px;box-sizing:border-box;color:inherit!important;cursor:pointer;display:inline-block;font-size:1em;height:32px;margin-left:2px;min-width:32px;padding:0 5px;text-align:center;text-decoration:none!important;vertical-align:middle}.dt-container .dt-paging .dt-paging-button.current,.dt-container .dt-paging .dt-paging-button:hover{border:1px solid #111}.dt-container .dt-paging .dt-paging-button.disabled,.dt-container .dt-paging .dt-paging-button.disabled:active,.dt-container .dt-paging .dt-paging-button.disabled:hover{border:1px solid #0000;color:#0000004d!important;cursor:default;outline:none}.dt-container .dt-paging>.dt-paging-button:first-child{margin-left:0}.dt-container .dt-paging .ellipsis{padding:0 1em}@media screen and (max-width:767px){.dt-container .dt-layout-row{flex-direction:column}.dt-container .dt-layout-cell{flex-direction:column;padding:.5em 0}} PK! css/index.phpnu[ :not(caption) > * > * { padding: var(--padding); border: none; background: none; text-align: $align-side; vertical-align: top; float: none !important; /* Work around themes that set `float` on `.column-1` CSS classes and similar. */ box-sizing: border-box; } /* Horizontal borders, except for child rows in the tbody. */ > :where(thead) + tbody > :where(:not(.child)) > *, > tbody > * ~ :where(:not(.child)) > *, > tfoot > :where(:first-child) > * { border-top: 1px solid var(--border-color); } /* Row background colors. */ > :where(thead, tfoot) > tr > * { background-color: var(--head-bg-color); color: var(--head-text-color); font-weight: bold; word-break: normal; vertical-align: middle; } > :where(tbody) { > tr > * { color: var(--text-color); } } > :where(tbody.row-striping) { > :nth-child(odd of :where(:not(.child, .dtrg-group))) { /* Ignore child and group rows. */ > *, + :where(.child) > * { /* Give child rows the same style. */ background-color: var(--odd-bg-color); color: var(--odd-text-color); } } > :nth-child(even of :where(:not(.child, .dtrg-group))) { /* Ignore child and group rows. */ > *, + :where(.child) > * { /* Give child rows the same style. */ background-color: var(--even-bg-color); color: var(--even-text-color); } } } > .row-hover > tr { &:where(:not(.dtrg-group)):hover > *, /* Ignore group rows. */ &:hover + :where(.child) > *, /* Give child rows the same style. */ &:has(+ .child:hover) > * { /* Highlight the parent row when a child row is hovered. */ background-color: var(--hover-bg-color); color: var(--hover-text-color); } } /* Reset image layout in tables. */ img { margin: 0; padding: 0; border: none; max-width: none; } /* Table description. */ &-table-description { clear: both; display: block; } } PK![qqcss/default-rtl.scssnu[/** * TablePress Default CSS for RTL languages * * Attention: Do not modify this file directly, but use the "Custom CSS" textarea * on the "Plugin Options" screen of TablePress. * * @package TablePress * @subpackage Frontend CSS * @author Tobias Bäthge * @since 1.0.0 */ /* Import TablePress default CSS for RTL. */ @use "default" with ($direction: "rtl"); PK!ăBBcss/default.scssnu[/** * TablePress Default CSS * * Attention: Do not modify this file directly, but use the "Custom CSS" textarea * on the "Plugin Options" screen of TablePress. * * @package TablePress * @subpackage Frontend CSS * @author Tobias Bäthge * @since 1.0.0 */ /* Default toggle variable for LTR and RTL CSS. */ $direction: "ltr" !default; /* Import TablePress default CSS (basic/general styling). */ @use "default-core" with ($direction: $direction); /* Import TablePress default CSS (DataTables-related code). */ @use "default-datatables" with ($direction: $direction); PK!nYYcss/_default-datatables.scssnu[/** * TablePress Default CSS - DataTables-related code. * * @package TablePress * @subpackage Frontend CSS * @author Tobias Bäthge, Allan Jardine * @since 1.0.0 */ // Load the Sass string module, for use in the `unicode` function. @use "sass:string"; /* Default toggle variable for LTR and RTL CSS. */ $direction: "ltr" !default; /* Import DataTables pagination code. */ @use "default-datatables-pagination" with ($direction: $direction); // Ensure that Unicode characters are embedded in ASCII-encoded form. @function unicode($str) { @return string.unquote("\"") + string.unquote(string.insert($str, "\\", 1)) + string.unquote("\""); } /* Default variables for the LTR CSS. */ $align-side: left; $align-side-opposite: right; /* Variables for the RTL CSS. */ @if "rtl" == $direction { $align-side: right; $align-side-opposite: left; } /* Scrolling. */ .dt-scroll { width: 100%; /* Fix for wrong alignment and width. */ .tablepress { width: 100% !important; } } div.dt-scroll-body { thead, tfoot { tr { height: 0; th, td { height: 0 !important; padding-top: 0 !important; padding-bottom: 0 !important; border-top-width: 0 !important; border-bottom-width: 0 !important; div.dt-scroll-sizing { height: 0 !important; overflow: hidden !important; } } } } > table.dataTable > thead > tr { > th, > td { overflow: hidden; } } } /* Sorting. */ .tablepress { /* Custom properties. */ --head-active-bg-color: #049cdb; --head-active-text-color: var(--head-text-color); --head-sort-arrow-color: var(--head-active-text-color); thead { th:active { outline: none; } .dt-orderable-asc, .dt-ordering-asc { .dt-column-order:before { position: absolute; display: block; bottom: 50%; content: unicode("25b2") / ""; } } .dt-orderable-desc, .dt-ordering-desc { .dt-column-order:after { position: absolute; display: block; top: 50%; content: unicode("25bc") / ""; } } .dt-orderable-asc, .dt-orderable-desc, .dt-ordering-asc, .dt-ordering-desc { position: relative; padding-#{$align-side-opposite}: 24px; .dt-column-order { position: absolute; #{$align-side-opposite}: 6px; top: 0; bottom: 0; width: 12px; color: var(--head-sort-arrow-color); &:before, &:after { opacity: 0.2; line-height: 12px; font-size: 12px; font-family: sans-serif !important; } } } .dt-orderable-asc, .dt-orderable-desc { cursor: pointer; outline-offset: -2px; &:hover { background-color: var(--head-active-bg-color); color: var(--head-active-text-color); } } .dt-ordering-asc, .dt-ordering-desc { background-color: var(--head-active-bg-color); color: var(--head-active-text-color); } .dt-ordering-asc .dt-column-order:before, .dt-ordering-desc .dt-column-order:after { opacity: 0.8; } } } /* Automatic data type alignment. */ .tablepress:where(.auto-type-alignment) { .dt-type-numeric, .dt-type-date, /* .dt-right is used for custom dates. */ .dt-right { text-align: $align-side-opposite; } } /* Element positioning. */ .dt-container { position: relative; clear: both; margin-bottom: 1rem; .tablepress { margin-bottom: 0; /* Empty footers should be hidden for improved accessibility. */ tfoot:empty { display: none; } } .dt-layout-row { display: flex; flex-wrap: wrap; flex-direction: row; justify-content: space-between; &.dt-layout-table { .dt-layout-cell { display: block; width: 100%; } } } .dt-layout-cell { display: flex; flex-wrap: wrap; flex-direction: row; gap: 1em; padding: 5px 0; &.dt-layout-full { justify-content: space-between; align-items: center; width: 100%; > *:only-child { margin: auto; } } &.dt-layout-start { justify-content: flex-start; align-items: center; margin-right: auto; } &.dt-layout-end { justify-content: flex-end; align-items: center; margin-left: auto; } &:empty { display: none; } } label { display: inline; width: auto; } /* Input and select fields. */ .dt-input { display: inline; padding: 5px; width: auto; font-size: inherit; } /* Search and Length Change fields. */ .dt-search, .dt-length { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 5px; } /* Pagination. */ @include default-datatables-pagination.dt-paging; } @media screen and (max-width: 767px) { .dt-container { .dt-layout-row { flex-direction: column; } .dt-layout-cell { flex-direction: column; padding: 0.5em 0; } } } PK!>#^^%views/class-all-tables-list-table.phpnu[ */ protected array $_actions; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore /** * Initialize the List Table. * * @since 1.0.0 */ public function __construct() { $screen = get_current_screen(); // Hide "Last Modified By" column by default. if ( false === get_user_option( "manage{$screen->id}columnshidden" ) ) { // @phpstan-ignore property.nonObject update_user_option( get_current_user_id(), "manage{$screen->id}columnshidden", array( 'table_last_modified_by' ), true ); // @phpstan-ignore property.nonObject } // @phpstan-ignore argument.type (WordPress Core's docblocks state wrong argument types in some places.) parent::__construct( array( 'singular' => 'tablepress-table', // Singular name of the listed records. 'plural' => 'tablepress-all-tables', // Plural name of the listed records. 'ajax' => false, // Does this list table support AJAX? 'screen' => $screen, // WP_Screen object. ) ); } /** * Set the data items (here: tables) that are to be displayed by the List Tables, and their original count. * * @since 1.0.0 * * @param string[] $items Tables to be displayed in the List Table. */ public function set_items( array $items ): void { $this->items = $items; $this->items_count = count( $items ); } /** * Check whether the user has permissions for certain AJAX actions. * (not used, but must be implemented in this child class) * * @since 1.0.0 * * @return bool true (Default value). */ #[\Override] public function ajax_user_can(): bool { return true; } /** * Get a list of columns in this List Table. * * Format: 'internal-name' => 'Column Title'. * * @since 1.0.0 * * @return array List of columns in this List Table. */ #[\Override] public function get_columns(): array { $columns = array( 'cb' => $this->has_items() ? '' : '', // Checkbox for "Select all", but only if there are items in the table. // "name" is special in WP, which is why we prefix every entry here, to be safe! 'table_id' => __( 'ID', 'tablepress' ), 'table_name' => __( 'Table Name', 'tablepress' ), 'table_description' => __( 'Description', 'tablepress' ), 'table_author' => __( 'Author', 'tablepress' ), 'table_last_modified_by' => __( 'Last Modified By', 'tablepress' ), 'table_last_modified' => __( 'Last Modified', 'tablepress' ), ); return $columns; } /** * Get a list of columns that are sortable. * * Format: 'internal-name' => array( $field for $item[ $field ], true for already sorted ). * * @since 1.0.0 * * @return array List of sortable columns in this List Table. */ #[\Override] protected function get_sortable_columns(): array { // No sorting on the Empty List placeholder. if ( ! $this->has_items() ) { return array(); } $sortable_columns = array( 'table_id' => array( 'id', true ), // true means its already sorted. 'table_name' => array( 'name', false ), 'table_description' => array( 'description', false ), 'table_author' => array( 'author', false ), 'table_last_modified_by' => array( 'last_modified_by', false ), 'table_last_modified' => array( 'last_modified', false ), ); return $sortable_columns; } /** * Gets the name of the default primary column. * * @since 1.7.0 * * @return string Name of the default primary column, in this case, the table name. */ #[\Override] protected function get_default_primary_column_name(): string { return 'table_name'; } /** * Render a cell in the "cb" column. * * @since 1.0.0 * * @param array $item Data item for the current row. * @return string HTML content of the cell. */ #[\Override] protected function column_cb( /* array */ $item ): string { // Don't use type hints in the method declaration to prevent PHP errors, as the method is inherited. $user_can_copy_table = current_user_can( 'tablepress_copy_table', $item['id'] ); $user_can_delete_table = current_user_can( 'tablepress_delete_table', $item['id'] ); $user_can_export_table = current_user_can( 'tablepress_export_table', $item['id'] ); if ( ! ( $user_can_copy_table || $user_can_delete_table || $user_can_export_table ) ) { return ''; } if ( '' === trim( $item['name'] ) ) { $item['name'] = __( '(no name)', 'tablepress' ); } return sprintf( // The `label-covers-full-cell` class on the ' . esc_html( $item['name'] ) . ''; } else { $row_text = '' . esc_html( $item['name'] ) . ''; } $row_actions = array(); if ( $user_can_edit_table ) { $row_actions['edit'] = sprintf( '%3$s', $edit_url, esc_attr( sprintf( __( 'Edit “%s”', 'tablepress' ), $item['name'] ) ), __( 'Edit', 'tablepress' ) ); } $row_actions['shortcode hide-if-no-js'] = sprintf( '%3$s', '#', esc_attr( '[' . TablePress::$shortcode . " id={$item['id']} /]" ), __( 'Show Shortcode', 'tablepress' ) ); if ( $user_can_copy_table ) { $row_actions['copy'] = sprintf( '%3$s', $copy_url, esc_attr( sprintf( __( 'Copy “%s”', 'tablepress' ), $item['name'] ) ), __( 'Copy', 'tablepress' ) ); } if ( $user_can_export_table ) { $row_actions['export'] = sprintf( '%3$s', $export_url, esc_attr( sprintf( __( 'Export “%s”', 'tablepress' ), $item['name'] ) ), _x( 'Export', 'row action', 'tablepress' ) ); } if ( $user_can_delete_table ) { $row_actions['delete'] = sprintf( '%3$s', $delete_url, esc_attr( sprintf( __( 'Delete “%1$s” (ID %2$s)', 'tablepress' ), $item['name'], $item['id'] ) ), __( 'Delete', 'tablepress' ) ); } if ( $user_can_preview_table ) { $row_actions['table-preview'] = sprintf( '%3$s', $preview_url, esc_attr( sprintf( __( 'Preview of table “%1$s” (ID %2$s)', 'tablepress' ), $item['name'], $item['id'] ) ), __( 'Preview', 'tablepress' ) ); } return $row_text . $this->row_actions( $row_actions ); } /** * Render a cell in the "table_description" column. * * @since 1.0.0 * * @param array $item Data item for the current row. * @return string HTML content of the cell. */ protected function column_table_description( array $item ): string { if ( '' === trim( $item['description'] ) ) { $item['description'] = __( '(no description)', 'tablepress' ); } return esc_html( $item['description'] ); } /** * Render a cell in the "table_author" column. * * @since 1.0.0 * * @param array $item Data item for the current row. * @return string HTML content of the cell. */ protected function column_table_author( array $item ): string { return TablePress::get_user_display_name( $item['author'] ); } /** * Render a cell in the "last_modified_by" column. * * @since 1.0.0 * * @param array $item Data item for the current row. * @return string HTML content of the cell. */ protected function column_table_last_modified_by( array $item ): string { return TablePress::get_user_display_name( $item['options']['last_editor'] ); } /** * Render a cell in the "table_last_modified" column. * * @since 1.0.0 * * @param array $item Data item for the current row. * @return string HTML content of the cell. */ protected function column_table_last_modified( array $item ): string { $modified_timestamp = date_create( $item['last_modified'], wp_timezone() ); if ( false === $modified_timestamp ) { $modified_timestamp = $item['last_modified']; } else { $modified_timestamp = $modified_timestamp->getTimestamp(); } $current_timestamp = time(); $time_diff = $current_timestamp - $modified_timestamp; // Time difference is only shown up to one week. if ( $time_diff >= 0 && $time_diff < WEEK_IN_SECONDS ) { $time_diff = sprintf( __( '%s ago', 'default' ), human_time_diff( $modified_timestamp, $current_timestamp ) ); } else { $time_diff = TablePress::format_datetime( $item['last_modified'], '
' ); } $readable_time = TablePress::format_datetime( $item['last_modified'] ); return '' . $time_diff . ''; } /** * Handles output for the default column. * * @since 1.8.0 * * @param array $item Data item for the current row. * @param string $column_name Current column name. */ #[\Override] protected function column_default( /* array */ $item, /* string */ $column_name ): void { // Don't use type hints in the method declaration to prevent PHP errors, as the method is inherited. /** * Fires inside each custom column of the TablePress list table. * * @since 1.8.0 * * @param string $column_name Current column name. * @param array $item Data item for the current row. */ do_action( 'manage_tablepress_list_custom_column', $column_name, $item ); } /** * Generates and display row actions links for the list table. * * The "Table Name" column already gets these, so return an empty string here to prevent them from being duplicated. * * @since 2.0.0 * * @param object|array $item The item being acted upon. * @param string $column_name Current column name. * @param string $primary Primary column name. * @return string The row actions HTML, or an empty string if the current column is not the primary column. */ #[\Override] protected function handle_row_actions( /* object|array */ $item, /* string */ $column_name, /* string */ $primary ): string { // Don't use type hints in the method declaration to prevent PHP errors, as the method is inherited. return ''; } /** * Get a list (name => title) bulk actions that are available. * * @since 1.0.0 * * @return array Bulk actions for this table. */ #[\Override] protected function get_bulk_actions(): array { $bulk_actions = array(); if ( current_user_can( 'tablepress_copy_tables' ) ) { $bulk_actions['copy'] = _x( 'Copy', 'bulk action', 'tablepress' ); } if ( current_user_can( 'tablepress_export_tables' ) ) { $bulk_actions['export'] = _x( 'Export', 'bulk action', 'tablepress' ); } if ( current_user_can( 'tablepress_delete_tables' ) ) { $bulk_actions['delete'] = _x( 'Delete', 'bulk action', 'tablepress' ); } return $bulk_actions; } /** * Render the bulk actions dropdown. * * In comparison with parent class, this has modified HTML (especially no field named "action" as that's being used already)! * * @since 1.0.0 * * @param 'top'|'bottom' $which The location of the bulk actions: 'top' or 'bottom'. * This is designated as optional for backwards-compatibility. */ #[\Override] protected function bulk_actions( /* string */ $which = 'top' ): void { // Don't use type hints in the method declaration to prevent PHP errors, as the method is inherited. if ( ! isset( $this->_actions ) ) { $this->_actions = $this->get_bulk_actions(); $no_new_actions = $this->_actions; /** This filter is documented in the WordPress function WP_List_Table::bulk_actions() in wp-admin/includes/class-wp-list-table.php */ $this->_actions = apply_filters( 'bulk_actions-' . $this->screen->id, $this->_actions ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores $this->_actions = array_intersect_assoc( $this->_actions, $no_new_actions ); $two = ''; } else { $two = '2'; } if ( empty( $this->_actions ) ) { return; } $name_id = "bulk-action-selector-{$which}"; echo "\n"; echo "\n"; submit_button( __( 'Apply', 'tablepress' ), 'action', '', false, array( 'id' => "doaction{$two}" ) ); echo "\n"; } /** * Holds the message to be displayed when there are no items in the table. * * @since 1.0.0 */ #[\Override] public function no_items(): void { _e( 'No tables found.', 'tablepress' ); if ( 0 === $this->items_count ) { $user_can_add_tables = current_user_can( 'tablepress_add_tables' ); $user_can_import_tables = current_user_can( 'tablepress_import_tables' ); $add_url = TablePress::url( array( 'action' => 'add' ) ); $import_url = TablePress::url( array( 'action' => 'import' ) ); if ( $user_can_add_tables && $user_can_import_tables ) { echo ' ' . sprintf( __( 'You should add or import a table to get started!', 'tablepress' ), $add_url, $import_url ); } elseif ( $user_can_add_tables ) { echo ' ' . sprintf( __( 'You should add a table to get started!', 'tablepress' ), $add_url ); } elseif ( $user_can_import_tables ) { echo ' ' . sprintf( __( 'You should import a table to get started!', 'tablepress' ), $import_url ); } } } /** * Generate the elements above or below the table (like bulk actions and pagination). * * In comparison with parent class, this has no nonce field in its HTML code and a one-time filter around the pagination call, to change a string. * * @since 1.0.0 * * @param 'top'|'bottom' $which Location ("top" or "bottom"). */ #[\Override] protected function display_tablenav( /* string */ $which ): void { // Don't use type hints in the method declaration to prevent PHP errors, as the method is inherited. ?>
has_items() ) : ?>
bulk_actions( $which ); ?>
extra_tablenav( $which ); add_filter( 'ngettext_default', array( $this, 'change_pagination_items_string' ), 10, 5 ); $this->pagination( $which ); remove_filter( 'ngettext_default', array( $this, 'change_pagination_items_string' ), 10 ); ?>
load( $item, true, true ); if ( is_wp_error( $item ) ) { return false; } // Don't search corrupted tables, except when debug mode is enabled via $_GET parameter or WP_DEBUG constant. if ( ! $debug && isset( $item['is_corrupted'] ) && $item['is_corrupted'] ) { return false; } // Search from easy to hard, so that "expensive" code maybe doesn't have to run. if ( false !== stripos( $item['id'], (string) $term ) || false !== stripos( $item['name'], (string) $term ) || false !== stripos( $item['description'], (string) $term ) || false !== stripos( TablePress::get_user_display_name( $item['author'] ), (string) $term ) || false !== stripos( TablePress::get_user_display_name( $item['options']['last_editor'] ), (string) $term ) || false !== stripos( TablePress::format_datetime( $item['last_modified'] ), (string) $term ) || false !== stripos( wp_json_encode( $item['data'], TABLEPRESS_JSON_OPTIONS ), (string) $json_encoded_term ) ) { // @phpstan-ignore argument.type return true; } return false; } /** * Callback to for the array sort function. * * @since 1.0.0 * * @param array $item_a First item that shall be compared to. * @param array $item_b The second item for the comparison. * @return int (-1, 0, 1) depending on which item sorts "higher". */ protected function _order_callback( array $item_a, array $item_b ): int { global $orderby, $order; if ( 'last_modified_by' !== $orderby ) { if ( $item_a[ $orderby ] === $item_b[ $orderby ] ) { return 0; } } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found if ( $item_a['options']['last_editor'] === $item_b['options']['last_editor'] ) { return 0; } } // Certain fields require some extra work before being sortable. switch ( $orderby ) { case 'last_modified': // Compare UNIX timestamps for "last modified", which actually is a mySQL datetime string. $result = ( strtotime( $item_a['last_modified'] ) > strtotime( $item_b['last_modified'] ) ) ? 1 : -1; break; case 'author': // Get the actual author name, plain value is just the user ID. $result = strnatcasecmp( TablePress::get_user_display_name( $item_a['author'] ), TablePress::get_user_display_name( $item_b['author'] ) ); break; case 'last_modified_by': // Get the actual last editor name, plain value is just the user ID. $result = strnatcasecmp( TablePress::get_user_display_name( $item_a['options']['last_editor'] ), TablePress::get_user_display_name( $item_b['options']['last_editor'] ) ); break; default: // Other fields (ID, name, description) are sorted as strings. $result = strnatcasecmp( $item_a[ $orderby ], $item_b[ $orderby ] ); } return ( 'asc' === $order ) ? $result : - $result; } /** * Prepares the list of items for displaying, by maybe searching and sorting, and by doing pagination. * * @since 1.0.0 */ #[\Override] public function prepare_items(): void { global $orderby, $order, $s; wp_reset_vars( array( 'orderby', 'order', 's' ) ); // Maybe search in the items. if ( $s ) { $this->items = array_filter( $this->items, array( $this, '_search_callback' ) ); } // Load actual tables after search for less memory consumption. foreach ( $this->items as &$item ) { // Don't load data, but load table options for access to last_editor. $item = TablePress::$model_table->load( $item, false, true ); } unset( $item ); // Unset use-by-reference parameter of foreach loop. // Maybe sort the items. $_sortable_columns = $this->get_sortable_columns(); if ( $orderby && ! empty( $this->items ) && isset( $_sortable_columns[ "table_{$orderby}" ] ) ) { usort( $this->items, array( $this, '_order_callback' ) ); } // Number of records to show per page. $per_page = $this->get_items_per_page( 'tablepress_list_per_page', 20 ); // Hard-coded, as in filter in Admin_Controller. // Page number the user is currently viewing. $current_page = $this->get_pagenum(); // Number of records in the array. $total_items = count( $this->items ); // Slice items array to hold only items for the current page. $this->items = array_slice( $this->items, ( ( $current_page - 1 ) * $per_page ), $per_page ); // Register pagination options and calculation results. $this->set_pagination_args( array( 'total_items' => $total_items, // Total number of records/items. 'per_page' => $per_page, // Number of items per page. 'total_pages' => (int) ceil( $total_items / $per_page ), // Total number of pages. ) ); } } // class TablePress_All_Tables_List_Table PK!{4!views/view-add.phpnu[ $data Data for this view. */ #[\Override] public function setup( /* string */ $action, array $data ) /* : void */ { // Don't use type hints in the method declaration to prevent PHP errors, as the method is inherited. parent::setup( $action, $data ); $this->add_text_box( 'no-javascript', array( $this, 'textbox_no_javascript' ), 'header' ); $this->process_action_messages( array( 'error_add' => __( 'Error: The table could not be added.', 'tablepress' ), ) ); $this->admin_page->enqueue_script( 'add' ); $this->add_text_box( 'head', array( $this, 'textbox_head' ), 'normal' ); $this->add_text_box( 'add-table', array( $this, 'textbox_add_table' ), 'normal' ); } /** * Prints the screen head text. * * @since 1.0.0 * * @param array $data Data for this screen. * @param array $box Information about the text box. */ public function textbox_head( array $data, array $box ): void { ?>

$data Data for this screen. * @param array $box Information about the text box. */ public function textbox_add_table( array $data, array $box ): void { echo '
'; } } // class TablePress_Add_View PK!$]*VUVUviews/view-edit.phpnu[ $data Data for this view. */ #[\Override] public function setup( /* string */ $action, array $data ) /* : void */ { // Don't use type hints in the method declaration to prevent PHP errors, as the method is inherited. parent::setup( $action, $data ); $this->add_text_box( 'no-javascript', array( $this, 'textbox_no_javascript' ), 'header' ); if ( isset( $data['table']['is_corrupted'] ) && $data['table']['is_corrupted'] ) { $this->add_text_box( 'table-corrupted', array( $this, 'textbox_corrupted_table' ), 'header' ); return; } $this->process_action_messages( array( 'success_add' => __( 'The table was added successfully.', 'tablepress' ), 'success_copy' => _n( 'The table was copied successfully.', 'The tables were copied successfully.', 1, 'tablepress' ) . ' ' . sprintf( __( 'You are now seeing the copied table, which has the table ID “%s”.', 'tablepress' ), esc_html( $data['table']['id'] ) ), 'success_import' => __( 'The table was imported successfully.', 'tablepress' ), 'error_delete' => __( 'Error: The table could not be deleted.', 'tablepress' ), ) ); // For the Advanced Editor. wp_enqueue_style( 'wp-jquery-ui-dialog' ); wp_enqueue_style( 'editor-buttons' ); wp_enqueue_script( 'wpdialogs' ); // For the "Insert Image" dialog. add_filter( 'media_view_strings', array( $this, 'change_media_view_strings' ) ); wp_enqueue_media(); // For the "Insert Link" dialog. wp_enqueue_script( 'wplink' ); $this->admin_page->enqueue_style( 'jspreadsheet' ); $this->admin_page->enqueue_style( 'jsuites', array( 'tablepress-jspreadsheet' ) ); $this->admin_page->enqueue_style( 'edit', array( 'tablepress-jspreadsheet', 'tablepress-jsuites' ) ); if ( ! TABLEPRESS_IS_PLAYGROUND_PREVIEW && tb_tp_fs()->is_free_plan() ) { $this->admin_page->enqueue_style( 'edit-features', array( 'tablepress-edit' ) ); } $this->admin_page->enqueue_script( 'jspreadsheet' ); $this->admin_page->enqueue_script( 'jsuites', array( 'tablepress-jspreadsheet' ) ); $this->admin_page->enqueue_script( 'edit', array( 'tablepress-jspreadsheet', 'tablepress-jsuites', 'jquery-core' ) ); $this->add_text_box( 'head', array( $this, 'textbox_head' ), 'normal' ); $this->add_text_box( 'buttons-top', array( $this, 'textbox_buttons' ), 'normal' ); $this->add_meta_box( 'table-information', __( 'Table Information', 'tablepress' ), array( $this, 'postbox_table_information' ), 'normal' ); $this->add_meta_box( 'table-data', __( 'Table Content', 'tablepress' ), array( $this, 'postbox_table_data' ), 'normal' ); $this->add_meta_box( 'table-manipulation', __( 'Table Manipulation', 'tablepress' ), array( $this, 'postbox_table_manipulation' ), 'normal' ); $this->add_meta_box( 'table-options', __( 'Table Options', 'tablepress' ), array( $this, 'postbox_table_options' ), 'normal' ); $this->add_meta_box( 'datatables-features', __( 'Table Features for Site Visitors', 'tablepress' ), array( $this, 'postbox_datatables_features' ), 'normal' ); $this->add_text_box( 'hidden-containers', array( $this, 'textbox_hidden_containers' ), 'additional' ); $this->add_text_box( 'buttons-bottom', array( $this, 'textbox_buttons' ), 'additional' ); $this->add_text_box( 'other-actions', array( $this, 'textbox_other_actions' ), 'submit' ); add_filter( 'screen_settings', array( $this, 'add_screen_options_output' ), 10, 2 ); } /** * Changes the Media View string "Insert into post" to "Insert into Table". * * @since 1.0.0 * * @param array $strings Current set of Media View strings. * @return array Changed Media View strings. */ public function change_media_view_strings( array $strings ): array { $strings['insertIntoPost'] = __( 'Insert into Table', 'tablepress' ); return $strings; } /** * Adds custom screen options to the screen. * * @since 2.1.0 * * @param string $screen_settings Screen settings. * @param WP_Screen $screen WP_Screen object. * @return string Extended Screen settings. */ public function add_screen_options_output( /* string */ $screen_settings, WP_Screen $screen ): string { // Don't use a type hint for the `$screen_settings` argument as many WordPress plugins seem to be returning `null` in their filter hook handlers. // Protect against other plugins not returning a string for the `$screen_settings` argument. if ( ! is_string( $screen_settings ) ) { // @phpstan-ignore function.alreadyNarrowedType (The `is_string()` check is needed as the input is coming from a filter hook.) $screen_settings = ''; } $screen_settings = '
'; $screen_settings .= '' . __( 'Table editor settings', 'tablepress' ) . ''; $screen_settings .= '

' . __( 'Adjust the default size of the table cells in the table editor below.', 'tablepress' ) . ' ' . __( 'Cells with many lines of text will expand to their full height when they are edited.', 'tablepress' ) . '

'; $screen_settings .= '

' . __( 'Please note: These settings only influence the table editor view on this screen, but not the table that the site visitor sees!', 'tablepress' ) . '

'; $screen_settings .= '
'; $screen_settings .= ' '; $input = ''; $screen_settings .= sprintf( __( '%s pixels', 'tablepress' ), $input ); $screen_settings .= '
'; $screen_settings .= '
'; $screen_settings .= ' '; $input = ''; $screen_settings .= sprintf( __( '%s lines', 'tablepress' ), $input ); $screen_settings .= '
'; $screen_settings .= '
'; return $screen_settings; } /** * Renders the current view. * * In comparison to the parent class method, this contains handling for the no-js case and adjusts the HTML code structure. * * @since 2.0.0 */ #[\Override] public function render(): void { $this->print_javascript_data(); ?>
print_nav_tab_menu(); ?>

header_messages as $message ) { echo $message; } $this->do_text_boxes( 'header' ); ?>
do_text_boxes( 'normal' ); $this->do_meta_boxes( 'normal' ); $this->do_text_boxes( 'additional' ); $this->do_meta_boxes( 'additional' ); $this->do_text_boxes( 'submit' ); $this->do_text_boxes( 'side' ); $this->do_meta_boxes( 'side' ); ?>

$data Data for this screen. * @param array $box Information about the text box. */ #[\Override] protected function action_nonce_field( array $data, array $box ): void { // Intentionally left empty. Nonces for this view are generated in `print_javascript_data()`. } /** * Prints the table data for use in JS code and the main React container. * * @since 3.0.0 */ public function print_javascript_data(): void { echo "\n"; echo '
'; // React container. } /** * Prints the content of the "Table Information" post meta box. * * @since 1.0.0 * * @param array $data Data for this screen. * @param array $box Information about the meta box. */ public function postbox_table_information( array $data, array $box ): void { echo '
'; if ( ! TABLEPRESS_IS_PLAYGROUND_PREVIEW && tb_tp_fs()->is_free_plan() ) : ?>

' . __( 'Supercharge your tables with exceptional features:', 'tablepress' ) . ''; echo '

' . $feature_module['name'] . '

'; echo '' . $feature_module['description'] . ' ' . __( 'Read more!', 'tablepress' ) . ''; ?>
$data Data for this screen. * @param array $box Information about the meta box. */ public function postbox_table_data( array $data, array $box ): void { $css_variables = '--table-editor-line-clamp:' . absint( TablePress::$model_options->get( 'table_editor_line_clamp' ) ) . ';'; $css_variables = esc_attr( $css_variables ); echo "
"; } /** * Prints the content of the "Table Manipulation" post meta box. * * @since 1.0.0 * * @param array $data Data for this screen. * @param array $box Information about the meta box. */ public function postbox_table_manipulation( array $data, array $box ): void { echo '
'; } /** * Prints the container for the "Preview" and "Save Changes" buttons. * * @since 1.0.0 * * @param array $data Data for this screen. * @param array $box Information about the text box. */ public function textbox_buttons( array $data, array $box ): void { echo '
'; echo '
'; } /** * Prints the container for the "Copy Table, "Export Table", and "Delete Table" buttons. * * @since 1.0.0 * * @param array $data Data for this screen. * @param array $box Information about the text box. */ public function textbox_other_actions( array $data, array $box ): void { echo '
'; } /** * Prints the hidden containers for the Preview. * * @since 1.0.0 * * @param array $data Data for this screen. * @param array $box Information about the text box. */ public function textbox_hidden_containers( array $data, array $box ): void { ?>
10, 'tinymce' => false, 'quicktags' => array( 'buttons' => 'strong,em,link,del,ins,img,code,spell,close', ), ); wp_editor( '', 'advanced-editor-content', $wp_editor_options ); ?>
$data Data for this screen. * @param array $box Information about the meta box. */ public function postbox_table_options( array $data, array $box ): void { echo '
'; } /** * Prints the content of the "Table Features for Site Visitors" post meta box. * * @since 1.0.0 * * @param array $data Data for this screen. * @param array $box Information about the meta box. */ public function postbox_datatables_features( array $data, array $box ): void { echo '
'; } /** * Prints a notification about a corrupted table. * * @since 1.4.0 * * @param array $data Data for this screen. * @param array $box Information about the text box. */ public function textbox_corrupted_table( array $data, array $box ): void { ?>

' . esc_html( $data['table']['json_error'] ) . '' ); ?>

TablePress FAQ page for further instructions.', 'tablepress' ), 'https://tablepress.org/faq/corrupted-tables/' ); ?>

'list' ) ) ) . '" class="components-button is-secondary is-compact">' . __( 'Back to the List of Tables', 'tablepress' ) . ''; ?>

$data Data for this screen. * @param array $box Information about the text box. */ public function textbox_head( array $data, array $box ): void { echo '

'; _e( 'To edit the content or modify the structure of this table, use the input fields and buttons below.', 'tablepress' ); echo ' '; // Show the instructions string depending on whether the Block Editor is used on the site or not. if ( 'block' === $data['site_used_editor'] ) { printf( __( 'To insert a table into a post or page, add a “%1$s” block in the block editor and select the desired table.', 'tablepress' ), __( 'TablePress table', 'tablepress' ) ); } elseif ( 'elementor' === $data['site_used_editor'] ) { printf( __( 'To insert a table into a post or page, add a “%1$s” widget in the Elementor editor and select the desired table.', 'tablepress' ), __( 'TablePress table', 'tablepress' ) ); } else { _e( 'To insert a table into a post or page, paste its Shortcode at the desired place in the editor.', 'tablepress' ); } echo '

'; } /** * Sets the content for the WP feature pointer about the drag and drop and sort on the "Edit" screen. * * @since 2.0.0 */ public function wp_pointer_tp20_edit_context_menu(): void { $content = '

' . __( 'TablePress feature: Context menu', 'tablepress' ) . '

'; $content .= '

' . __( 'Did you know?', 'tablepress' ) . ' ' . __( 'Right-clicking the table content fields will open a context menu for quick access to common editing tools.', 'tablepress' ) . '

'; $this->admin_page->print_wp_pointer_js( 'tp20_edit_context_menu', '#table-editor', array( 'content' => $content, 'position' => array( 'edge' => 'bottom', 'align' => 'center' ), ), ); } /** * Sets the content for the WP feature pointer about the screen options for changing the cell size. * * @since 2.1.0 */ public function wp_pointer_tp21_edit_screen_options(): void { $content = '

' . __( 'TablePress feature: Column width and row height of the table editor', 'tablepress' ) . '

'; $content .= '

' . __( 'Did you know?', 'tablepress' ) . ' ' . sprintf( __( 'You can change the default cell size for the table editor on this “Edit” screen in the “%s”.', 'tablepress' ), __( 'Screen Options', 'default' ) ) . '

'; $this->admin_page->print_wp_pointer_js( 'tp21_edit_screen_options', '#screen-options-link-wrap', array( 'content' => $content, 'position' => array( 'edge' => 'top', 'align' => 'right' ), 'pointerClass' => 'wp-pointer pointer-tp21_edit_screen_options', ), ); } } // class TablePress_Edit_View PK!42Y2Yviews/view-list.phpnu[ $data Data for this view. */ #[\Override] public function setup( /* string */ $action, array $data ) /* : void */ { // Don't use type hints in the method declaration to prevent PHP errors, as the method is inherited. parent::setup( $action, $data ); $this->admin_page->enqueue_script( 'list' ); if ( $data['messages']['superseded_extensions'] ) { $superseded_extensions = array( 'tablepress-advanced-access-rights/tablepress-advanced-access-rights.php' => array( 'name' => 'TablePress Extension: Advanced Access Rights', 'compatible' => true, ), 'tablepress-auto-export-tables/tablepress-auto-export-tables.php' => array( 'name' => 'TablePress Extension: Automatic Table Export', 'compatible' => true, ), 'tablepress-cell-highlighting/tablepress-cell-highlighting.php' => array( 'name' => 'TablePress Extension: Cell Highlighting', 'compatible' => true, ), 'tablepress-datatables-alphabetsearch/tablepress-datatables-alphabetsearch.php' => array( 'name' => 'TablePress Extension: DataTables AlphabetSearch', 'compatible' => false, ), 'tablepress-datatables-buttons/tablepress-datatables-buttons.php' => array( 'name' => 'TablePress Extension: DataTables Buttons', 'compatible' => true, ), 'tablepress-datatables-column-filter-widgets/tablepress-datatables-column-filter-widgets.php' => array( 'name' => 'TablePress Extension: DataTables ColumnFilterWidgets', 'compatible' => false, ), 'tablepress-datatables-columnfilter/tablepress-datatables-columnfilter.php' => array( 'name' => 'TablePress Extension: DataTables Column Filter', 'compatible' => false, ), 'tablepress-datatables-counter-column/tablepress-datatables-counter-column.php' => array( 'name' => 'TablePress Extension: DataTables Counter Column', 'compatible' => true, ), 'tablepress-datatables-fixedcolumns/tablepress-datatables-fixedcolumns.php' => array( 'name' => 'TablePress Extension: DataTables FixedColumns', 'compatible' => false, ), 'tablepress-datatables-fixedheader/tablepress-datatables-fixedheader.php' => array( 'name' => 'TablePress Extension: DataTables FixedHeader', 'compatible' => true, ), 'tablepress-datatables-inverted-filter/tablepress-datatables-inverted-filter.php' => array( 'name' => 'TablePress Extension: DataTables Inverted Filter', 'compatible' => false, ), 'tablepress-datatables-row-details/tablepress-datatables-row-details.php' => array( 'name' => 'TablePress Extension: DataTables Row Details', 'compatible' => false, ), 'tablepress-datatables-rowgroup/tablepress-datatables-rowgroup.php' => array( 'name' => 'TablePress Extension: DataTables RowGroup', 'compatible' => false, ), 'tablepress-responsive-tables/tablepress-responsive-tables.php' => array( 'name' => 'TablePress Extension: Responsive Tables', 'compatible' => false, ), 'tablepress-row-filter/tablepress-row-filter.php' => array( 'name' => 'TablePress Extension: Row Filtering', 'compatible' => true, ), 'tablepress-row-highlighting/tablepress-row-highlighting.php' => array( 'name' => 'TablePress Extension: Row Highlighting', 'compatible' => true, ), 'tablepress-table-auto-update/tablepress-table-auto-update.php' => array( 'name' => 'TablePress Extension: Table Auto Update', 'compatible' => true, ), 'tablepress-table-column-order/tablepress-table-column-order.php' => array( 'name' => 'TablePress Extension: Table Column Order', 'compatible' => true, ), ); $active_compatible_superseded_extensions = array(); $active_incompatible_superseded_extensions = array(); foreach ( $superseded_extensions as $plugin => $extension ) { if ( is_plugin_active( $plugin ) ) { if ( $extension['compatible'] ) { $active_compatible_superseded_extensions[] = $extension['name']; } else { $active_incompatible_superseded_extensions[] = $extension['name']; } } } /* * If no superseded extensions are used, hide the message, to prevent running the checks on every load of the view. * This leaves a small risk of an Extension being activated later, but that's acceptable as they are no longer publicly available. */ if ( empty( $active_incompatible_superseded_extensions ) && ( empty( $active_compatible_superseded_extensions ) || ! tb_tp_fs()->is_free_plan() ) ) { TablePress::$model_options->update( 'message_superseded_extensions', false ); } $message = ''; $notice_css_classes = 'not-dismissible'; if ( ! empty( $active_incompatible_superseded_extensions ) ) { $notice_css_classes .= ' is-error'; $message .= '

' . __( 'You are using TablePress Extension plugins on this website that have been retired more than 2 years ago.', 'tablepress' ) . '
' . __( 'For technical reasons, some or all features of these outdated plugins do no longer work with TablePress 3:', 'tablepress' ) . '

'; $message .= '
    '; foreach ( $active_incompatible_superseded_extensions as $extension ) { $message .= '
  • ' . esc_html( $extension ) . '
  • '; } $message .= '
'; $message .= '

' . __( 'Keeping them activated can lead to errors on your website.', 'tablepress' ) . '

'; if ( tb_tp_fs()->is_free_plan() ) { $message .= '

' . sprintf( __( 'To continue using these features, upgrade to a TablePress Premium license plan!', 'tablepress' ), 'https://tablepress.org/upgrade-extensions/?utm_source=plugin&utm_medium=textlink&utm_content=superseded-extensions-message' ) . '
' . __( 'TablePress Pro and TablePress Max come with updated and heavily improved versions of these features and include direct priority email support.', 'tablepress' ) . '

'; } if ( ! empty( $active_compatible_superseded_extensions ) && tb_tp_fs()->is_free_plan() ) { $message .= '

' . __( 'In addition, these TablePress Extension plugins have been retired and might become incompatible in the future as well:', 'tablepress' ) . '

'; $message .= '
    '; foreach ( $active_compatible_superseded_extensions as $extension ) { $message .= '
  • ' . esc_html( $extension ) . '
  • '; } $message .= '
'; } } elseif ( ! empty( $active_compatible_superseded_extensions ) && tb_tp_fs()->is_free_plan() ) { $notice_css_classes .= ' is-warning'; $message .= '

' . __( 'You are using TablePress Extension plugins on this website that have been retired and will no longer receive updates or support:', 'tablepress' ) . '

'; $message .= '
    '; foreach ( $active_compatible_superseded_extensions as $extension ) { $message .= '
  • ' . esc_html( $extension ) . '
  • '; } $message .= '
'; $message .= '

' . __( 'It is possible that these become incompatible with TablePress or WordPress in the future!', 'tablepress' ) . '

'; $message .= '

' . sprintf( __( 'However, their features were heavily improved and are now part of the up-to-date TablePress Premium versions!', 'tablepress' ), 'https://tablepress.org/upgrade-extensions/?utm_source=plugin&utm_medium=textlink&utm_content=superseded-extensions-message' ) . '

'; } if ( '' !== $message ) { if ( tb_tp_fs()->is_free_plan() ) { $message .= '

' . __( 'Upgrade to a TablePress Premium license plan now and get:', 'tablepress' ) . '

'; $message .= '
    '; $message .= '
  • ' . __( 'Updated and heavily improved versions of these features!', 'tablepress' ) . '
  • '; $message .= '
  • ' . __( 'Direct integration into the user interface!', 'tablepress' ) . '
  • '; $message .= '
  • ' . __( 'Regular updates that ensure compatibility with WordPress!', 'tablepress' ) . '
  • '; $message .= '
  • ' . __( 'Priority email support!', 'tablepress' ) . '
  • '; $message .= '
'; $message .= '

' . sprintf( __( 'And the best: %s', 'tablepress' ), sprintf( __( 'Use the promo code %1$s during the checkout process for a special offer!', 'tablepress' ), 'UPGRADE' ) ) . '

'; $message .= '

' . sprintf( '%s', 'https://tablepress.org/upgrade-extensions/?utm_source=plugin&utm_medium=button&utm_content=superseded-extensions-message', __( 'Upgrade to a TablePress Premium version now!', 'tablepress' ) ); } $message .= $this->ajax_link( array( 'action' => 'hide_message', 'item' => 'superseded_extensions', 'return' => 'list' ), __( 'Hide this message', 'tablepress' ) ) . '

'; $title = '' . __( 'Important Notice!', 'tablepress' ) . ''; $this->add_header_message( $message, $notice_css_classes, $title ); } } if ( $data['messages']['first_visit'] ) { $message = '

' . __( 'Thank you for choosing TablePress, the most popular table plugin for WordPress!', 'tablepress' ) . '

'; $message .= '

' . sprintf( __( 'If you encounter any questions or problems, please visit the FAQ, the Documentation, and the Support section on the plugin website.', 'tablepress' ), 'https://tablepress.org/faq/', 'https://tablepress.org/documentation/', 'https://tablepress.org/support/', 'https://tablepress.org/' ) . '

'; if ( tb_tp_fs()->is_free_plan() ) { $message .= '

' . sprintf( __( 'More great features for you and your site’s visitors and priority email support are available with a Premium license plan of TablePress. Go check them out!', 'tablepress' ), 'https://tablepress.org/premium/?utm_source=plugin&utm_medium=textlink&utm_content=first-visit-message' ) . '

'; } $message .= '

' . $this->ajax_link( array( 'action' => 'hide_message', 'item' => 'first_visit', 'return' => 'list' ), __( 'Hide this message', 'tablepress' ) ) . '

'; $title = '' . __( 'Welcome!', 'tablepress' ) . ''; $this->add_header_message( $message, 'is-info not-dismissible', $title ); } if ( $data['messages']['donation_nag'] ) { $message = '

' . esc_attr__( 'Tobias Bäthge, developer of TablePress', 'tablepress' ) . '' . __( 'Hi, my name is Tobias, I’m the developer of the TablePress plugin.', 'tablepress' ) . '

'; $message .= '

' . __( 'Thank you for using it!', 'tablepress' ) . ' '; if ( $data['table_count'] > 0 ) { $message .= sprintf( _n( 'I hope that everything works and that you are satisfied with the results of managing your %s table.', 'I hope that everything works and that you are satisfied with the results of managing your %s tables.', $data['table_count'], 'tablepress' ), $data['table_count'] ); } else { $message .= sprintf( __( 'It looks like you haven’t added a table yet. If you need help to get started, please find more information in the FAQ and Documentation on the TablePress website.', 'tablepress' ), 'https://tablepress.org/' ); } $message .= '

'; $message .= '

' . sprintf( __( 'I would like to invite you to check out the Premium versions of TablePress.', 'tablepress' ), 'https://tablepress.org/premium/?utm_source=plugin&utm_medium=textlink&utm_content=upgrade-message' ) . '
' . __( 'The available Pro and Max plans offer user support and many exciting and helpful features for your tables.', 'tablepress' ) . '

'; $message .= '

' . __( 'Sincerely, Tobias', 'tablepress' ) . '

'; $message .= '

' . sprintf( '%s', 'https://tablepress.org/premium/?utm_source=plugin&utm_medium=button&utm_content=upgrade-message', __( 'Tell me more about the Premium features', 'tablepress' ) ) . $this->ajax_link( array( 'action' => 'hide_message', 'item' => 'donation_nag', 'return' => 'list' ), __( 'Hide this message', 'tablepress' ) ) . '

'; $title = '' . __( 'TablePress has more to offer!', 'tablepress' ) . ''; $this->add_header_message( $message, 'is-success not-dismissible', $title ); } if ( $data['messages']['plugin_update'] ) { $message = '

' . sprintf( __( 'To find out more about what’s new, please read the release announcement.', 'tablepress' ), 'https://tablepress.org/news/?utm_source=plugin&utm_medium=textlink&utm_content=plugin-update-message' ) . '

'; if ( tb_tp_fs()->is_free_plan() ) { $message .= '

' . sprintf( __( 'More great features and priority email support are available with a Premium license plan. Check them out!', 'tablepress' ), 'https://tablepress.org/premium/?utm_source=plugin&utm_medium=textlink&utm_content=plugin-update-message' ) . '

'; } $message .= '

' . $this->ajax_link( array( 'action' => 'hide_message', 'item' => 'plugin_update', 'return' => 'list' ), __( 'Hide this message', 'tablepress' ) ) . '

'; $title = '' . sprintf( __( 'Thank you for updating to TablePress %s!', 'tablepress' ), TablePress::version ) . ''; $this->add_header_message( $message, 'is-info not-dismissible', $title ); } $this->process_action_messages( array( 'success_delete' => _n( 'The table was deleted successfully.', 'The tables were deleted successfully.', 1, 'tablepress' ), 'success_delete_plural' => _n( 'The table was deleted successfully.', 'The tables were deleted successfully.', 2, 'tablepress' ), 'error_delete' => __( 'Error: The table could not be deleted.', 'tablepress' ), 'success_copy' => _n( 'The table was copied successfully.', 'The tables were copied successfully.', 1, 'tablepress' ) . ( ( false !== $data['table_id'] ) ? ' ' . ( current_user_can( 'tablepress_edit_table', $data['table_id'] ) ? sprintf( __( 'You can now edit the copied table, which has the table ID “%2$s”.', 'tablepress' ), esc_url( TablePress::url( array( 'action' => 'edit', 'table_id' => $data['table_id'] ) ) ), $data['table_id'] ) : sprintf( __( 'The copied table has the table ID “%s”.', 'tablepress' ), esc_html( $data['table_id'] ) ) ) : '' ), 'success_copy_plural' => _n( 'The table was copied successfully.', 'The tables were copied successfully.', 2, 'tablepress' ), 'error_copy' => __( 'Error: The table could not be copied.', 'tablepress' ), 'error_no_table' => __( 'Error: You did not specify a valid table ID.', 'tablepress' ), 'error_load_table' => __( 'Error: This table could not be loaded!', 'tablepress' ), 'error_bulk_action_invalid' => __( 'Error: This bulk action is invalid!', 'tablepress' ), 'error_no_selection' => __( 'Error: You did not select any tables!', 'tablepress' ), 'error_delete_not_all_tables' => __( 'Notice: Not all selected tables could be deleted!', 'tablepress' ), 'error_copy_not_all_tables' => __( 'Notice: Not all selected tables could be copied!', 'tablepress' ), 'success_import' => __( 'The tables were imported successfully.', 'tablepress' ), ) ); $this->add_text_box( 'head', array( $this, 'textbox_head' ), 'normal' ); $this->add_text_box( 'tables-list', array( $this, 'textbox_tables_list' ), 'normal' ); add_screen_option( 'per_page', array( 'label' => __( 'Tables', 'tablepress' ), 'default' => 20 ) ); // Admin_Controller contains function to allow changes to this in the Screen Options to be saved. $this->wp_list_table = TablePress::load_class( 'TablePress_All_Tables_List_Table', 'class-all-tables-list-table.php', 'views' ); $this->wp_list_table->set_items( $this->data['table_ids'] ); $this->wp_list_table->prepare_items(); // Cleanup Request URI string, which WP_List_Table uses to generate the sort URLs. $_SERVER['REQUEST_URI'] = remove_query_arg( array( 'message', 'table_id' ), $_SERVER['REQUEST_URI'] ); } /** * Render the current view (in this view: without form tag). * * @since 1.0.0 */ #[\Override] public function render(): void { ?>
print_nav_tab_menu(); ?>

header_messages as $message ) { echo $message; } // For this screen, this is done in textbox_tables_list(), to get the fields into the correct
: // $this->do_text_boxes( 'header' );. ?>
do_text_boxes( 'normal' ); $this->do_meta_boxes( 'normal' ); $this->do_text_boxes( 'additional' ); $this->do_meta_boxes( 'additional' ); // Print all submit buttons. $this->do_text_boxes( 'submit' ); ?>
do_text_boxes( 'side' ); $this->do_meta_boxes( 'side' ); ?>

$data Data for this screen. * @param array $box Information about the text box. */ public function textbox_head( array $data, array $box ): void { echo '

'; _e( 'This is a list of your tables.', 'tablepress' ); echo ' '; // Show the instructions string depending on whether the Block Editor is used on the site or not. if ( 'block' === $data['site_used_editor'] ) { printf( __( 'To insert a table into a post or page, add a “%1$s” block in the block editor and select the desired table.', 'tablepress' ), __( 'TablePress table', 'tablepress' ) ); } elseif ( 'elementor' === $data['site_used_editor'] ) { printf( __( 'To insert a table into a post or page, add a “%1$s” widget in the Elementor editor and select the desired table.', 'tablepress' ), __( 'TablePress table', 'tablepress' ) ); } else { _e( 'To insert a table into a post or page, paste its Shortcode at the desired place in the editor.', 'tablepress' ); echo ' '; _e( 'Each table has a unique ID that needs to be adjusted in that Shortcode.', 'tablepress' ); } echo '

'; } /** * Print the content of the "All Tables" text box. * * @since 1.0.0 * * @param array $data Data for this screen. * @param array $box Information about the text box. */ public function textbox_tables_list( array $data, array $box ): void { if ( ! empty( $_GET['s'] ) ) { printf( '' . __( 'Search results for “%s”', 'tablepress' ) . '', esc_html( wp_unslash( $_GET['s'] ) ) ); } ?> ' . "\n"; } $this->wp_list_table->search_box( __( 'Search Tables', 'tablepress' ), 'tables_search' ); ?>
). $this->do_text_boxes( 'header' ); $this->wp_list_table->display(); ?>
$params Parameters for the URL. * @param string $text Text for the link. * @return string HTML code for the link. */ protected function ajax_link( array $params, string $text ): string { $class = 'ajax-link'; if ( ! empty( $params['class'] ) ) { $class .= ' ' . esc_attr( $params['class'] ); } $url = TablePress::url( $params, true, 'admin-post.php' ); $action = esc_attr( $params['action'] ); $item = esc_attr( $params['item'] ); $target = esc_attr( $params['target'] ?? '' ); return "{$text}"; } } // class TablePress_List_View PK!G%U!views/view-options_custom_css.phpnu[ $data Data for this view. */ #[\Override] public function setup( /* string */ $action, array $data ) /* : void */ { // Don't use type hints in the method declaration to prevent PHP errors, as the method is inherited. // Set action manually here, to get correct page title and nav bar entries. $this->action = 'options'; $this->data = $data; // Set page title. $GLOBALS['title'] = sprintf( __( '%1$s ‹ %2$s', 'tablepress' ), $this->data['view_actions'][ $this->action ]['page_title'], 'TablePress' ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited $this->add_header_message( '' . __( 'Attention: Further action is required to save the changes to your “Custom CSS”!', 'tablepress' ) . '', 'is-success' ); // Admin page helpers, like script/style loading, could be moved to view. $this->admin_page = TablePress::load_class( 'TablePress_Admin_Page', 'class-admin-page-helper.php', 'classes' ); $this->admin_page->enqueue_style( 'common', array( 'wp-components' ) ); $this->admin_page->add_admin_footer_text(); $this->add_text_box( 'explanation-text', array( $this, 'textbox_explanation_text' ), 'normal' ); $this->add_text_box( 'credentials-form', array( $this, 'textbox_credentials_form' ), 'normal' ); $this->add_text_box( 'proceed-no-file-saving', array( $this, 'textbox_proceed_no_file_saving' ), 'submit' ); } /** * Render the current view (in this view: without form tag). * * @since 1.0.0 */ #[\Override] public function render(): void { ?>
print_nav_tab_menu(); ?>

header_messages as $message ) { echo $message; } $this->do_text_boxes( 'header' ); ?>
do_text_boxes( 'normal' ); $this->do_meta_boxes( 'normal' ); $this->do_text_boxes( 'additional' ); $this->do_meta_boxes( 'additional' ); // Print all submit buttons. $this->do_text_boxes( 'submit' ); ?>
do_text_boxes( 'side' ); $this->do_meta_boxes( 'side' ); ?>

$data Data for this screen. * @param array $box Information about the text box. */ public function textbox_explanation_text( array $data, array $box ): void { ?>

$data Data for this screen. * @param array $box Information about the text box. */ public function textbox_credentials_form( array $data, array $box ): void { echo $data['credentials_form']; } /** * Print the content of the "Cancel Saving" text box. * * @since 1.0.0 * * @param array $data Data for this screen. * @param array $box Information about the text box. */ public function textbox_proceed_no_file_saving( array $data, array $box ): void { ?>

$data Data for this view. */ #[\Override] public function setup( /* string */ $action, array $data ) /* : void */ { // Don't use type hints in the method declaration to prevent PHP errors, as the method is inherited. $this->action = $action; $this->data = $data; $this->wp_list_table = TablePress::load_class( 'TablePress_Editor_Button_Thickbox_List_Table', 'class-editor-button-thickbox-list-table.php', 'views' ); $this->wp_list_table->set_items( $this->data['table_ids'] ); $this->wp_list_table->prepare_items(); } /** * Renders the current view. * * @since 1.0.0 */ #[\Override] public function render(): void { _wp_admin_html_begin(); wp_print_styles( 'colors' ); wp_print_scripts( 'jquery-core' ); ?> <?php printf( __( '%1$s ‹ %2$s', 'tablepress' ), __( 'List of Tables', 'tablepress' ), 'TablePress' ); ?>

' . __( 'Search results for “%s”', 'tablepress' ) . '', esc_html( wp_unslash( $_GET['s'] ) ) ); } ?>
action ), '_wpnonce', false ); $this->wp_list_table->search_box( __( 'Search Tables', 'tablepress' ), 'tables_search' ); ?>
wp_list_table->display(); ?>
$data Data for this view. */ #[\Override] public function setup( /* string */ $action, array $data ) /* : void */ { // Don't use type hints in the method declaration to prevent PHP errors, as the method is inherited. parent::setup( $action, $data ); $this->add_text_box( 'spacer', array( $this, 'textbox_spacer' ), 'normal' ); $this->add_text_box( 'spacer', array( $this, 'textbox_spacer' ), 'side' ); $this->add_meta_box( 'plugin-purpose', __( 'Plugin Purpose', 'tablepress' ), array( $this, 'postbox_plugin_purpose' ), 'normal' ); $this->add_meta_box( 'usage', __( 'Usage', 'tablepress' ), array( $this, 'postbox_usage' ), 'normal' ); $this->add_meta_box( 'more-information', __( 'More Information and Documentation', 'tablepress' ), array( $this, 'postbox_more_information' ), 'normal' ); $this->add_meta_box( 'help-support', __( 'Help and Support', 'tablepress' ), array( $this, 'postbox_help_support' ), 'normal' ); $this->add_meta_box( 'author-license', __( 'Author and License', 'tablepress' ), array( $this, 'postbox_author_license' ), 'side' ); $this->add_meta_box( 'credits-thanks', __( 'Credits and Thanks', 'tablepress' ), array( $this, 'postbox_credits_thanks' ), 'side' ); $this->add_meta_box( 'debug-version-information', __( 'Debug and Version Information', 'tablepress' ), array( $this, 'postbox_debug_version_information' ), 'side' ); } /** * Prints the screen head spacer. * * @since 2.0.0 * * @param array $data Data for this screen. * @param array $box Information about the text box. */ public function textbox_spacer( array $data, array $box ): void { echo '

'; } /** * Print the content of the "Plugin Purpose" post meta box. * * @since 1.0.0 * * @param array $data Data for this screen. * @param array $box Information about the meta box. */ public function postbox_plugin_purpose( array $data, array $box ): void { ?>

$data Data for this screen. * @param array $box Information about the meta box. */ public function postbox_usage( array $data, array $box ): void { ?>

Documentation for a list of these selectors and for styling examples.', 'tablepress' ), 'https://tablepress.org/documentation/' ); ?>

$data Data for this screen. * @param array $box Information about the meta box. */ public function postbox_more_information( array $data, array $box ): void { ?>

plugin website or on its page in the WordPress Plugin Directory.', 'tablepress' ), 'https://tablepress.org/', 'https://wordpress.org/plugins/tablepress/' ); ?> Documentation.', 'tablepress' ), 'https://tablepress.org/documentation/' ); ?> FAQ.', 'tablepress' ), 'https://tablepress.org/faq/' ); ?>

$data Data for this screen. * @param array $box Information about the meta box. */ public function postbox_author_license( array $data, array $box ): void { ?>

Tobias Bäthge.', 'tablepress' ), 'https://tobias.baethge.com/' ); ?>

WordPress Plugin Directory.', 'tablepress' ), 'https://wordpress.org/support/view/plugin-reviews/tablepress' ); ?>

is_free_plan() ) { echo '

' . sprintf( __( 'I would like to invite you to check out the Premium versions of TablePress.', 'tablepress' ), 'https://tablepress.org/premium/?utm_source=plugin&utm_medium=textlink&utm_content=about-screen' ) . ' ' . __( 'The available Pro and Max plans offer user support and many exciting and helpful features for your tables.', 'tablepress' ) . '

'; } ?> $data Data for this screen. * @param array $box Information about the meta box. */ public function postbox_help_support( array $data, array $box ): void { if ( tb_tp_fs()->is_free_plan() ) { ?>

Find out more!', 'tablepress' ), 'https://tablepress.org/premium/?utm_source=plugin&utm_medium=textlink&utm_content=about-screen' ); ?>

Support is provided through the WordPress Support Forums.', 'tablepress' ), 'https://tablepress.org/support/', 'https://wordpress.org/support/plugin/tablepress' ); ?> Frequently Asked Questions, where you will find answers to the most common questions, and search through the forums.', 'tablepress' ), 'https://tablepress.org/faq/' ); ?> open a new thread in the WordPress Support Forums.', 'tablepress' ), 'https://wordpress.org/support/plugin/tablepress' ); ?>

$data Data for this screen. * @param array $box Information about the meta box. */ public function postbox_debug_version_information( array $data, array $box ): void { ?>

  • Website:
  • Block Theme:
  • TablePress:
  • TablePress (DB):
  • TablePress table scheme:
  • Plan: Free
  • Plugin installed:
  • WordPress:
  • Multisite:
  • PHP:
  • mySQL (Server): dbh ) && function_exists( 'mysqli_get_server_info' ) ) ? mysqli_get_server_info( $GLOBALS['wpdb']->dbh ) : 'no mySQL server'; // phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysqli_get_server_info ?>
  • mySQL (Client):
  • mbstring: no'; ?>
  • ZipArchive: no'; ?>
  • DOMDocument: no'; ?>
  • simplexml_load_string: no'; ?>
  • libxml_disable_entity_loader: no'; ?>
  • UTF-8 conversion: no'; ?>
  • WP Memory Limit:
  • Server Memory Limit:
  • WP_DEBUG:
  • WP_POST_REVISIONS:
$data Data for this screen. * @param array $box Information about the meta box. */ public function postbox_credits_thanks( array $data, array $box ): void { ?>

  • DataTables,', 'tablepress' ), 'https://datatables.net/' ); ?>
  • Plugin Directory,', 'tablepress' ), 'https://translate.wordpress.org/projects/wp-plugins/tablepress/' ); ?>
$data Data for this view. */ #[\Override] public function setup( /* string */ $action, array $data ) /* : void */ { // Don't use type hints in the method declaration to prevent PHP errors, as the method is inherited. parent::setup( $action, $data ); $this->add_text_box( 'no-javascript', array( $this, 'textbox_no_javascript' ), 'header' ); $this->process_action_messages( array( 'error_export' => __( 'Error: The export failed.', 'tablepress' ), 'error_load_table' => __( 'Error: This table could not be loaded!', 'tablepress' ), 'error_table_corrupted' => __( 'Error: The internal data of this table is corrupted!', 'tablepress' ), 'error_create_zip_file' => __( 'Error: The ZIP file could not be created.', 'tablepress' ), ) ); $this->add_text_box( 'head', array( $this, 'textbox_head' ), 'normal' ); if ( 0 === $data['tables_count'] ) { $this->add_meta_box( 'no-tables', __( 'Export Tables', 'tablepress' ), array( $this, 'postbox_no_tables' ), 'normal' ); } else { $this->admin_page->enqueue_script( 'export' ); $this->add_meta_box( 'export-form', __( 'Export Tables', 'tablepress' ), array( $this, 'postbox_export_form' ), 'normal' ); } } /** * Prints the screen head text. * * @since 1.0.0 * * @param array $data Data for this screen. * @param array $box Information about the text box. */ public function textbox_head( array $data, array $box ): void { ?>


$data Data for this screen. * @param array $box Information about the meta box. */ public function postbox_no_tables( array $data, array $box ): void { $add_url = TablePress::url( array( 'action' => 'add' ) ); $import_url = TablePress::url( array( 'action' => 'import' ) ); ?>

add or import a table to get started!', 'tablepress' ), $add_url, $import_url ); ?>

$data Data for this screen. * @param array $box Information about the meta box. */ public function postbox_export_form( array $data, array $box ): void { $this->print_script_data_json( 'export', array( 'tables' => $data['tables'], 'exportFormats' => $data['export_formats'], 'csvDelimiters' => $data['csv_delimiters'], 'zipSupportAvailable' => $data['zip_support_available'], 'selectedTables' => $data['export_ids'], 'exportFormat' => $data['export_format'], 'csvDelimiter' => $data['csv_delimiter'], ), ); echo '
'; } } // class TablePress_Export_View PK!Gc))views/view-options.phpnu[ $data Data for this view. */ #[\Override] public function setup( /* string */ $action, array $data ) /* : void */ { // Don't use type hints in the method declaration to prevent PHP errors, as the method is inherited. parent::setup( $action, $data ); $this->admin_page->enqueue_script( 'options' ); $this->process_action_messages( array( 'success_save' => __( 'Options saved successfully.', 'tablepress' ), 'success_save_error_custom_css' => __( 'Options saved successfully, but “Custom CSS” was not saved to file.', 'tablepress' ), 'error_save' => __( 'Error: Options could not be saved.', 'tablepress' ), ) ); $this->add_text_box( 'head', array( $this, 'textbox_head' ), 'normal' ); if ( current_user_can( 'tablepress_edit_options' ) ) { // Enqueue WordPress copy of CodeMirror, with CSS linting, etc., for the "Custom CSS" textarea, which is only shown to admins. $codemirror_settings = wp_enqueue_code_editor( array( 'type' => 'text/css' ) ); if ( ! empty( $codemirror_settings ) ) { // Load CSS adjustments for CodeMirror and the added vertical resizing. $this->admin_page->enqueue_style( 'codemirror', array( 'code-editor' ) ); $this->admin_page->enqueue_script( 'codemirror' ); } if ( ! TABLEPRESS_IS_PLAYGROUND_PREVIEW ) { $this->add_meta_box( 'default-style', sprintf( __( 'Default Styling %s', 'tablepress' ), tb_tp_fs()->is_free_plan() ? '' . __( 'Premium', 'tablepress' ) . '' : '' ), array( $this, 'postbox_default_style_customizer_screen' ), 'normal' ); } $this->add_meta_box( 'frontend-options', __( 'Custom Styling', 'tablepress' ), array( $this, 'postbox_frontend_options' ), 'normal' ); } $this->add_meta_box( 'user-options', __( 'User Options', 'tablepress' ), array( $this, 'postbox_user_options' ), 'normal' ); $this->add_text_box( 'submit', array( $this, 'textbox_submit_button' ), 'submit' ); if ( current_user_can( 'deactivate_plugin', TABLEPRESS_BASENAME ) && current_user_can( 'tablepress_edit_options' ) && current_user_can( 'tablepress_delete_tables' ) && ! is_plugin_active_for_network( TABLEPRESS_BASENAME ) ) { $this->add_text_box( 'uninstall-tablepress', array( $this, 'textbox_uninstall_tablepress' ), 'submit' ); } } /** * Prints the screen head text. * * @since 1.0.0 * * @param array $data Data for this screen. * @param array $box Information about the text box. */ public function textbox_head( array $data, array $box ): void { ?>

$data Data for this screen. * @param array $box Information about the meta box. */ public function postbox_default_style_customizer_screen( array $data, array $box ): void { if ( tb_tp_fs()->is_free_plan() ) : ?> $data Data for this screen. * @param array $box Information about the meta box. */ public function postbox_frontend_options( array $data, array $box ): void { ?>
:

Cascading Style Sheets) can be used to change the styling or layout of a table.', 'tablepress' ), 'https://www.htmldog.com/guides/css/beginner/' ); echo ' '; printf( __( 'You can get styling examples from the FAQ.', 'tablepress' ), 'https://tablepress.org/faq/' ); echo ' '; printf( __( 'Information on available CSS selectors can be found in the Documentation.', 'tablepress' ), 'https://tablepress.org/documentation/' ); echo ' '; _e( 'Please note that invalid CSS code will be stripped, if it can not be corrected automatically.', 'tablepress' ); ?>

$data Data for this screen. * @param array $box Information about the meta box. */ public function postbox_user_options( array $data, array $box ): void { // Get list of current admin menu entries. $entries = array(); foreach ( $GLOBALS['menu'] as $entry ) { if ( str_contains( $entry[2], '.php' ) ) { $entries[ $entry[2] ] = $entry[0]; } } // Remove elements with notification bubbles (e.g. update or comment count). if ( isset( $entries['plugins.php'] ) ) { $entries['plugins.php'] = preg_replace( '/ /', '', $entries['plugins.php'] ); } if ( isset( $entries['edit-comments.php'] ) ) { $entries['edit-comments.php'] = preg_replace( '/ /', '', $entries['edit-comments.php'] ); } // Add separator and generic positions. $entries['-'] = '---'; $entries['top'] = __( 'Top-Level (top)', 'tablepress' ); $entries['middle'] = __( 'Top-Level (middle)', 'tablepress' ); $entries['bottom'] = __( 'Top-Level (bottom)', 'tablepress' ); $select_box = '\n"; ?>
$data Data for this screen. * @param array $box Information about the text box. */ #[\Override] public function textbox_submit_button( array $data, array $box ): void { ?>

$data Data for this screen. * @param array $box Information about the text box. */ public function textbox_uninstall_tablepress( array $data, array $box ): void { ?>

will permanently delete all TablePress tables and options from the database.', 'tablepress' ) . '
' . __( 'It is recommended that you create a backup of the tables (by exporting the tables in the JSON format), in case you later change your mind.', 'tablepress' ) . '
' . __( 'You will manually need to remove the plugin’s files from the plugin folder afterwards.', 'tablepress' ) . '
' . __( 'Be very careful with this and only click the button if you know what you are doing!', 'tablepress' ); ?>

'tablepress-table', // Singular name of the listed records. 'plural' => 'tablepress-editor-button-list', // Plural name of the listed records. 'ajax' => false, // Does this list table support AJAX? 'screen' => get_current_screen(), // WP_Screen object. ) ); } /** * Sets the data items (here: tables) that are to be displayed by the List Tables, and their original count. * * @since 1.0.0 * * @param string[] $items Tables to be displayed in the List Table. */ public function set_items( array $items ): void { $this->items = $items; $this->items_count = count( $items ); } /** * Checks whether the user has permissions for certain AJAX actions. * (not used, but must be implemented in this child class) * * @since 1.0.0 * * @return bool true (Default value). */ #[\Override] public function ajax_user_can(): bool { return true; } /** * Gets a list of columns in this List Table. * * Format: 'internal-name' => 'Column Title'. * * @since 1.0.0 * * @return array List of columns in this List Table. */ #[\Override] public function get_columns(): array { $columns = array( // "name" is special in WP, which is why we prefix every entry here, to be safe! 'table_id' => __( 'ID', 'tablepress' ), 'table_name' => __( 'Table Name', 'tablepress' ), 'table_description' => __( 'Description', 'tablepress' ), 'table_action' => __( 'Action', 'tablepress' ), ); return $columns; } /** * Gets a list of columns that are sortable. * * Format: 'internal-name' => array( $field for $item[ $field ], true for already sorted ). * * @since 1.0.0 * * @return array List of sortable columns in this List Table. */ #[\Override] protected function get_sortable_columns(): array { // No sorting on the Empty List placeholder. if ( ! $this->has_items() ) { return array(); } $sortable_columns = array( 'table_id' => array( 'id', true ), // true means its already sorted. 'table_name' => array( 'name', false ), 'table_description' => array( 'description', false ), ); return $sortable_columns; } /** * Gets the name of the default primary column. * * @since 1.7.0 * * @return string Name of the default primary column, in this case, the table name. */ #[\Override] protected function get_default_primary_column_name(): string { return 'table_name'; } /** * Renders a cell in the "table_id" column. * * @since 1.0.0 * * @param array $item Data item for the current row. * @return string HTML content of the cell. */ protected function column_table_id( array $item ): string { return esc_html( $item['id'] ); } /** * Renders a cell in the "table_name" column. * * @since 1.0.0 * * @param array $item Data item for the current row. * @return string HTML content of the cell. */ protected function column_table_name( array $item ): string { if ( '' === trim( $item['name'] ) ) { $item['name'] = __( '(no name)', 'tablepress' ); } return esc_html( $item['name'] ); } /** * Renders a cell in the "table_description" column. * * @since 1.0.0 * * @param array $item Data item for the current row. * @return string HTML content of the cell. */ protected function column_table_description( array $item ): string { if ( '' === trim( $item['description'] ) ) { $item['description'] = __( '(no description)', 'tablepress' ); } return esc_html( $item['description'] ); } /** * Renders a cell in the "table_action" column, i.e. the "Insert" link. * * @since 1.0.0 * * @param array $item Data item for the current row. * @return string HTML content of the cell. */ protected function column_table_action( array $item ): string { return ''; } /** * Holds the message to be displayed when there are no items in the table. * * @since 1.0.0 */ #[\Override] public function no_items(): void { _e( 'No tables found.', 'tablepress' ); if ( 0 === $this->items_count ) { echo ' ' . __( 'You should add or import a table on the TablePress screens to get started!', 'tablepress' ); } } /** * Generates the elements above or below the table (like bulk actions and pagination). * * In comparison with parent class, this has modified HTML (no nonce field), and a check whether there are items. * * @since 1.0.0 * * @param 'top'|'bottom' $which Location ("top" or "bottom"). */ #[\Override] protected function display_tablenav( /* string */ $which ): void { // Don't use type hints in the method declaration to prevent PHP errors, as the method is inherited. if ( ! $this->has_items() ) { return; } ?>
bulk_actions( $which ); ?>
extra_tablenav( $which ); add_filter( 'ngettext_default', array( $this, 'change_pagination_items_string' ), 10, 5 ); $this->pagination( $which ); remove_filter( 'ngettext_default', array( $this, 'change_pagination_items_string' ), 10 ); ?>
load( $item, true, false ); if ( is_wp_error( $item ) ) { return false; } // Don't search corrupted tables. if ( isset( $item['is_corrupted'] ) && $item['is_corrupted'] ) { return false; } // Search from easy to hard, so that "expensive" code maybe doesn't have to run. if ( false !== stripos( $item['id'], (string) $term ) || false !== stripos( $item['name'], (string) $term ) || false !== stripos( $item['description'], (string) $term ) || false !== stripos( TablePress::get_user_display_name( $item['author'] ), (string) $term ) || false !== stripos( TablePress::format_datetime( $item['last_modified'] ), (string) $term ) || false !== stripos( wp_json_encode( $item['data'], TABLEPRESS_JSON_OPTIONS ), (string) $json_encoded_term ) ) { // @phpstan-ignore argument.type return true; } return false; } /** * Callback to for the array sort function. * * @since 1.0.0 * * @param array $item_a First item that shall be compared to. * @param array $item_b The second item for the comparison. * @return int (-1, 0, 1) depending on which item sorts "higher". */ protected function _order_callback( array $item_a, array $item_b ): int { global $orderby, $order; if ( $item_a[ $orderby ] === $item_b[ $orderby ] ) { return 0; } // Fields in this list table are all strings. $result = strnatcasecmp( $item_a[ $orderby ], $item_b[ $orderby ] ); return ( 'asc' === $order ) ? $result : - $result; } /** * Prepares the list of items for displaying, by maybe searching and sorting, and by doing pagination. * * @since 1.0.0 */ #[\Override] public function prepare_items(): void { global $orderby, $order, $s; wp_reset_vars( array( 'orderby', 'order', 's' ) ); // Maybe search in the items. if ( $s ) { $this->items = array_filter( $this->items, array( $this, '_search_callback' ) ); } // Load actual tables after search for less memory consumption. foreach ( $this->items as &$item ) { // Don't load data nor table options. $item = TablePress::$model_table->load( $item, false, false ); } unset( $item ); // Unset use-by-reference parameter of foreach loop. // Maybe sort the items. $_sortable_columns = $this->get_sortable_columns(); if ( $orderby && ! empty( $this->items ) && isset( $_sortable_columns[ "table_{$orderby}" ] ) ) { usort( $this->items, array( $this, '_order_callback' ) ); } // Number of records to show per page. $per_page = 20; // Hard-coded, as there's no possibility to change this in the Thickbox. // Page number the user is currently viewing. $current_page = $this->get_pagenum(); // Number of records in the array. $total_items = count( $this->items ); // Slice items array to hold only items for the current page. $this->items = array_slice( $this->items, ( ( $current_page - 1 ) * $per_page ), $per_page ); // Register pagination options and calculation results. $this->set_pagination_args( array( 'total_items' => $total_items, // Total number of records/items. 'per_page' => $per_page, // Number of items per page. 'total_pages' => (int) ceil( $total_items / $per_page ), // Total number of pages. ) ); } } // class TablePress_Editor_Button_Thickbox_List_Table PK! views/view-preview_table.phpnu[ $data Data for this view. */ #[\Override] public function setup( /* string */ $action, array $data ) /* : void */ { // Don't use type hints in the method declaration to prevent PHP errors, as the method is inherited. $this->action = $action; $this->data = $data; } /** * Render the current view. * * @since 1.0.0 */ #[\Override] public function render(): void { _wp_admin_html_begin(); ?> <?php printf( __( '%1$s ‹ %2$s', 'tablepress' ), __( 'Preview', 'tablepress' ), 'TablePress' ); ?> data['head_html']; ?>


data['site_used_editor'] ) { printf( __( 'To insert a table into a post or page, add a “%1$s” block in the block editor and select the desired table.', 'tablepress' ), __( 'TablePress table', 'tablepress' ) ); } elseif ( 'elementor' === $this->data['site_used_editor'] ) { printf( __( 'To insert a table into a post or page, add a “%1$s” widget in the Elementor editor and select the desired table.', 'tablepress' ), __( 'TablePress table', 'tablepress' ) ); } else { _e( 'To insert a table into a post or page, paste its Shortcode at the desired place in the editor.', 'tablepress' ); echo ' '; _e( 'Each table has a unique ID that needs to be adjusted in that Shortcode.', 'tablepress' ); } ?>

data['body_html']; ?>
$data Data for this view. */ #[\Override] public function setup( /* string */ $action, array $data ) /* : void */ { // Don't use type hints in the method declaration to prevent PHP errors, as the method is inherited. parent::setup( $action, $data ); $this->add_text_box( 'no-javascript', array( $this, 'textbox_no_javascript' ), 'header' ); $this->admin_page->enqueue_style( 'import' ); $this->admin_page->enqueue_script( 'import' ); $this->process_action_messages( array( 'error_import' => __( 'Error: The import failed.', 'tablepress' ), ) ); $this->add_text_box( 'head', array( $this, 'textbox_head' ), 'normal' ); $this->add_meta_box( 'import-form', __( 'Import Tables', 'tablepress' ), array( $this, 'postbox_import_form' ), 'normal' ); $screen = get_current_screen(); add_filter( "postbox_classes_{$screen->id}_tablepress_{$this->action}-import-form", array( $this, 'postbox_classes' ) ); // @phpstan-ignore property.nonObject if ( ! TABLEPRESS_IS_PLAYGROUND_PREVIEW ) { $this->add_meta_box( 'tables-auto-import', __( 'Automatic Periodic Table Import', 'tablepress' ), array( $this, 'postbox_auto_import' ), 'additional' ); } } /** * Print the screen head text. * * @since 1.0.0 * * @param array $data Data for this screen. * @param array $box Information about the text box. */ public function textbox_head( array $data, array $box ): void { ?>

$data Data for this screen. * @param array $box Information about the meta box. */ public function postbox_import_form( array $data, array $box ): void { $this->print_script_data_json( 'import', array( 'tables' => $data['tables'], 'importSource' => $data['import_source'], 'importType' => $data['import_type'], 'importUrl' => esc_url( $data['import_url'] ), 'importServer' => $data['import_server'], 'importFormField' => $data['import_form-field'], 'importExistingTable' => $data['import_existing_table'], 'showImportSourceServer' => ( ( ! is_multisite() && current_user_can( 'manage_options' ) ) || is_super_admin() ), 'showImportSourceUrl' => current_user_can( 'tablepress_import_tables_url' ), 'legacyImport' => $data['legacy_import'], ), ); echo '
'; } /** * Adds the "no-validation-highlighting" class to the "Import Tables" post meta box. * * @since 2.2.0 * * @param string[] $classes The array of postbox classes. * @return string[] The modified array of postbox classes. */ public function postbox_classes( array $classes ): array { $classes[] = 'no-validation-highlighting'; return $classes; } /** * Prints the content of the "Automatic Periodic Table Import Screen" post meta box. * * @since 2.2.0 * * @param array $data Data for this screen. * @param array $box Information about the meta box. * * @phpstan-ignore missingType.return (The method is extended elsewhere and can't have type hints.) */ public function postbox_auto_import( /* array */ $data, /* array */ $box ) /* : void */ { // Don't use type hints in the method declaration, as the method is extended in the TablePress Table Auto Update Extension which is no longer updated. if ( tb_tp_fs()->is_free_plan() ) : ?>

“%2$s” premium feature!', 'tablepress' ), 'https://tablepress.org/modules/automatic-periodic-table-import/?utm_source=plugin&utm_medium=textlink&utm_content=import-screen', __( 'Automatic Periodic Table Import', 'tablepress' ) ); ?>

' . __( 'TablePress feature: Drag and Drop Import with Format Detection', 'tablepress' ) . ''; $content .= '

' . __( 'Did you know?', 'tablepress' ) . ' ' . __( 'To import tables, you can simply drag and drop your spreadsheet files into this area and TablePress will automatically detect the file format!', 'tablepress' ) . '

'; $this->admin_page->print_wp_pointer_js( 'tp20_import_drag_drop_detect_format', '#tables-import-file-upload-dropzone span', array( 'content' => $content, 'position' => array( 'edge' => 'bottom', 'align' => 'center' ), ), ); } } // class TablePress_Import_View PK!\$$#controllers/controller-frontend.phpnu[>}> */ protected array $shown_tables = array(); /** * List of registered DataTables datetime formats. * * @since 3.0.0 * @var string[] */ protected array $datatables_datetime_formats = array(); /** * Initiates Frontend functionality. * * @since 1.0.0 */ public function __construct() { parent::__construct(); /** * Filters the admin menu parent page, which is needed for the construction of plugin URLs. * * @since 1.0.0 * * @param string $parent_page Current admin menu parent page. */ $this->parent_page = apply_filters( 'tablepress_admin_menu_parent_page', TablePress::$model_options->get( 'admin_menu_parent_page' ) ); $this->is_top_level_page = in_array( $this->parent_page, array( 'top', 'middle', 'bottom' ), true ); /** * Filters whether TablePress should load its frontend CSS files on all pages. * For block themes, the default behavior is to only load the CSS files when a table is encountered on the page. * If Elementor is active, the CSS is also loaded on the editor page. * * @since 3.0.1 * * @param bool $use_legacy_css_loading Whether TablePress should load its frontend CSS files on all pages. */ $this->use_legacy_css_loading = apply_filters( 'tablepress_frontend_legacy_css_loading', ! wp_is_block_theme() || ( isset( $_GET['elementor-preview'] ) && is_plugin_active( 'elementor/elementor.php' ) ) ); if ( $this->use_legacy_css_loading ) { add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_css' ) ); } add_action( 'wp_print_footer_scripts', array( $this, 'add_datatables_calls' ), 9 ); // Priority 9 so that this runs before `_wp_footer_scripts()`. // Register TablePress Shortcodes. Priority 20 is kept for backwards-compatibility purposes. add_action( 'init', array( $this, 'init_shortcodes' ), 20 ); /** * Filters whether the WordPress search shall also search TablePress tables. * * @since 1.0.0 * * @param bool $search Whether the TablePress tables shall be searched. Default true. */ if ( apply_filters( 'tablepress_wp_search_integration', true ) ) { // Extend WordPress Search to also find posts/pages that have a table with the one of the search terms in title (if shown), description (if shown), or content. add_filter( 'posts_search', array( $this, 'posts_search_filter' ) ); } /** * Load TablePress Template Tag functions. */ TablePress::load_file( 'template-tag-functions.php', 'controllers' ); /** * Register the tablepress/table block and its dependencies. */ if ( function_exists( 'wp_register_block_metadata_collection' ) ) { // wp_register_block_metadata_collection() is only available since WP 6.7. wp_register_block_metadata_collection( TABLEPRESS_ABSPATH . 'blocks', TABLEPRESS_ABSPATH . 'blocks/blocks-manifest.php', ); } register_block_type_from_metadata( TABLEPRESS_ABSPATH . 'blocks/table/block.json', array( 'render_callback' => array( $this, 'table_block_render_callback' ), ), ); /** * Register the TablePress Elementor widgets. */ add_action( 'elementor/widgets/register', array( $this, 'register_elementor_widgets' ) ); add_action( 'elementor/editor/after_enqueue_styles', array( $this, 'enqueue_elementor_editor_styles' ), 10, 0 ); } /** * Registers TablePress Shortcodes. * * @since 1.0.0 */ public function init_shortcodes(): void { add_shortcode( TablePress::$shortcode, array( $this, 'shortcode_table' ) ); add_shortcode( TablePress::$shortcode_info, array( $this, 'shortcode_table_info' ) ); } /** * Checks if the CSS files for TablePress default CSS and "Custom CSS" should be loaded. * * This function is only called when a [table /] Shortcode or "TablePress Table" block is evaluated, so that CSS files are only loaded when needed. * * @since 3.0.0 */ public function maybe_enqueue_css(): void { // Bail early if the legacy CSS loading mechanism is used, as the files will then have been enqueued already. if ( $this->use_legacy_css_loading && ! doing_action( 'enqueue_block_assets' ) ) { return; } /* * Bail early if the function is called from some action hook outside of the normal rendering process. * These are often used by e.g. SEO plugins that render the content in additional contexts, e.g. to get an excerpt via an output buffer. * In these cases, we don't want to enqueue the CSS, as it would likely not be printed on the page. */ if ( doing_action( 'wp_head' ) || doing_action( 'wp_footer' ) ) { return; } // Prevent repeated execution via a static variable. static $css_enqueued = false; if ( $css_enqueued && ! doing_action( 'enqueue_block_assets' ) ) { return; } $css_enqueued = true; $this->enqueue_css(); } /** * Enqueues CSS files for TablePress default CSS and "Custom CSS" (if desired). * * If styles have not been printed to the page (in the ``), the TablePress CSS files will be enqueued. * If styles have already been printed to the page, the TablePress CSS files will be printed right away (likely in the `). * * @since 1.0.0 */ public function enqueue_css(): void { /** * Filters whether the TablePress Default CSS code shall be loaded. * * @since 1.0.0 * * @param bool $use Whether the Default CSS shall be loaded. Default true. */ $use_default_css = apply_filters( 'tablepress_use_default_css', true ); $use_custom_css = TablePress::$model_options->get( 'use_custom_css' ); if ( ! $use_default_css && ! $use_custom_css ) { // Register a placeholder dependency, so that the handle is known for other styles. wp_register_style( 'tablepress-default', false ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion return; } $custom_css = TablePress::$model_options->get( 'custom_css' ); $use_custom_css = $use_custom_css && '' !== $custom_css; $use_custom_css_file = $use_custom_css && TablePress::$model_options->get( 'use_custom_css_file' ); /** * Filters the "Custom CSS" version number that is appended to the enqueued CSS files * * @since 1.0.0 * * @param int $version The "Custom CSS" version. */ $custom_css_version = (string) apply_filters( 'tablepress_custom_css_version', TablePress::$model_options->get( 'custom_css_version' ) ); $tablepress_css = TablePress::load_class( 'TablePress_CSS', 'class-css.php', 'classes' ); // Determine Default CSS URL. $rtl = ( is_rtl() ) ? '-rtl' : ''; $unfiltered_default_css_url = plugins_url( "css/build/default{$rtl}.css", TABLEPRESS__FILE__ ); /** * Filters the URL from which the TablePress Default CSS file is loaded. * * @since 1.0.0 * * @param string $unfiltered_default_css_url URL of the TablePress Default CSS file. */ $default_css_url = apply_filters( 'tablepress_default_css_url', $unfiltered_default_css_url ); $use_custom_css_combined_file = ( $use_default_css && $use_custom_css_file && ! SCRIPT_DEBUG && ! is_rtl() && $unfiltered_default_css_url === $default_css_url && $tablepress_css->load_custom_css_from_file( 'combined' ) ); if ( $use_custom_css_combined_file ) { $custom_css_combined_url = $tablepress_css->get_custom_css_location( 'combined', 'url' ); // Need to use 'tablepress-default' instead of 'tablepress-combined' to not break existing TablePress Extensions. wp_enqueue_style( 'tablepress-default', $custom_css_combined_url, array(), $custom_css_version ); if ( did_action( 'wp_print_styles' ) ) { wp_print_styles( 'tablepress-default' ); } return; } if ( $use_default_css ) { wp_enqueue_style( 'tablepress-default', $default_css_url, array(), TablePress::version ); } else { // Register a placeholder dependency, so that the handle is known for other styles. wp_register_style( 'tablepress-default', false ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion } $use_custom_css_minified_file = ( $use_custom_css_file && ! SCRIPT_DEBUG && $tablepress_css->load_custom_css_from_file( 'minified' ) ); if ( $use_custom_css_minified_file ) { $custom_css_minified_url = $tablepress_css->get_custom_css_location( 'minified', 'url' ); wp_enqueue_style( 'tablepress-custom', $custom_css_minified_url, array( 'tablepress-default' ), $custom_css_version ); if ( did_action( 'wp_print_styles' ) ) { wp_print_styles( 'tablepress-custom' ); } return; } $use_custom_css_normal_file = ( $use_custom_css_file && $tablepress_css->load_custom_css_from_file( 'normal' ) ); if ( $use_custom_css_normal_file ) { $custom_css_normal_url = $tablepress_css->get_custom_css_location( 'normal', 'url' ); wp_enqueue_style( 'tablepress-custom', $custom_css_normal_url, array( 'tablepress-default' ), $custom_css_version ); if ( did_action( 'wp_print_styles' ) ) { wp_print_styles( 'tablepress-custom' ); } return; } if ( $use_custom_css ) { // Get "Custom CSS" from options, try minified Custom CSS first. $custom_css_minified = TablePress::$model_options->get( 'custom_css_minified' ); if ( ! empty( $custom_css_minified ) ) { $custom_css = $custom_css_minified; } /** * Filters the "Custom CSS" code that is to be loaded as inline CSS. * * @since 1.0.0 * * @param string $custom_css The "Custom CSS" code. */ $custom_css = apply_filters( 'tablepress_custom_css', $custom_css ); if ( ! empty( $custom_css ) ) { wp_add_inline_style( 'tablepress-default', $custom_css ); if ( did_action( 'wp_print_styles' ) ) { wp_print_styles( 'tablepress-default' ); } return; } } } /** * Enqueues the DataTables JavaScript library and its dependencies. * * @since 3.0.0 */ protected function enqueue_datatables_files(): void { $js_file = 'js/jquery.datatables.min.js'; $js_url = plugins_url( $js_file, TABLEPRESS__FILE__ ); /** * Filters the URL from which the DataTables JavaScript library file is loaded. * * @since 1.0.0 * * @param string $js_url URL of the DataTables JS library file. * @param string $js_file Path and file name of the DataTables JS library file. */ $js_url = apply_filters( 'tablepress_datatables_js_url', $js_url, $js_file ); $dependencies = array( 'jquery-core' ); if ( ! empty( $this->datatables_datetime_formats ) ) { $dependencies[] = 'moment'; } /** * Filters the dependencies for the DataTables JavaScript library. * * @since 3.0.0 * * @param string[] $dependencies The dependencies for the DataTables JS library. */ $dependencies = apply_filters( 'tablepress_datatables_js_dependencies', $dependencies ); wp_enqueue_script( 'tablepress-datatables', $js_url, $dependencies, TablePress::version, true ); } /** * Adds the JavaScript code for the invocation of the DataTables JS library. * * @since 1.0.0 */ public function add_datatables_calls(): void { // Prevent repeated execution (which would lead to DataTables error messages) via a static variable. static $datatables_calls_printed = false; if ( $datatables_calls_printed ) { return; } // Bail early if there are no TablePress tables on the page. if ( empty( $this->shown_tables ) ) { return; } /* * Don't add the DataTables function calls in the scope of the block editor iframe. * This is necessary for non-block themes, for others, the repeated execution check above is sufficient. */ if ( function_exists( 'get_current_screen' ) ) { $current_screen = get_current_screen(); if ( ( $current_screen instanceof WP_Screen ) && $current_screen->is_block_editor() ) { return; } } // Filter out all tables that use DataTables. $shown_tables_with_datatables = array(); foreach ( $this->shown_tables as $table_id => $table_store ) { if ( ! empty( $table_store['instances'] ) ) { $shown_tables_with_datatables[ (string) $table_id ] = $table_store; } } // Bail early if there are no tables with activated DataTables on the page. if ( empty( $shown_tables_with_datatables ) ) { return; } $this->enqueue_datatables_files(); // Storage for the DataTables language strings. $datatables_language = array(); // Generate the specific JS commands, depending on chosen features on the "Edit" screen and the Shortcode parameters. $commands = array(); foreach ( $shown_tables_with_datatables as $table_id => $table_store ) { $table_id = (string) $table_id; // Ensure that the table ID is a string, as it comes from an array key where numeric strings are converted to integers. foreach ( $table_store['instances'] as $html_id => $js_options ) { $parameters = array(); // Settle dependencies/conflicts between certain features. if ( false !== $js_options['datatables_scrolly'] ) { // datatables_scrolly can be a string, so that the explicit `false` check is needed. // Vertical scrolling and pagination don't work together. $js_options['datatables_paginate'] = false; } // Sanitize, as it may come from a Shortcode attribute. $js_options['datatables_paginate_entries'] = (int) $js_options['datatables_paginate_entries']; /* * DataTables language/translation handling. */ /** * Filters the locale/language for the DataTables JavaScript library. * * @since 1.0.0 * * @param string $locale The DataTables JS library locale. * @param string $table_id The current table ID. */ $datatables_locale = apply_filters( 'tablepress_datatables_locale', $js_options['datatables_locale'], $table_id ); // Only load each locale's language file once. if ( ! isset( $datatables_language[ $datatables_locale ] ) ) { $orig_language_file = TABLEPRESS_ABSPATH . "i18n/datatables/lang-{$datatables_locale}.php"; /** * Filters the language file path for the DataTables JavaScript library. * * PHP files that return an array and JSON files are supported. * The JSON file method is deprecated and should no longer be used. * * @since 1.0.0 * * @param string $orig_language_file Language file path for the DataTables JS library. * @param string $datatables_locale Current locale/language for the DataTables JS library. * @param string $tablepress_abspath Base path of the TablePress plugin. */ $language_file = apply_filters( 'tablepress_datatables_language_file', $orig_language_file, $datatables_locale, TABLEPRESS_ABSPATH ); /* * Load translation file if it's not "en_US" (included as the default in DataTables) * or if the filter was used to change the language file, and the language file exists. * Otherwise, use an empty en_US placeholder, so that the strings are filterable later. */ if ( ( 'en_US' !== $datatables_locale || $orig_language_file !== $language_file ) && file_exists( $language_file ) ) { if ( str_ends_with( $language_file, '.php' ) ) { $datatables_strings = require $language_file; if ( ! is_array( $datatables_strings ) ) { $datatables_strings = array(); } } elseif ( str_ends_with( $language_file, '.json' ) ) { $datatables_strings = file_get_contents( $language_file ); $datatables_strings = json_decode( $datatables_strings, true ); // @phpstan-ignore argument.type // Check if JSON could be decoded. if ( is_null( $datatables_strings ) ) { $datatables_strings = array(); } $datatables_strings = (array) $datatables_strings; } else { // The filtered language file exists, but is not a .php or .json file, so don't use it. $datatables_strings = array(); } } else { // If no translation file for the defined locale exists or is needed, use "en_US", as that's built-in. $datatables_locale = 'en_US'; $datatables_strings = array(); } /** * Filters the language strings for the DataTables JavaScript library's features. * * @since 2.0.0 * * @param array $datatables_strings The language strings for DataTables. * @param string $datatables_locale Current locale/language for the DataTables JS library. */ $datatables_language[ $datatables_locale ] = apply_filters( 'tablepress_datatables_language_strings', $datatables_strings, $datatables_locale ); } $parameters['language'] = "language:DT_language['{$datatables_locale}']"; // These parameters need to be added for performance gain or to overwrite unwanted default behavior. if ( $js_options['datatables_sort'] ) { // No initial sort. $parameters['order'] = 'order:[]'; // Don't add additional classes, to speed up sorting. $parameters['orderClasses'] = 'orderClasses:false'; } // The following options are activated by default, so we only need to "false" them if we don't want them, but don't need to "true" them if we do. if ( ! $js_options['datatables_sort'] ) { $parameters['ordering'] = 'ordering:false'; } if ( $js_options['datatables_paginate'] ) { $parameters['pagingType'] = "pagingType:'simple_numbers'"; if ( $js_options['datatables_lengthchange'] ) { $length_menu = array( 10, 25, 50, 100 ); if ( ! in_array( $js_options['datatables_paginate_entries'], $length_menu, true ) ) { $length_menu[] = $js_options['datatables_paginate_entries']; sort( $length_menu, SORT_NUMERIC ); $parameters['lengthMenu'] = 'lengthMenu:[' . implode( ',', $length_menu ) . ']'; } } else { $parameters['lengthChange'] = 'lengthChange:false'; } if ( 10 !== $js_options['datatables_paginate_entries'] ) { $parameters['pageLength'] = "pageLength:{$js_options['datatables_paginate_entries']}"; } } else { $parameters['paging'] = 'paging:false'; } if ( ! $js_options['datatables_filter'] ) { $parameters['searching'] = 'searching:false'; } if ( ! $js_options['datatables_info'] ) { $parameters['info'] = 'info:false'; } if ( $js_options['datatables_scrollx'] ) { $parameters['scrollX'] = 'scrollX:true'; } if ( false !== $js_options['datatables_scrolly'] ) { $parameters['scrollY'] = 'scrollY:"' . preg_replace( '#[^0-9a-z.%]#', '', $js_options['datatables_scrolly'] ) . '"'; $parameters['scrollCollapse'] = 'scrollCollapse:true'; } if ( '' !== $js_options['datatables_custom_commands'] ) { $parameters['custom_commands'] = trim( $js_options['datatables_custom_commands'] ); // Remove leading and trailing whitespace. $parameters['custom_commands'] = trim( $parameters['custom_commands'], ',' ); // Remove potentially leading and trailing commas to prevent JS script errors. } /** * Filters the parameters that are passed to the DataTables JavaScript library. * * @since 1.0.0 * * @param array $parameters The parameters for the DataTables JS library. * @param string $table_id The current table ID. * @param string $html_id The ID of the table HTML element. * @param array $js_options The options for the JS library. */ $parameters = apply_filters( 'tablepress_datatables_parameters', $parameters, $table_id, $html_id, $js_options ); // If an existing parameter is set as an object key in the "Custom Commands", remove its separate value, to allow for full overrides. if ( isset( $parameters['custom_commands'] ) && '' !== $parameters['custom_commands'] ) { $parameters_in_custom_commands = TablePress::extract_keys_from_js_object_string( '{' . $parameters['custom_commands'] . '}' ); foreach ( $parameters_in_custom_commands as $parameter_in_custom_commands ) { unset( $parameters[ $parameter_in_custom_commands ] ); } } $name = substr( $html_id, 11 ); // Remove "tablepress-" from the HTML ID. $name = "DT_TP['" . str_replace( '-', '_', $name ) . "']"; $parameters = implode( ',', $parameters ); $parameters = ( ! empty( $parameters ) ) ? '{' . $parameters . '}' : ''; $command = "{$name} = new DataTable('#{$html_id}',{$parameters});"; /** * Filters the JavaScript command that invokes the DataTables JavaScript library on one table. * * @since 1.0.0 * * @param string $command The JS command for the DataTables JS library. * @param string $html_id The ID of the table HTML element. * @param string $parameters The parameters for the DataTables JS library. * @param string $table_id The current table ID. * @param array $js_options The options for the JS library. * @param string $name The name of the DataTable instance. */ $command = apply_filters( 'tablepress_datatables_command', $command, $html_id, $parameters, $table_id, $js_options, $name ); if ( ! empty( $command ) ) { $commands[] = $command; } } // foreach table instance } // foreach table ID // DataTables language/translation handling. if ( ! empty( $datatables_language ) ) { $datatables_language_command = wp_json_encode( $datatables_language, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_FORCE_OBJECT ); $datatables_language_command = "var DT_language={$datatables_language_command};\n"; } else { $datatables_language_command = ''; } // DataTables datetime format string handling. if ( ! empty( $this->datatables_datetime_formats ) ) { // Create a command like `DataTable.datetime('MM/DD/YYYY');DataTable.datetime('DD.MM.YYYY');`. $datatables_datetime_command = implode( '', array_map( static fn( string $datetime_format ): string => "DataTable.datetime('{$datetime_format}');", $this->datatables_datetime_formats, ) ) . "\n"; } else { $datatables_datetime_command = ''; } /** * Filters the JavaScript code for the DataTables JavaScript library that initializes the automatically detected date/time formats via moment.js. * * @since 3.0.0 * * @param string $datatables_datetime_command The JS code for the DataTables JS library that initializes the date/time formats. * @param string[] $datatables_datetime_formats The date/time formats for moment.js. */ $datatables_datetime_command = apply_filters( 'tablepress_datatables_datetime_command', $datatables_datetime_command, $this->datatables_datetime_formats ); $datatables_pre_commands = $datatables_language_command . $datatables_datetime_command; $commands = implode( "\n", $commands ); /** * Filters the JavaScript commands that invoke the DataTables JavaScript library on all tables on the page. * * @since 1.0.0 * * @param string $commands The JS commands for the DataTables JS library. */ $commands = apply_filters( 'tablepress_all_datatables_commands', $commands ); if ( '' === $commands ) { return; } $script_template = <<<'JS' var DT_TP = {}; jQuery(($)=>{ %1$s%2$s }); JS; /** * Filters the script/jQuery wrapper code for the DataTables commands calls. * * @since 1.14.0 * * @param string $script_template Default script/jQuery wrapper code for the DataTables commands calls. */ $script_template = apply_filters( 'tablepress_all_datatables_commands_wrapper', $script_template ); $script = sprintf( $script_template, $datatables_pre_commands, $commands ); wp_add_inline_script( 'tablepress-datatables', $script ); // Prevent repeated execution (which would lead to DataTables error messages) via a static variable. $datatables_calls_printed = true; } /** * Handles the Shortcode [table id= /]. * * @since 1.0.0 * * @param array|string $shortcode_atts List of attributes that where included in the Shortcode. An empty string for empty Shortcodes like [table] or [table /]. * @return string Resulting HTML code for the table with the ID . */ public function shortcode_table( /* array|string */ $shortcode_atts ): string { $shortcode_atts = (array) $shortcode_atts; $this->maybe_enqueue_css(); $_render = TablePress::load_class( 'TablePress_Render', 'class-render.php', 'classes' ); $default_shortcode_atts = $_render->get_default_render_options(); /** * Filters the available/default attributes for the [table] Shortcode. * * @since 1.0.0 * * @param array $default_shortcode_atts The [table] Shortcode default attributes. */ $default_shortcode_atts = apply_filters( 'tablepress_shortcode_table_default_shortcode_atts', $default_shortcode_atts ); // Parse Shortcode attributes, only allow those that are specified. $shortcode_atts = shortcode_atts( $default_shortcode_atts, $shortcode_atts ); // Optional third argument left out on purpose. Use filter in the next line instead. /** * Filters the attributes that were passed to the [table] Shortcode. * * @since 1.0.0 * * @param array $shortcode_atts The attributes passed to the [table] Shortcode. */ $shortcode_atts = apply_filters( 'tablepress_shortcode_table_shortcode_atts', $shortcode_atts ); // Check, if a table with the given ID exists. $table_id = (string) preg_replace( '/[^a-zA-Z0-9_-]/', '', $shortcode_atts['id'] ); if ( ! TablePress::$model_table->table_exists( $table_id ) ) { $message = "[table “{$table_id}” not found /]
\n"; /** * Filters the "Table not found" message. * * @since 1.0.0 * * @param string $message The "Table not found" message. * @param string $table_id The current table ID. */ $message = apply_filters( 'tablepress_table_not_found_message', $message, $table_id ); return $message; } // Load table, with table data, options, and visibility settings. $table = TablePress::$model_table->load( $table_id, true, true ); if ( is_wp_error( $table ) ) { $message = "[table “{$table_id}” could not be loaded /]
\n"; /** * Filters the "Table could not be loaded" message. * * @since 1.0.0 * * @param string $message The "Table could not be loaded" message. * @param string $table_id The current table ID. * @param WP_Error $table The error object for the table. */ $message = apply_filters( 'tablepress_table_load_error_message', $message, $table_id, $table ); return $message; } if ( isset( $table['is_corrupted'] ) && $table['is_corrupted'] ) { $message = "
Attention: The internal data of table “{$table_id}” is corrupted!
"; /** * Filters the "Table data is corrupted" message. * * @since 1.0.0 * * @param string $message The "Table data is corrupted" message. * @param string $table_id The current table ID. * @param string $json_error The JSON error with information about the corrupted table. */ $message = apply_filters( 'tablepress_table_corrupted_message', $message, $table_id, $table['json_error'] ); return $message; } if ( ! is_null( $shortcode_atts['datatables_custom_commands'] ) ) { /** * Filters whether the "datatables_custom_commands" Shortcode parameter is disabled. * * By default, the "datatables_custom_commands" Shortcode parameter is disabled for security reasons. * * @since 1.0.0 * * @param bool $disable Whether to disable the "datatables_custom_commands" Shortcode parameter. Default true. */ if ( apply_filters( 'tablepress_disable_custom_commands_shortcode_parameter', true ) ) { $shortcode_atts['datatables_custom_commands'] = null; } else { // Convert the HTML entity `&` back to `&` manually, as entities in Shortcodes in normal text paragraphs are sometimes double-encoded. $shortcode_atts['datatables_custom_commands'] = str_replace( '&', '&', $shortcode_atts['datatables_custom_commands'] ); // Convert HTML entities like `<`, `[`, `[`, and `&` back to their respective characters. $shortcode_atts['datatables_custom_commands'] = html_entity_decode( $shortcode_atts['datatables_custom_commands'], ENT_QUOTES | ENT_HTML5, get_option( 'blog_charset' ) ); } } // Determine options to use (if set in Shortcode, use those, otherwise use stored options, from the "Edit" screen). $render_options = array(); foreach ( $shortcode_atts as $key => $value ) { if ( is_null( $value ) && isset( $table['options'][ $key ] ) ) { // Use the table's stored option value, if the Shortcode parameter was not set. $render_options[ $key ] = $table['options'][ $key ]; } elseif ( is_string( $value ) ) { // Convert strings 'true' or 'false' to boolean, keep others. $value_lowercase = strtolower( $value ); if ( 'true' === $value_lowercase ) { $render_options[ $key ] = true; } elseif ( 'false' === $value_lowercase ) { $render_options[ $key ] = false; } else { $render_options[ $key ] = $value; } } else { // Keep all other values. $render_options[ $key ] = $value; } } // Backward compatibility: Convert boolean or numeric string "table_head" and "table_foot" options to integer. $render_options['table_head'] = absint( $render_options['table_head'] ); $render_options['table_foot'] = absint( $render_options['table_foot'] ); // Generate unique HTML ID, depending on how often this table has already been shown on this page. if ( ! isset( $this->shown_tables[ $table_id ] ) ) { $this->shown_tables[ $table_id ] = array( 'count' => 0, 'instances' => array(), ); } ++$this->shown_tables[ $table_id ]['count']; $count = $this->shown_tables[ $table_id ]['count']; $render_options['html_id'] = "tablepress-{$table_id}"; if ( $count > 1 ) { $render_options['html_id'] .= "-no-{$count}"; } /** * Filters the ID of the table HTML element. * * @since 1.0.0 * * @param string $html_id The ID of the table HTML element. * @param string $table_id The current table ID. * @param int $count Number of copies of the table with this table ID on the page. */ $render_options['html_id'] = apply_filters( 'tablepress_html_id', $render_options['html_id'], $table_id, $count ); // Generate the "Edit Table" link. $render_options['edit_table_url'] = ''; /** * Filters whether the "Edit" link below the table shall be shown. * * The "Edit" link is only shown to logged-in users who possess the necessary capability to edit the table. * * @since 1.0.0 * * @param bool $show Whether to show the "Edit" link below the table. Default true. * @param string $table_id The current table ID. */ if ( is_user_logged_in() && ! $render_options['block_preview'] && apply_filters( 'tablepress_edit_link_below_table', true, $table['id'] ) && current_user_can( 'tablepress_edit_table', $table['id'] ) ) { $render_options['edit_table_url'] = TablePress::url( array( 'action' => 'edit', 'table_id' => $table['id'] ) ); } /** * Filters the render options for the table. * * The render options are determined from the settings on a table's "Edit" screen and the Shortcode parameters. * * @since 1.0.0 * * @param array $render_options The render options for the table. * @param array $table The current table. */ $render_options = apply_filters( 'tablepress_table_render_options', $render_options, $table ); // Backward compatibility: Convert boolean "table_head" and "table_foot" options to integer, in case they were overwritten via the filter hook. $render_options['table_head'] = absint( $render_options['table_head'] ); $render_options['table_foot'] = absint( $render_options['table_foot'] ); // Check if table output shall and can be loaded from the transient cache, otherwise generate the output. if ( $render_options['cache_table_output'] && ! is_user_logged_in() ) { // Hash the Render Options array to get a unique cache identifier. $table_hash = md5( wp_json_encode( $render_options, TABLEPRESS_JSON_OPTIONS ) ); // @phpstan-ignore argument.type $transient_name = 'tablepress_' . $table_hash; // Attention: This string must not be longer than 45 characters! $output = get_transient( $transient_name ); if ( false === $output || '' === $output ) { // Render/generate the table HTML, as it was not found in the cache. $_render->set_input( $table, $render_options ); $output = $_render->get_output( 'html' ); // Save render output in a transient, set cache timeout to 24 hours. set_transient( $transient_name, $output, DAY_IN_SECONDS ); // Update output caches list transient (necessary for cache invalidation upon table saving). $caches_list_transient_name = 'tablepress_c_' . md5( $table_id ); $caches_list = get_transient( $caches_list_transient_name ); if ( false === $caches_list ) { $caches_list = array(); } else { $caches_list = (array) json_decode( $caches_list, true ); } if ( ! in_array( $transient_name, $caches_list, true ) ) { $caches_list[] = $transient_name; } set_transient( $caches_list_transient_name, wp_json_encode( $caches_list, TABLEPRESS_JSON_OPTIONS ), 2 * DAY_IN_SECONDS ); } else { /** * Filters the cache hit comment message. * * @since 1.0.0 * * @param string $comment The cache hit comment message. */ $output .= apply_filters( 'tablepress_cache_hit_comment', "" ); } } else { // Render/generate the table HTML, as no cache is to be used. $_render->set_input( $table, $render_options ); $output = $_render->get_output( 'html' ); } // If DataTables is to be and can be used with this instance of a table, process its parameters and register the call for inclusion in the footer. if ( $render_options['use_datatables'] && 0 < $render_options['table_head'] && ! str_contains( $output, 'tbody-has-connected-cells' ) // The Render class adds this CSS class to the `` element if the table has connected cells in the ``. ) { // Get options for the DataTables JavaScript library from the table's render options. $js_options = array(); foreach ( array( 'alternating_row_colors', 'datatables_sort', 'datatables_paginate', 'datatables_paginate', 'datatables_paginate_entries', 'datatables_lengthchange', 'datatables_filter', 'datatables_info', 'datatables_scrollx', 'datatables_scrolly', 'datatables_locale', 'datatables_custom_commands', ) as $option ) { $js_options[ $option ] = $render_options[ $option ]; } /** * Filters the JavaScript options for the table. * * The JavaScript options are determined from the settings on a table's "Edit" screen and the Shortcode parameters. * They are part of the render options and can be overwritten with Shortcode parameters. * * @since 1.0.0 * * @param array $js_options The JavaScript options for the table. * @param string $table_id The current table ID. * @param array $render_options The render options for the table. */ $js_options = apply_filters( 'tablepress_table_js_options', $js_options, $table_id, $render_options ); $this->shown_tables[ $table_id ]['instances'][ (string) $render_options['html_id'] ] = $js_options; // DataTables datetime format string handling. if ( '' !== $render_options['datatables_datetime'] ) { $render_options['datatables_datetime'] = explode( '|', $render_options['datatables_datetime'] ); foreach ( $render_options['datatables_datetime'] as $datetime_format ) { $datetime_format = trim( $datetime_format ); if ( '' !== $datetime_format && ! in_array( $datetime_format, $this->datatables_datetime_formats, true ) ) { $this->datatables_datetime_formats[] = $datetime_format; } } } } // Maybe print a list of used render options. if ( $render_options['shortcode_debug'] && is_user_logged_in() ) { $output .= '
' . var_export( $render_options, true ) . '
'; // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export } return $output; } /** * Handles the Shortcode [table-info id= field= /]. * * @since 1.0.0 * * @param array|string $shortcode_atts List of attributes that where included in the Shortcode. An empty string for empty Shortcodes like [table] or [table /]. * @return string Text that replaces the Shortcode (error message or asked-for information). */ public function shortcode_table_info( /* array|string */ $shortcode_atts ): string { $shortcode_atts = (array) $shortcode_atts; // Parse Shortcode attributes, only allow those that are specified. $default_shortcode_atts = array( 'id' => '', 'field' => '', 'format' => '', ); /** * Filters the available/default attributes for the [table-info] Shortcode. * * @since 1.0.0 * * @param array $default_shortcode_atts The [table-info] Shortcode default attributes. */ $default_shortcode_atts = apply_filters( 'tablepress_shortcode_table_info_default_shortcode_atts', $default_shortcode_atts ); $shortcode_atts = shortcode_atts( $default_shortcode_atts, $shortcode_atts ); // Optional third argument left out on purpose. Use filter in the next line instead. /** * Filters the attributes that were passed to the [table-info] Shortcode. * * @since 1.0.0 * * @param array $shortcode_atts The attributes passed to the [table-info] Shortcode. */ $shortcode_atts = apply_filters( 'tablepress_shortcode_table_info_shortcode_atts', $shortcode_atts ); /** * Filters whether the output of the [table-info] Shortcode is overwritten/short-circuited. * * @since 1.0.0 * * @param false|string $overwrite Whether the [table-info] output is overwritten. Return false for the regular content, and a string to overwrite the output. * @param array $shortcode_atts The attributes passed to the [table-info] Shortcode. */ $overwrite = apply_filters( 'tablepress_shortcode_table_info_overwrite', false, $shortcode_atts ); if ( is_string( $overwrite ) ) { return $overwrite; } // Check, if a table with the given ID exists. $table_id = preg_replace( '/[^a-zA-Z0-9_-]/', '', $shortcode_atts['id'] ); if ( ! TablePress::$model_table->table_exists( $table_id ) ) { $message = "[table “{$table_id}” not found /]
\n"; /** This filter is documented in controllers/controller-frontend.php */ $message = apply_filters( 'tablepress_table_not_found_message', $message, $table_id ); return $message; } // Load table, with table data, options, and visibility settings. $table = TablePress::$model_table->load( $table_id, true, true ); if ( is_wp_error( $table ) ) { $message = "[table “{$table_id}” could not be loaded /]
\n"; /** This filter is documented in controllers/controller-frontend.php */ $message = apply_filters( 'tablepress_table_load_error_message', $message, $table_id, $table ); return $message; } $field = (string) preg_replace( '/[^a-z_]/', '', strtolower( $shortcode_atts['field'] ) ); $format = (string) preg_replace( '/[^a-z]/', '', strtolower( $shortcode_atts['format'] ) ); // Generate output, depending on what information (field) was asked for. switch ( $field ) { case 'name': case 'description': $output = $table[ $field ]; break; case 'last_modified': switch ( $format ) { case 'raw': case 'mysql': $output = $table['last_modified']; break; case 'human': $modified_timestamp = date_create( $table['last_modified'], wp_timezone() ); if ( false === $modified_timestamp ) { $modified_timestamp = $table['last_modified']; } else { $modified_timestamp = $modified_timestamp->getTimestamp(); } $current_timestamp = time(); $time_diff = $current_timestamp - $modified_timestamp; // Time difference is only shown up to one week. if ( $time_diff >= 0 && $time_diff < WEEK_IN_SECONDS ) { $output = sprintf( __( '%s ago', 'default' ), human_time_diff( $modified_timestamp, $current_timestamp ) ); } else { $output = TablePress::format_datetime( $table['last_modified'], '
' ); } break; case 'date': $output = TablePress::format_datetime( $table['last_modified'], get_option( 'date_format' ) ); break; case 'time': $output = TablePress::format_datetime( $table['last_modified'], get_option( 'time_format' ) ); break; default: $output = TablePress::format_datetime( $table['last_modified'] ); break; } break; case 'last_editor': $output = TablePress::get_user_display_name( $table['options']['last_editor'] ); break; case 'author': $output = TablePress::get_user_display_name( $table['author'] ); break; case 'number_rows': $output = count( $table['data'] ); if ( 'raw' !== $format ) { $output -= $table['options']['table_head']; $output -= $table['options']['table_foot']; } break; case 'number_columns': $output = count( $table['data'][0] ); break; default: $output = "[table-info field “{$field}” not found in table “{$table_id}” /]
\n"; /** * Filters the "table info field not found" message. * * @since 1.0.0 * * @param string $output The "table info field not found" message. * @param array $table The current table. * @param string $field The field that was not found. * @param string $format The return format for the field. */ $output = apply_filters( 'tablepress_table_info_not_found_message', $output, $table, $field, $format ); } /** * Filters the output of the [table-info] Shortcode. * * @since 1.0.0 * * @param string $output The output of the [table-info] Shortcode. * @param array $table The current table. * @param array $shortcode_atts The attributes passed to the [table-info] Shortcode. */ $output = apply_filters( 'tablepress_shortcode_table_info_output', $output, $table, $shortcode_atts ); return $output; } /** * Expands the WP Search to also find posts and pages that have a search term in a table that is shown in them. * * This is done by looping through all search terms and TablePress tables and searching there for the search term, * saving all tables's IDs that have a search term and then expanding the WP query to search for posts or pages that have the * Shortcode for one of these tables in their content. * * @since 1.0.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $search_sql Current part of the "WHERE" clause of the SQL statement used to get posts/pages from the WP database that is related to searching. * @return string Eventually extended SQL "WHERE" clause, to also find posts/pages with Shortcodes in them. */ public function posts_search_filter( /* string */ $search_sql ): string { // Don't use a type hint in the method declaration as there can be cases where `null` is passed to the filter hook callback somehow. global $wpdb; // Protect against cases where `null` is somehow passed to the filter hook callback. if ( ! is_string( $search_sql ) ) { // @phpstan-ignore function.alreadyNarrowedType (The `is_string()` check is needed as the input is coming from a filter hook.) return ''; } if ( ! is_search() || ! is_main_query() ) { return $search_sql; } // Get variable that contains all search terms, parsed from $_GET['s'] by WP. $search_terms = get_query_var( 'search_terms' ); if ( empty( $search_terms ) || ! is_array( $search_terms ) ) { return $search_sql; } // Load all table IDs and prime post meta cache for cached access to options and visibility settings of the tables, don't run filter hook. $table_ids = TablePress::$model_table->load_all( true, false ); // Array of all search words that were found, and the table IDs where they were found. $query_result = array(); foreach ( $table_ids as $table_id ) { // Load table, with table data, options, and visibility settings. $table = TablePress::$model_table->load( $table_id, true, true ); // Skip tables that could not be loaded. if ( is_wp_error( $table ) ) { continue; } // Do not search in corrupted tables. if ( isset( $table['is_corrupted'] ) && $table['is_corrupted'] ) { continue; } foreach ( $search_terms as $search_term ) { if ( ( $table['options']['print_name'] && false !== stripos( $table['name'], (string) $search_term ) ) || ( $table['options']['print_description'] && false !== stripos( $table['description'], (string) $search_term ) ) ) { // Found the search term in the name or description (and they are shown). $query_result[ $search_term ][] = $table_id; // Add table ID to result list. // No need to continue searching this search term in this table. continue; } // Search search term in visible table cells (without taking Shortcode parameters into account!). foreach ( $table['data'] as $row_idx => $table_row ) { if ( 0 === $table['visibility']['rows'][ $row_idx ] ) { // Row is hidden, so don't search in it. continue; } foreach ( $table_row as $col_idx => $table_cell ) { if ( 0 === $table['visibility']['columns'][ $col_idx ] ) { // Column is hidden, so don't search in it. continue; } // @todo Cells are not evaluated here, so math formulas are searched. if ( false !== stripos( $table_cell, (string) $search_term ) ) { // Found the search term in the cell content. $query_result[ $search_term ][] = $table_id; // Add table ID to result list // No need to continue searching this search term in this table. continue 3; } } } } } // For all found table IDs for each search term, add additional OR statement to the SQL "WHERE" clause. // If $_GET['exact'] is set, WordPress doesn't use % in SQL LIKE clauses. $exact = get_query_var( 'exact' ); $n = ( empty( $exact ) ) ? '%' : ''; $search_sql = $wpdb->remove_placeholder_escape( $search_sql ); foreach ( $query_result as $search_term => $table_ids ) { $search_term = esc_sql( $wpdb->esc_like( $search_term ) ); $old_or = "OR ({$wpdb->posts}.post_content LIKE '{$n}{$search_term}{$n}')"; // @phpstan-ignore encapsedStringPart.nonString (The esc_sql() call above returns a string, as a string is passed.) $table_ids = implode( '|', $table_ids ); $regexp = '\\\\[' . TablePress::$shortcode . ' id=(["\\\']?)(' . $table_ids . ')([\]"\\\' /])'; // ' needs to be single escaped, [ double escaped (with \\) in mySQL $new_or = $old_or . " OR ({$wpdb->posts}.post_content REGEXP '{$regexp}')"; $search_sql = str_replace( $old_or, $new_or, $search_sql ); } $search_sql = $wpdb->add_placeholder_escape( $search_sql ); return $search_sql; } /** * Callback function for rendering the tablepress/table block. * * @since 2.0.0 * * @param array $block_attributes List of attributes that where included in the block settings. * @return string Resulting HTML code for the table. */ public function table_block_render_callback( array $block_attributes ): string { // Don't return anything if no table was selected. if ( '' === $block_attributes['id'] ) { return ''; } if ( '' !== trim( $block_attributes['parameters'] ) ) { $render_attributes = shortcode_parse_atts( $block_attributes['parameters'] ); } else { $render_attributes = array(); } $render_attributes['id'] = $block_attributes['id']; return $this->shortcode_table( $render_attributes ); } /** * Registers the TablePress Elementor widgets. * * @since 3.1.0 * * @param \Elementor\Widgets_Manager $widgets_manager Elementor widgets manager. */ public function register_elementor_widgets( \Elementor\Widgets_Manager $widgets_manager ): void { TablePress::load_file( 'class-elementor-widget-table.php', 'classes' ); $widgets_manager->register( new TablePress\Elementor\TablePressTableWidget() ); // @phpstan-ignore method.notFound (Elementor methods are not in the stubs.) } /** * Enqueues the TablePress Elementor Editor CSS styles. * * @since 3.1.0 */ public function enqueue_elementor_editor_styles(): void { $svg_url = plugins_url( 'admin/img/tablepress-editor-button.svg', TABLEPRESS__FILE__ ); wp_register_style( 'tablepress-elementor', false ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion wp_add_inline_style( 'tablepress-elementor', << .tablepress-elementor-icon) { height: 43.5px; } .tablepress-elementor-icon { display: inline-block; height: 28px; width: 28px; background-image: url({$svg_url}); background-repeat: no-repeat; background-position: center; background-size: 28px auto; } CSS ); wp_enqueue_style( 'tablepress-elementor' ); } } // class TablePress_Frontend_Controller PK!controllers/index.phpnu[

update your WordPress installation to at least version %2$s!', 'tablepress' ), esc_url( self_admin_url( 'update-core.php' ) ), '6.2' ); } else { printf( __( 'Please ask your site’s administrator to update WordPress to at least version %1$s!', 'tablepress' ), '6.2' ); } ?>

Learn more about updating PHP or contact your server administrator.', 'tablepress' ), esc_url( wp_get_update_php_url() ) ); ?>

> */ protected array $view_actions = array(); /** * Instance of the TablePress Admin View that is rendered. * * @since 1.0.0 */ protected \TablePress_View $view; /** * Initialize the Admin Controller, determine location the admin menu, set up actions. * * @since 1.0.0 */ public function __construct() { parent::__construct(); // Handler for changing the number of shown tables in the list of tables (via WP List Table class). add_filter( 'set_screen_option_tablepress_list_per_page', array( $this, 'save_list_tables_screen_option' ), 10, 3 ); add_action( 'admin_menu', array( $this, 'add_admin_menu_entry' ) ); add_action( 'admin_init', array( $this, 'add_admin_actions' ) ); add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_block_editor_assets' ) ); add_action( 'enqueue_block_assets', array( $this, 'enqueue_block_assets' ) ); } /** * Handler for changing the number of shown tables in the list of tables (via WP List Table class). * * @since 1.0.0 * * @param mixed $screen_option Current value of the filter (probably bool false). * @param string $option Option in which the setting is stored. * @param int $value Current value of the setting. * @return int Changed value of the setting */ public function save_list_tables_screen_option( /* mixed */ $screen_option, string $option, int $value ): int { return $value; } /** * Add admin screens to the correct place in the admin menu. * * @since 1.0.0 */ public function add_admin_menu_entry(): void { // Callback for all menu entries. $callback = array( $this, 'show_admin_page' ); /** * Filters the TablePress admin menu entry name. * * @since 1.0.0 * * @param string $entry_name The admin menu entry name. Default "TablePress". */ $admin_menu_entry_name = apply_filters( 'tablepress_admin_menu_entry_name', 'TablePress' ); $this->init_view_actions(); $min_access_cap = $this->view_actions['list']['required_cap']; if ( TablePress::$controller->is_top_level_page ) { $icon_url = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld0JveD0iLTMyIC0zMiA2NCA2NCIgZmlsbD0iI2ZmZiI+PHBhdGggZD0iTTAtMjUuODU0aC0yNS44NTR2NTEuNzA4aDUxLjcwOFYwSDIxdjIxaC00MnYtNDJIMFoiLz48cGF0aCBkPSJNLTE4LTE4aDEwdjEwaC0xMHpNLTE4LTVoMTBWNWgtMTB6TS01LTVINVY1SC01ek0tMTggOGgxMHYxMGgtMTB6TS01IDhINXYxMEgtNXpNOCA4aDEwdjEwSDh6TTUtMzFoNi4xOHY2LjE4SDV6TTE5LTI1aDYuMTh2Ni4xOEgxOXpNMC0xNWgzLjgydjMuODJIMHpNMTAtMjBoMy44MnYzLjgySDEwek0yNS0xMmgzLjgydjMuODJIMjV6TTgtMTNoMTB2MTBIOHoiLz48L3N2Zz4='; switch ( TablePress::$controller->parent_page ) { case 'top': $position = 3; // Position of Dashboard + 1. break; case 'bottom': $position = isset( $GLOBALS['_wp_last_utility_menu'] ) ? ++$GLOBALS['_wp_last_utility_menu'] : 80; break; case 'middle': default: $position = isset( $GLOBALS['_wp_last_object_menu'] ) ? ++$GLOBALS['_wp_last_object_menu'] : 25; break; } // Prevent overwriting existing menu entries. while ( isset( $GLOBALS['menu'][ $position ] ) ) { ++$position; } add_menu_page( 'TablePress', $admin_menu_entry_name, $min_access_cap, 'tablepress', $callback, $icon_url, $position ); // @phpstan-ignore argument.type foreach ( $this->view_actions as $action => $entry ) { if ( ! $entry['show_entry'] ) { continue; } $slug = 'tablepress'; if ( 'list' !== $action ) { $slug .= '_' . $action; } // @phpstan-ignore argument.type, argument.type $page_hook = add_submenu_page( 'tablepress', sprintf( __( '%1$s ‹ %2$s', 'tablepress' ), $entry['page_title'], 'TablePress' ), $entry['admin_menu_title'], $entry['required_cap'], $slug, $callback ); if ( false !== $page_hook ) { $this->page_hooks[] = $page_hook; } } } else { // @phpstan-ignore argument.type $page_hook = add_submenu_page( TablePress::$controller->parent_page, 'TablePress', $admin_menu_entry_name, $min_access_cap, 'tablepress', $callback ); if ( false !== $page_hook ) { $this->page_hooks[] = $page_hook; } } } /** * Set up handlers for user actions in the backend that exceed plain viewing. * * @since 1.0.0 */ public function add_admin_actions(): void { // Register the callbacks for processing action requests. $post_actions = array( 'list', 'add', 'options', 'export', 'import' ); $get_actions = array( 'hide_message', 'delete_table', 'copy_table', 'preview_table', 'editor_button_thickbox', 'uninstall_tablepress' ); foreach ( $post_actions as $action ) { add_action( "admin_post_tablepress_{$action}", array( $this, "handle_post_action_{$action}" ) ); } foreach ( $get_actions as $action ) { add_action( "admin_post_tablepress_{$action}", array( $this, "handle_get_action_{$action}" ) ); } // Register callbacks to trigger load behavior for admin pages. foreach ( $this->page_hooks as $page_hook ) { add_action( "load-{$page_hook}", array( $this, 'load_admin_page' ) ); } /** * Filters whether the legacy editor button should be loaded on the post editing screen. * * @since 2.1.0 * * @param bool $load_button Whether to load the legacy editor button. Default true. */ if ( apply_filters( 'tablepress_add_legacy_editor_button', true ) ) { $pages_with_editor_button = array( 'post.php', 'post-new.php' ); foreach ( $pages_with_editor_button as $editor_page ) { add_action( "load-{$editor_page}", array( $this, 'add_editor_buttons' ) ); } } if ( ! is_network_admin() && ! is_user_admin() ) { add_action( 'admin_bar_menu', array( $this, 'add_wp_admin_bar_new_content_menu_entry' ), 71 ); } add_action( 'load-plugins.php', array( $this, 'plugins_page' ) ); } /** * Loads additional JavaScript code for the TablePress table block (in the block editor context). * * @since 2.2.0 */ public function enqueue_block_editor_assets(): void { /* * Register the `react-jsx-runtime` polyfill, if it is not already registered. * This is needed as a polyfill for WP < 6.6, and can be removed once WP 6.6 is the minimum requirement for TablePress. */ if ( ! wp_script_is( 'react-jsx-runtime', 'registered' ) ) { wp_register_script( 'react-jsx-runtime', plugins_url( 'admin/js/react-jsx-runtime.min.js', TABLEPRESS__FILE__ ), array( 'react' ), TablePress::version, true ); } // Add table information for the block editor to the page. $handle = generate_block_asset_handle( 'tablepress/table', 'editorScript' ); $data = $this->get_block_editor_data(); wp_add_inline_script( $handle, $data, 'before' ); } /** * Loads additional CSS code for the TablePress table block (inside the block editor iframe). * * @since 2.2.0 */ public function enqueue_block_assets(): void { // Load the TablePress default CSS and the user's "Custom CSS" in the block editor iframe. if ( is_admin() ) { TablePress::$controller->maybe_enqueue_css(); } } /** * Gets the inline data that is referenced by the Block Editor JavaScript code for the TablePress blocks. * * @since 2.0.0 * * @return string JavaScript code for the Block Editor. */ protected function get_block_editor_data(): string { $tables = array(); // Load all table IDs without priming the post meta cache, as table options/visibility are not needed. $table_ids = TablePress::$model_table->load_all( false ); foreach ( $table_ids as $table_id ) { // Load table, without table data, options, and visibility settings. $table = TablePress::$model_table->load( $table_id, false, false ); // Skip tables that could not be loaded. if ( is_wp_error( $table ) ) { continue; } if ( '' === trim( $table['name'] ) ) { $table['name'] = __( '(no name)', 'tablepress' ); } $tables[ $table_id ] = esc_html( $table['name'] ); } /** * Filters the list of table IDs and names that is passed to the block editor, and is then used in the dropdown of the TablePress table block. * * @since 2.0.0 * * @param array $tables List of table names, the table ID is the array key. */ $tables = apply_filters( 'tablepress_block_editor_tables_list', $tables ); $tables = wp_json_encode( $tables, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ); if ( false === $tables ) { // JSON encoding failed, return an error object. Use a prefixed "_error" key to avoid conflicts with intentionally added "error" keys. $tables = '{ "_error": "The data could not be encoded to JSON!" }'; } // Print the JSON data inside a `JSON.parse()` call in JS for speed gains, with necessary escaping of `\` and `'`. $tables = str_replace( array( '\\', "'" ), array( '\\\\', "\'" ), $tables ); $shortcode = esc_js( TablePress::$shortcode ); $template = TablePress::$model_table->get_table_template(); $template = wp_json_encode( $template['options'], JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ); if ( false === $template ) { // JSON encoding failed, return an error object. Use a prefixed "_error" key to avoid conflicts with intentionally added "error" keys. $template = '{ "_error": "The data could not be encoded to JSON!" }'; } // Print the JSON data inside a `JSON.parse()` call in JS for speed gains, with necessary escaping of `\` and `'`. $template = str_replace( array( '\\', "'" ), array( '\\\\', "\'" ), $template ); /** * Filters whether the table block preview should be loaded via a in the block editor. * * @since 2.0.0 * * @param bool $load_block_preview Whether the table block preview should be loaded. */ $load_block_preview = apply_filters( 'tablepress_show_block_editor_preview', true ); $load_block_preview = (bool) $load_block_preview ? 'true' : 'false'; $url = ''; if ( current_user_can( 'tablepress_list_tables' ) ) { $url = TablePress::url( array( 'action' => 'list' ) ); } return <<enqueue_script( 'quicktags-button', array( 'quicktags', 'media-upload' ), array( 'editor_button' => array( 'caption' => __( 'Table', 'tablepress' ), 'title' => __( 'Insert a TablePress table', 'tablepress' ), 'thickbox_title' => __( 'Insert a TablePress table', 'tablepress' ), 'thickbox_url' => TablePress::url( array( 'action' => 'editor_button_thickbox' ), true, 'admin-post.php' ), ), ), ); // TinyMCE integration. if ( user_can_richedit() ) { add_filter( 'mce_external_plugins', array( $this, 'add_tinymce_plugin' ) ); add_filter( 'mce_buttons', array( $this, 'add_tinymce_button' ) ); } } /** * Adds the "Table" button to the TinyMCE toolbar. * * @since 1.0.0 * * @param string[] $buttons Current set of buttons in the TinyMCE toolbar. * @return string[] Extended set of buttons in the TinyMCE toolbar, including the "Table" button. */ public function add_tinymce_button( array $buttons ): array { $buttons[] = 'tablepress_insert_table'; return $buttons; } /** * Registers the "Table" button plugin for the TinyMCE editor. * * @since 1.0.0 * * @param array $plugins Current set of registered TinyMCE plugins. * @return array Extended set of registered TinyMCE plugins, including the "Table" button plugin. */ public function add_tinymce_plugin( array $plugins ): array { $plugins['tablepress_tinymce'] = plugins_url( 'admin/js/build/tinymce-button.js', TABLEPRESS__FILE__ ); return $plugins; } /** * Add "TablePress Table" entry to "New" dropdown menu in the WP Admin Bar. * * @since 1.0.0 * * @param WP_Admin_Bar $wp_admin_bar The current WP Admin Bar object. */ public function add_wp_admin_bar_new_content_menu_entry( WP_Admin_Bar $wp_admin_bar ): void { if ( ! current_user_can( 'tablepress_add_tables' ) ) { return; } // Don't load TablePress assets on the Freemius opt-in/activation screen. if ( tb_tp_fs()->is_activation_mode() && tb_tp_fs()->is_activation_page() ) { return; } $wp_admin_bar->add_menu( array( 'parent' => 'new-content', 'id' => 'new-tablepress-table', 'title' => __( 'TablePress table', 'tablepress' ), 'href' => TablePress::url( array( 'action' => 'add' ) ), ) ); } /** * Handle actions for loading of Plugins page. * * @since 1.0.0 */ public function plugins_page(): void { // Add additional links on Plugins page. add_filter( 'plugin_action_links_' . TABLEPRESS_BASENAME, array( $this, 'add_plugin_action_links' ) ); add_filter( 'plugin_row_meta', array( $this, 'add_plugin_row_meta' ), 10, 2 ); $incompatible_superseded_extensions = array( 'tablepress-datatables-alphabetsearch/tablepress-datatables-alphabetsearch.php', 'tablepress-datatables-column-filter-widgets/tablepress-datatables-column-filter-widgets.php', 'tablepress-datatables-columnfilter/tablepress-datatables-columnfilter.php', 'tablepress-datatables-fixedcolumns/tablepress-datatables-fixedcolumns.php', 'tablepress-datatables-inverted-filter/tablepress-datatables-inverted-filter.php', 'tablepress-datatables-row-details/tablepress-datatables-row-details.php', 'tablepress-datatables-rowgroup/tablepress-datatables-rowgroup.php', 'tablepress-responsive-tables/tablepress-responsive-tables.php', ); foreach ( $incompatible_superseded_extensions as $plugin_file ) { add_action( "after_plugin_row_{$plugin_file}", array( $this, 'add_superseded_extension_meta_row' ), 10, 3 ); } } /** * Add links to the TablePress entry in the "Plugin" column on the Plugins page. * * @since 1.0.0 * * @param string[] $links List of links to print in the "Plugin" column on the Plugins page. * @return string[] Extended list of links to print in the "Plugin" column on the Plugins page. */ public function add_plugin_action_links( array $links ): array { if ( current_user_can( 'tablepress_list_tables' ) ) { $links[] = '' . __( 'Plugin page', 'tablepress' ) . ''; } return $links; } /** * Add links to the TablePress entry in the "Description" column on the Plugins page. * * @since 1.0.0 * * @param string[] $links List of links to print in the "Description" column on the Plugins page. * @param string $file Name of the plugin. * @return string[] Extended list of links to print in the "Description" column on the Plugins page. */ public function add_plugin_row_meta( array $links, string $file ): array { if ( TABLEPRESS_BASENAME === $file ) { $links[] = '' . __( 'FAQ', 'tablepress' ) . ''; $links[] = '' . __( 'Documentation', 'tablepress' ) . ''; $links[] = '' . __( 'Support', 'tablepress' ) . ''; if ( ! TABLEPRESS_IS_PLAYGROUND_PREVIEW && tb_tp_fs()->is_free_plan() ) { $links[] = '' . __( 'Go Premium', 'tablepress' ) . ''; } } return $links; } /** * Prints a superseded extension notice below certain TablePress Extension plugins' meta rows on the "Plugins" screen. * * @since 2.4.1 * * @param string $plugin_file Path to the plugin file relative to the plugins directory. * @param array $plugin_data An array of plugin data. * @param string $status Status filter currently applied to the plugin list. */ public function add_superseded_extension_meta_row( string $plugin_file, array $plugin_data, string $status ): void { if ( ! is_plugin_active( $plugin_file ) ) { return; } ?>
is_top_level_page ) { // Or, for sub-menu entry of an admin menu "TablePress" entry, get it from the "page" GET parameter. if ( 'tablepress' !== $_GET['page'] ) { // Actions that are top-level entries, but don't have an action GET parameter (action is after last _ in string). $action = substr( $_GET['page'], 11 ); // $_GET['page'] has the format 'tablepress_{$action}' } } // Check if action is a supported action, and whether the user is allowed to access this screen. if ( ! isset( $this->view_actions[ $action ] ) || ! current_user_can( $this->view_actions[ $action ]['required_cap'] ) ) { // @phpstan-ignore argument.type (The array value for the capability is always a string.) wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 ); } // Don't load TablePress assets on the Freemius opt-in/activation screen. if ( tb_tp_fs()->is_activation_mode() && tb_tp_fs()->is_activation_page() ) { return; } // Changes current screen ID and pagenow variable in JS, to enable automatic meta box JS handling. set_current_screen( "tablepress_{$action}" ); /* * Set the `$typenow` global to the current CPT ourselves, as `WP_Screen::get()` does not determine the CPT correctly. * This is necessary as the WP Admin Menu can otherwise highlight wrong entries, see https://github.com/TablePress/TablePress/issues/24. */ if ( isset( $_GET['post_type'] ) && post_type_exists( $_GET['post_type'] ) ) { $GLOBALS['typenow'] = $_GET['post_type']; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited } // Pre-define some view data. $data = array( 'view_actions' => $this->view_actions, 'message' => ( ! empty( $_GET['message'] ) ) ? $_GET['message'] : false, 'error_details' => ( ! empty( $_GET['error_details'] ) ) ? $_GET['error_details'] : '', 'site_used_editor' => TablePress::site_used_editor(), ); // Depending on the action, load more necessary data for the corresponding view. switch ( $action ) { case 'list': $data['table_id'] = ( ! empty( $_GET['table_id'] ) ) ? $_GET['table_id'] : false; // Prime the post meta cache for cached loading of last_editor. $data['table_ids'] = TablePress::$model_table->load_all( true ); $data['messages']['donation_nag'] = $this->maybe_show_donation_message(); $data['messages']['first_visit'] = ! $data['messages']['donation_nag'] && TablePress::$model_options->get( 'message_first_visit' ); $data['messages']['plugin_update'] = TablePress::$model_options->get( 'message_plugin_update' ); $data['messages']['superseded_extensions'] = current_user_can( 'manage_options' ) && TablePress::$model_options->get( 'message_superseded_extensions' ); $data['table_count'] = count( $data['table_ids'] ); break; case 'about': $data['first_activation'] = TablePress::$model_options->get( 'first_activation' ); break; case 'options': /* * Maybe try saving "Custom CSS" to a file: * (called here, as the credentials form posts to this handler again, due to how `request_filesystem_credentials()` works) */ if ( isset( $_GET['item'] ) && 'save_custom_css' === $_GET['item'] ) { TablePress::check_nonce( 'options', $_GET['item'] ); // Nonce check here, as we don't have an explicit handler, and even viewing the screen needs to be checked. $action = 'options_custom_css'; // To load a different view. // Try saving "Custom CSS" to a file, otherwise this gets the HTML for the credentials form. $tablepress_css = TablePress::load_class( 'TablePress_CSS', 'class-css.php', 'classes' ); $result = $tablepress_css->save_custom_css_to_file_plugin_options( TablePress::$model_options->get( 'custom_css' ), TablePress::$model_options->get( 'custom_css_minified' ) ); if ( is_string( $result ) ) { $data['credentials_form'] = $result; // This will only be called if the save function doesn't do a redirect. } elseif ( true === $result ) { /* * At this point, saving was successful, so enable usage of CSS in files again, * and also increase the "Custom CSS" version number (for cache busting). */ TablePress::$model_options->update( array( 'use_custom_css_file' => true, 'custom_css_version' => TablePress::$model_options->get( 'custom_css_version' ) + 1, ) ); TablePress::redirect( array( 'action' => 'options', 'message' => 'success_save' ) ); } else { // Leaves only $result === false. TablePress::redirect( array( 'action' => 'options', 'message' => 'success_save_error_custom_css' ) ); } break; } $data['frontend_options']['use_custom_css'] = TablePress::$model_options->get( 'use_custom_css' ); $data['frontend_options']['custom_css'] = TablePress::$model_options->get( 'custom_css' ); $data['user_options']['parent_page'] = TablePress::$controller->parent_page; break; case 'edit': if ( empty( $_GET['table_id'] ) ) { TablePress::redirect( array( 'action' => 'list', 'message' => 'error_no_table' ) ); } // Load table, with table data, options, and visibility settings. $data['table'] = TablePress::$model_table->load( $_GET['table_id'], true, true ); if ( is_wp_error( $data['table'] ) ) { TablePress::redirect( array( 'action' => 'list', 'message' => 'error_load_table', 'error_details' => TablePress::get_wp_error_string( $data['table'] ) ) ); } if ( ! current_user_can( 'tablepress_edit_table', $_GET['table_id'] ) ) { wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 ); } break; case 'export': // Load all table IDs without priming the post meta cache, as table options/visibility are not needed. $table_ids = TablePress::$model_table->load_all( false ); $data['tables'] = array(); foreach ( $table_ids as $table_id ) { if ( ! current_user_can( 'tablepress_export_table', $table_id ) ) { continue; } // Load table, without table data, options, and visibility settings. $table = TablePress::$model_table->load( $table_id, false, false ); // Skip tables that could not be loaded. if ( is_wp_error( $table ) ) { continue; } $data['tables'][ $table['id'] ] = $table['name']; } $data['tables_count'] = TablePress::$model_table->count_tables(); $data['export_ids'] = ( ! empty( $_GET['table_id'] ) ) ? explode( ',', $_GET['table_id'] ) : array(); $exporter = TablePress::load_class( 'TablePress_Export', 'class-export.php', 'classes' ); $data['zip_support_available'] = $exporter->zip_support_available; $data['export_formats'] = $exporter->export_formats; $data['csv_delimiters'] = $exporter->csv_delimiters; $data['export_format'] = ( ! empty( $_GET['export_format'] ) ) ? $_GET['export_format'] : 'csv'; $data['csv_delimiter'] = ( ! empty( $_GET['csv_delimiter'] ) ) ? $_GET['csv_delimiter'] : _x( ',', 'Default CSV delimiter in the translated language (";", ",", or "tab")', 'tablepress' ); break; case 'import': // Load all table IDs without priming the post meta cache, as table options/visibility are not needed. $table_ids = TablePress::$model_table->load_all( false ); $data['tables'] = array(); foreach ( $table_ids as $table_id ) { if ( ! current_user_can( 'tablepress_edit_table', $table_id ) ) { continue; } // Load table, without table data, options, and visibility settings. $table = TablePress::$model_table->load( $table_id, false, false ); // Skip tables that could not be loaded. if ( is_wp_error( $table ) ) { continue; } $data['tables'][ $table['id'] ] = $table['name']; } $data['table_ids'] = $table_ids; // Backward compatibility for the retired "Table Auto Update" Extension, which still relies on this variable name. $data['tables_count'] = TablePress::$model_table->count_tables(); $importer = TablePress::load_class( 'TablePress_Import', 'class-import.php', 'classes' ); $data['import_type'] = ( ! empty( $_GET['import_type'] ) ) ? $_GET['import_type'] : 'add'; $data['import_existing_table'] = ( ! empty( $_GET['import_existing_table'] ) ) ? $_GET['import_existing_table'] : ''; $data['import_source'] = ( ! empty( $_GET['import_source'] ) ) ? $_GET['import_source'] : 'file-upload'; $data['import_url'] = ( ! empty( $_GET['import_url'] ) ) ? wp_unslash( $_GET['import_url'] ) : 'https://'; $data['import_server'] = ( ! empty( $_GET['import_server'] ) ) ? wp_unslash( $_GET['import_server'] ) : ABSPATH; $data['import_form-field'] = ( ! empty( $_GET['import_form-field'] ) ) ? wp_unslash( $_GET['import_form-field'] ) : ''; $data['legacy_import'] = ( ! empty( $_GET['legacy_import'] ) ) ? $_GET['legacy_import'] : 'false'; break; } /** * Filters the data that is passed to the current TablePress View. * * @since 1.0.0 * * @param array $data Data for the view. * @param string $action The current action for the view. */ $data = apply_filters( 'tablepress_view_data', $data, $action ); // Prepare and initialize the view. $this->view = TablePress::load_view( $action, $data ); } /** * Render the view that has been initialized in load_admin_page() (called by WordPress when the actual page content is needed). * * @since 1.0.0 */ public function show_admin_page(): void { $this->view->render(); } /** * Decides whether a message about Premium versions (previously, about donations) shall be shown on the "All Tables" screen, depending on passed days since installation and whether it was shown before. * * @since 1.0.0 * * @return bool Whether the message shall be shown on the "All Tables" screen. */ protected function maybe_show_donation_message(): bool { // Only show the message to plugin admins. if ( ! current_user_can( 'tablepress_edit_options' ) ) { return false; } if ( ! TablePress::$model_options->get( 'message_donation_nag' ) ) { return false; } // Determine, how long has the plugin been installed. $seconds_installed = time() - TablePress::$model_options->get( 'first_activation' ); return ( $seconds_installed > MONTH_IN_SECONDS / 2 ); } /** * Init list of actions that have a view with their titles/names/caps. * * @since 1.0.0 */ protected function init_view_actions(): void { $this->view_actions = array( 'list' => array( 'show_entry' => true, 'page_title' => __( 'All Tables', 'tablepress' ), 'admin_menu_title' => __( 'All Tables', 'tablepress' ), 'nav_tab_title' => __( 'All Tables', 'tablepress' ), 'required_cap' => 'tablepress_list_tables', ), 'add' => array( 'show_entry' => true, 'page_title' => __( 'Add New Table', 'tablepress' ), 'admin_menu_title' => __( 'Add New Table', 'tablepress' ), 'nav_tab_title' => __( 'Add New', 'tablepress' ), 'required_cap' => 'tablepress_add_tables', ), 'edit' => array( 'show_entry' => false, 'page_title' => __( 'Edit Table', 'tablepress' ), 'admin_menu_title' => '', 'nav_tab_title' => '', 'required_cap' => 'tablepress_edit_tables', ), 'import' => array( 'show_entry' => true, 'page_title' => __( 'Import a Table', 'tablepress' ), 'admin_menu_title' => __( 'Import a Table', 'tablepress' ), 'nav_tab_title' => _x( 'Import', 'navigation bar', 'tablepress' ), 'required_cap' => 'tablepress_import_tables', ), 'export' => array( 'show_entry' => true, 'page_title' => __( 'Export a Table', 'tablepress' ), 'admin_menu_title' => __( 'Export a Table', 'tablepress' ), 'nav_tab_title' => _x( 'Export', 'navigation bar', 'tablepress' ), 'required_cap' => 'tablepress_export_tables', ), 'options' => array( 'show_entry' => true, 'page_title' => __( 'Plugin Options', 'tablepress' ), 'admin_menu_title' => __( 'Plugin Options', 'tablepress' ), 'nav_tab_title' => __( 'Plugin Options', 'tablepress' ), 'required_cap' => 'tablepress_access_options_screen', ), 'about' => array( 'show_entry' => true, 'page_title' => __( 'About', 'tablepress' ), 'admin_menu_title' => __( 'About TablePress', 'tablepress' ), 'nav_tab_title' => __( 'About', 'tablepress' ), 'required_cap' => 'tablepress_access_about_screen', ), ); /** * Filters the available TablePres Views/Actions and their parameters. * * @since 1.0.0 * * @param array> $view_actions The available Views/Actions and their parameters. */ $this->view_actions = apply_filters( 'tablepress_admin_view_actions', $this->view_actions ); } /* * HTTP POST actions. */ /** * Handle Bulk Actions (Copy, Export, Delete) on "All Tables" list screen. * * @since 1.0.0 */ public function handle_post_action_list(): void { TablePress::check_nonce( 'list' ); if ( isset( $_POST['bulk-action-selector-top'] ) && '-1' !== $_POST['bulk-action-selector-top'] ) { $bulk_action = $_POST['bulk-action-selector-top']; } elseif ( isset( $_POST['bulk-action-selector-bottom'] ) && '-1' !== $_POST['bulk-action-selector-bottom'] ) { $bulk_action = $_POST['bulk-action-selector-bottom']; } else { $bulk_action = false; } if ( ! in_array( $bulk_action, array( 'copy', 'export', 'delete' ), true ) ) { TablePress::redirect( array( 'action' => 'list', 'message' => 'error_bulk_action_invalid' ) ); } if ( empty( $_POST['table'] ) || ! is_array( $_POST['table'] ) ) { TablePress::redirect( array( 'action' => 'list', 'message' => 'error_no_selection' ) ); } $tables = wp_unslash( $_POST['table'] ); $no_success = array(); // To store table IDs that failed. switch ( $bulk_action ) { case 'copy': foreach ( $tables as $table_id ) { if ( current_user_can( 'tablepress_copy_table', $table_id ) ) { $copy_table_id = TablePress::$model_table->copy( $table_id ); if ( is_wp_error( $copy_table_id ) ) { $no_success[] = $table_id; } } else { $no_success[] = $table_id; } } break; case 'export': /* * Cap check is done on redirect target page. * To export, redirect to "Export" screen, with selected table IDs. */ $table_ids = implode( ',', $tables ); TablePress::redirect( array( 'action' => 'export', 'table_id' => $table_ids ) ); // break; // unreachable. case 'delete': foreach ( $tables as $table_id ) { if ( current_user_can( 'tablepress_delete_table', $table_id ) ) { $deleted = TablePress::$model_table->delete( $table_id ); if ( is_wp_error( $deleted ) ) { $no_success[] = $table_id; } } else { $no_success[] = $table_id; } } break; } if ( 0 !== count( $no_success ) ) { // @todo maybe pass this information to the view? $message = "error_{$bulk_action}_not_all_tables"; } else { $plural = ( count( $tables ) > 1 ) ? '_plural' : ''; $message = "success_{$bulk_action}{$plural}"; } /* * Slightly more complex redirect method, to account for sort, search, and pagination in the WP_List_Table on the List View, * but only if this action succeeds, to have everything fresh in the event of an error. */ $sendback = wp_get_referer(); if ( ! $sendback ) { $sendback = TablePress::url( array( 'action' => 'list', 'message' => $message ) ); } else { $sendback = remove_query_arg( array( 'action', 'message', 'table_id' ), $sendback ); $sendback = add_query_arg( array( 'action' => 'list', 'message' => $message ), $sendback ); } wp_redirect( $sendback ); exit; } /** * Add a table, according to the parameters on the "Add new Table" screen. * * @since 1.0.0 */ public function handle_post_action_add(): void { TablePress::check_nonce( 'add' ); if ( ! current_user_can( 'tablepress_add_tables' ) ) { wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 ); } if ( empty( $_POST['table'] ) || ! is_array( $_POST['table'] ) ) { TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add', 'error_details' => 'The HTTP POST data is empty.' ) ); } $add_table = wp_unslash( $_POST['table'] ); // Perform confidence checks of posted data. $name = $add_table['name'] ?? ''; $description = $add_table['description'] ?? ''; if ( ! isset( $add_table['rows'], $add_table['columns'] ) ) { TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add', 'error_details' => 'The HTTP POST data does not contain the table size.' ) ); } $num_rows = absint( $add_table['rows'] ); $num_columns = absint( $add_table['columns'] ); if ( 0 === $num_rows || 0 === $num_columns ) { TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add', 'error_details' => 'The table size is invalid.' ) ); } // Create a new table array with information from the posted data. $new_table = array( 'name' => $name, 'description' => $description, 'data' => array_fill( 0, $num_rows, array_fill( 0, $num_columns, '' ) ), 'visibility' => array( 'rows' => array_fill( 0, $num_rows, 1 ), 'columns' => array_fill( 0, $num_columns, 1 ), ), ); // Merge this data into an empty table template. $table = TablePress::$model_table->prepare_table( TablePress::$model_table->get_table_template(), $new_table, false ); if ( is_wp_error( $table ) ) { TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add', 'error_details' => TablePress::get_wp_error_string( $table ) ) ); } // Add the new table (and get its first ID). $table_id = TablePress::$model_table->add( $table ); if ( is_wp_error( $table_id ) ) { TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add', 'error_details' => TablePress::get_wp_error_string( $table_id ) ) ); } TablePress::redirect( array( 'action' => 'edit', 'table_id' => $table_id, 'message' => 'success_add' ) ); } /** * Save changed "Plugin Options". * * @since 1.0.0 */ public function handle_post_action_options(): void { TablePress::check_nonce( 'options' ); if ( ! current_user_can( 'tablepress_access_options_screen' ) ) { wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 ); } if ( empty( $_POST['options'] ) || ! is_array( $_POST['options'] ) ) { TablePress::redirect( array( 'action' => 'options', 'message' => 'error_save' ) ); } $posted_options = wp_unslash( $_POST['options'] ); // Valid new options that will be merged into existing ones. $new_options = array(); // Check each posted option value, and (maybe) add it to the new options. if ( ! empty( $posted_options['admin_menu_parent_page'] ) && '-' !== $posted_options['admin_menu_parent_page'] ) { $new_options['admin_menu_parent_page'] = $posted_options['admin_menu_parent_page']; // Re-init parent information, as `TablePress::redirect()` URL might be wrong otherwise. /** This filter is documented in classes/class-controller.php */ TablePress::$controller->parent_page = apply_filters( 'tablepress_admin_menu_parent_page', $posted_options['admin_menu_parent_page'] ); TablePress::$controller->is_top_level_page = in_array( TablePress::$controller->parent_page, array( 'top', 'middle', 'bottom' ), true ); } // Custom CSS can only be saved if the user is allowed to do so. $update_custom_css_files = false; if ( current_user_can( 'tablepress_edit_options' ) ) { // Checkbox. $new_options['use_custom_css'] = ( isset( $posted_options['use_custom_css'] ) && 'true' === $posted_options['use_custom_css'] ); if ( isset( $posted_options['custom_css'] ) ) { $new_options['custom_css'] = $posted_options['custom_css']; $tablepress_css = TablePress::load_class( 'TablePress_CSS', 'class-css.php', 'classes' ); if ( '' !== $new_options['custom_css'] ) { // Update "Custom CSS" to use DataTables 2 variants instead of old DataTables 1.x CSS classes. $new_options['custom_css'] = TablePress::convert_datatables_api_data( $new_options['custom_css'] ); // Sanitize and tidy up Custom CSS. $new_options['custom_css'] = $tablepress_css->sanitize_css( $new_options['custom_css'] ); // Minify Custom CSS. $new_options['custom_css_minified'] = $tablepress_css->minify_css( $new_options['custom_css'] ); } else { $new_options['custom_css_minified'] = ''; } // Maybe update CSS files as well. $custom_css_file_contents = $tablepress_css->load_custom_css_from_file( 'normal' ); if ( false === $custom_css_file_contents ) { $custom_css_file_contents = ''; } // Don't write to file if it already has the desired content. if ( $new_options['custom_css'] !== $custom_css_file_contents ) { $update_custom_css_files = true; // Set to false again. As it was set here, it will be set true again, if file saving succeeds. $new_options['use_custom_css_file'] = false; } } } // Save gathered new options (will be merged into existing ones), and flush caches of caching plugins, to make sure that the new Custom CSS is used. if ( ! empty( $new_options ) ) { TablePress::$model_options->update( $new_options ); TablePress::$model_table->_flush_caching_plugins_caches(); } if ( $update_custom_css_files ) { // Capability check is performed above. TablePress::redirect( array( 'action' => 'options', 'item' => 'save_custom_css' ), true ); } TablePress::redirect( array( 'action' => 'options', 'message' => 'success_save' ) ); } /** * Export selected tables. * * @since 1.0.0 */ public function handle_post_action_export(): void { TablePress::check_nonce( 'export' ); if ( ! current_user_can( 'tablepress_export_tables' ) ) { wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 ); } if ( empty( $_POST['export'] ) || ! is_array( $_POST['export'] ) ) { TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export', 'error_details' => 'The HTTP POST data is empty.' ) ); } $export = wp_unslash( $_POST['export'] ); if ( empty( $export['tables_list'] ) ) { TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export', 'error_details' => 'The HTTP POST data does not contain tables.' ) ); } /** @var TablePress_Export $exporter */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort $exporter = TablePress::load_class( 'TablePress_Export', 'class-export.php', 'classes' ); if ( empty( $export['format'] ) || ! isset( $exporter->export_formats[ $export['format'] ] ) ) { TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export', 'error_details' => 'The export format is invalid.' ) ); } if ( empty( $export['csv_delimiter'] ) ) { // Set a value, so that the variable exists. $export['csv_delimiter'] = ''; } if ( 'csv' === $export['format'] && ! isset( $exporter->csv_delimiters[ $export['csv_delimiter'] ] ) ) { TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export', 'error_details' => 'The CSV delimiter is invalid.' ) ); } $tables = explode( ',', $export['tables_list'] ); // Determine if ZIP file support is available. if ( $exporter->zip_support_available && ( ( isset( $export['zip_file'] ) && 'true' === $export['zip_file'] ) || count( $tables ) > 1 ) ) { // Export to ZIP only if ZIP is desired or if more than one table were selected (mandatory then). $export_to_zip = true; } else { $export_to_zip = false; } if ( ! $export_to_zip ) { // Exporting without a ZIP file is only possible for one table, so take the first one. if ( ! current_user_can( 'tablepress_export_table', $tables[0] ) ) { wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 ); } // Load table, with table data, options, and visibility settings. $table = TablePress::$model_table->load( $tables[0], true, true ); if ( is_wp_error( $table ) ) { TablePress::redirect( array( 'action' => 'export', 'message' => 'error_load_table', 'export_format' => $export['format'], 'csv_delimiter' => $export['csv_delimiter'], 'error_details' => TablePress::get_wp_error_string( $table ) ) ); } if ( isset( $table['is_corrupted'] ) && $table['is_corrupted'] ) { TablePress::redirect( array( 'action' => 'export', 'message' => 'error_table_corrupted', 'export_format' => $export['format'], 'csv_delimiter' => $export['csv_delimiter'] ) ); } $download_filename = sprintf( '%1$s-%2$s-%3$s.%4$s', $table['id'], $table['name'], wp_date( 'Y-m-d' ), $export['format'] ); /** * Filters the download filename of the exported table. * * @since 2.0.0 * * @param string $download_filename The download filename of exported table. * @param string $table_id Table ID of the exported table. * @param string $table_name Table name of the exported table. * @param string $export_format Format for the export ('csv', 'html', 'json', 'zip'). * @param bool $export_to_zip Whether the export is to a ZIP file (of multiple export files). */ $download_filename = apply_filters( 'tablepress_export_filename', $download_filename, $table['id'], $table['name'], $export['format'], $export_to_zip ); $download_filename = sanitize_file_name( $download_filename ); // Export the table. $export_data = $exporter->export_table( $table, $export['format'], $export['csv_delimiter'] ); /** * Filters the exported table data. * * @since 1.6.0 * * @param string $export_data The exported table data. * @param array $table Table to be exported. * @param string $export_format Format for the export ('csv', 'html', 'json'). * @param string $csv_delimiter Delimiter for CSV export. */ $export_data = apply_filters( 'tablepress_export_data', $export_data, $table, $export['format'], $export['csv_delimiter'] ); $download_data = $export_data; } else { // Zipping can use a lot of memory and execution time, but not this much hopefully. wp_raise_memory_limit( 'admin' ); if ( function_exists( 'set_time_limit' ) ) { @set_time_limit( 300 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged } $zip_file = new ZipArchive(); $download_filename = sprintf( 'tablepress-export-%1$s-%2$s.zip', wp_date( 'Y-m-d-H-i-s' ), $export['format'] ); /** This filter is documented in controllers/controller-admin.php */ $download_filename = apply_filters( 'tablepress_export_filename', $download_filename, '', '', $export['format'], $export_to_zip ); $download_filename = sanitize_file_name( $download_filename ); $full_filename = wp_tempnam( $download_filename ); if ( true !== $zip_file->open( $full_filename, ZipArchive::OVERWRITE ) ) { @unlink( $full_filename ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged TablePress::redirect( array( 'action' => 'export', 'message' => 'error_create_zip_file', 'export_format' => $export['format'], 'csv_delimiter' => $export['csv_delimiter'], 'error_details' => 'The ZIP file could not be opened for writing.' ) ); } foreach ( $tables as $table_id ) { // Don't export tables for which the user doesn't have the necessary export rights. if ( ! current_user_can( 'tablepress_export_table', $table_id ) ) { continue; } // Load table, with table data, options, and visibility settings. $table = TablePress::$model_table->load( $table_id, true, true ); // Don't export if the table could not be loaded. if ( is_wp_error( $table ) ) { continue; } // Don't export if the table is corrupted. if ( isset( $table['is_corrupted'] ) && $table['is_corrupted'] ) { continue; } $export_data = $exporter->export_table( $table, $export['format'], $export['csv_delimiter'] ); /** This filter is documented in controllers/controller-admin.php */ $export_data = apply_filters( 'tablepress_export_data', $export_data, $table, $export['format'], $export['csv_delimiter'] ); $export_filename = sprintf( '%1$s-%2$s-%3$s.%4$s', $table['id'], $table['name'], wp_date( 'Y-m-d' ), $export['format'] ); /** This filter is documented in controllers/controller-admin.php */ $export_filename = apply_filters( 'tablepress_export_filename', $export_filename, $table['id'], $table['name'], $export['format'], $export_to_zip ); $export_filename = sanitize_file_name( $export_filename ); $zip_file->addFromString( $export_filename, $export_data ); } // If something went wrong, or no files were added to the ZIP file, bail out. // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase if ( ZipArchive::ER_OK !== $zip_file->status || 0 === $zip_file->numFiles ) { $zip_file->close(); @unlink( $full_filename ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged TablePress::redirect( array( 'action' => 'export', 'message' => 'error_create_zip_file', 'export_format' => $export['format'], 'csv_delimiter' => $export['csv_delimiter'], 'error_details' => 'The ZIP file could not be written or is empty.' ) ); } $zip_file->close(); // Load contents of the ZIP file, to send it as a download. $download_data = file_get_contents( $full_filename ); if ( false === $download_data ) { @unlink( $full_filename ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged TablePress::redirect( array( 'action' => 'export', 'message' => 'error_create_zip_file', 'export_format' => $export['format'], 'csv_delimiter' => $export['csv_delimiter'], 'error_details' => 'The ZIP file content could not be read.' ) ); } @unlink( $full_filename ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged } // Send download headers for export file. header( 'Content-Description: File Transfer' ); header( 'Content-Type: application/octet-stream' ); header( "Content-Disposition: attachment; filename=\"{$download_filename}\"" ); header( 'Content-Transfer-Encoding: binary' ); header( 'Expires: 0' ); header( 'Cache-Control: must-revalidate' ); header( 'Pragma: public' ); header( 'Content-Length: ' . strlen( $download_data ) ); // $filetype = text/csv, text/html, application/json // header( 'Content-Type: ' . $filetype. '; charset=' . get_option( 'blog_charset' ) ); @ob_end_clean(); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged flush(); echo $download_data; exit; } /** * Import data from existing source (Upload, URL, Server, Direct input). * * @since 1.0.0 */ public function handle_post_action_import(): void { TablePress::check_nonce( 'import' ); if ( ! current_user_can( 'tablepress_import_tables' ) ) { wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 ); } if ( empty( $_POST['import'] ) || ! is_array( $_POST['import'] ) ) { TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'The HTTP POST data is empty.' ) ); } $import_config = wp_unslash( $_POST['import'] ); if ( empty( $import_config['source'] ) ) { TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'The HTTP POST does not contain an import configuration.' ) ); } // For security reasons, the "server" source is only available for super admins on multisite and admins on single sites. if ( 'server' === $import_config['source'] ) { if ( ! is_super_admin() && ! ( ! is_multisite() && current_user_can( 'manage_options' ) ) ) { TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'You do not have the required access rights.' ) ); } } // For security reasons, the "url" source is only available admins and editors via a custom capability. if ( 'url' === $import_config['source'] ) { if ( ! current_user_can( 'tablepress_import_tables_url' ) ) { TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'You do not have the required access rights.' ) ); } } // Move file upload data to the main import configuration. $import_config['file-upload'] = $_FILES['import_file_upload'] ?? null; // Check if the source data for the chosen import source is defined. if ( empty( $import_config[ $import_config['source'] ] ) ) { TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'The HTTP POST data does not contain an import source.' ) ); } // Set default values for non-essential configuration variables. if ( ! isset( $import_config['type'] ) ) { $import_config['type'] = 'add'; } if ( ! isset( $import_config['existing_table'] ) ) { $import_config['existing_table'] = ''; } $import_config['legacy_import'] = ( isset( $import_config['legacy_import'] ) && 'true' === $import_config['legacy_import'] ); $importer = TablePress::load_class( 'TablePress_Import', 'class-import.php', 'classes' ); $import = $importer->run( $import_config ); if ( is_wp_error( $import ) || 0 < count( $import['errors'] ) ) { $redirect_parameters = array( 'action' => 'import', 'message' => 'error_import', 'import_type' => $import_config['type'], 'import_existing_table' => $import_config['existing_table'], 'import_source' => $import_config['source'], 'legacy_import' => $import_config['legacy_import'], ); if ( in_array( $import_config['source'], array( 'url', 'server' ), true ) ) { $redirect_parameters[ "import_{$import_config['source']}" ] = $import_config[ $import_config['source'] ]; } if ( is_wp_error( $import ) ) { $redirect_parameters['error_details'] = TablePress::get_wp_error_string( $import ); } elseif ( 0 < count( $import['errors'] ) ) { $wp_error_strings = array(); foreach ( $import['errors'] as $file ) { $wp_error_strings[] = TablePress::get_wp_error_string( $file->error ); } $redirect_parameters['error_details'] = implode( ', ', $wp_error_strings ); } TablePress::redirect( $redirect_parameters ); } // At this point, there were no import errors. if ( count( $import['tables'] ) > 1 ) { TablePress::redirect( array( 'action' => 'list', 'message' => 'success_import' ) ); } elseif ( 1 === count( $import['tables'] ) ) { TablePress::redirect( array( 'action' => 'edit', 'table_id' => $import['tables'][0]['id'], 'message' => 'success_import' ) ); } else { TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'The number of imported tables is invalid.' ) ); } } /* * HTTP GET actions. */ /** * Hide a header message on an admin screen. * * @since 1.0.0 */ public function handle_get_action_hide_message(): void { $message_item = ! empty( $_GET['item'] ) ? $_GET['item'] : ''; TablePress::check_nonce( 'hide_message', $message_item ); if ( ! current_user_can( 'tablepress_list_tables' ) ) { wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 ); } TablePress::$model_options->update( "message_{$message_item}", false ); $return = ! empty( $_GET['return'] ) ? $_GET['return'] : 'list'; TablePress::redirect( array( 'action' => $return ) ); } /** * Delete a table. * * @since 1.0.0 */ public function handle_get_action_delete_table(): void { $table_id = ( ! empty( $_GET['item'] ) ) ? $_GET['item'] : false; TablePress::check_nonce( 'delete_table', $table_id ); $return = ! empty( $_GET['return'] ) ? $_GET['return'] : 'list'; $return_item = ! empty( $_GET['return_item'] ) ? $_GET['return_item'] : false; // The nonce check should actually catch this already. if ( false === $table_id ) { TablePress::redirect( array( 'action' => $return, 'message' => 'error_delete', 'table_id' => $return_item ) ); } if ( ! current_user_can( 'tablepress_delete_table', $table_id ) ) { wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 ); } $deleted = TablePress::$model_table->delete( $table_id ); if ( is_wp_error( $deleted ) ) { TablePress::redirect( array( 'action' => $return, 'message' => 'error_delete', 'table_id' => $return_item, 'error_details' => TablePress::get_wp_error_string( $deleted ) ) ); } /* * Slightly more complex redirect method, to account for sort, search, and pagination in the WP_List_Table on the List View, * but only if this action succeeds, to have everything fresh in the event of an error. */ $sendback = wp_get_referer(); if ( ! $sendback ) { $sendback = TablePress::url( array( 'action' => 'list', 'message' => 'success_delete', 'table_id' => $return_item ) ); } else { $sendback = remove_query_arg( array( 'action', 'message', 'table_id' ), $sendback ); $sendback = add_query_arg( array( 'action' => 'list', 'message' => 'success_delete', 'table_id' => $return_item ), $sendback ); } wp_redirect( $sendback ); exit; } /** * Copy a table. * * @since 1.0.0 */ public function handle_get_action_copy_table(): void { $table_id = ( ! empty( $_GET['item'] ) ) ? $_GET['item'] : false; TablePress::check_nonce( 'copy_table', $table_id ); $return = ! empty( $_GET['return'] ) ? $_GET['return'] : 'list'; $return_item = ! empty( $_GET['return_item'] ) ? $_GET['return_item'] : false; // The nonce check should actually catch this already. if ( false === $table_id ) { TablePress::redirect( array( 'action' => $return, 'message' => 'error_copy', 'table_id' => $return_item ) ); } if ( ! current_user_can( 'tablepress_copy_table', $table_id ) ) { wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 ); } $copy_table_id = TablePress::$model_table->copy( $table_id ); if ( is_wp_error( $copy_table_id ) ) { TablePress::redirect( array( 'action' => $return, 'message' => 'error_copy', 'table_id' => $return_item, 'error_details' => TablePress::get_wp_error_string( $copy_table_id ) ) ); } $return_item = $copy_table_id; /* * Slightly more complex redirect method, to account for sort, search, and pagination in the WP_List_Table on the List View, * but only if this action succeeds, to have everything fresh in the event of an error. */ $sendback = wp_get_referer(); if ( ! $sendback ) { $sendback = TablePress::url( array( 'action' => $return, 'message' => 'success_copy', 'table_id' => $return_item ) ); } else { $sendback = remove_query_arg( array( 'action', 'message', 'table_id' ), $sendback ); $sendback = add_query_arg( array( 'action' => $return, 'message' => 'success_copy', 'table_id' => $return_item ), $sendback ); } wp_redirect( $sendback ); exit; } /** * Preview a table. * * @since 1.0.0 */ public function handle_get_action_preview_table(): void { $table_id = ( ! empty( $_GET['item'] ) ) ? $_GET['item'] : false; TablePress::check_nonce( 'preview_table', $table_id ); // Nonce check should actually catch this already. if ( false === $table_id ) { wp_die( __( 'The preview could not be loaded.', 'tablepress' ), __( 'Preview', 'tablepress' ) ); } if ( ! current_user_can( 'tablepress_preview_table', $table_id ) ) { wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 ); } // Load table, with table data, options, and visibility settings. $table = TablePress::$model_table->load( $table_id, true, true ); if ( is_wp_error( $table ) ) { wp_die( __( 'The table could not be loaded.', 'tablepress' ), __( 'Preview', 'tablepress' ) ); } // Sanitize all table data to remove unsafe HTML from the preview output, if the user is not allowed to work with unfiltered HTML. if ( ! current_user_can( 'unfiltered_html' ) ) { $table = TablePress::$model_table->sanitize( $table ); } // Create a render class instance. $_render = TablePress::load_class( 'TablePress_Render', 'class-render.php', 'classes' ); // Merge desired options with default render options (see TablePress_Controller_Frontend::shortcode_table()). $default_render_options = $_render->get_default_render_options(); /** This filter is documented in controllers/controller-frontend.php */ $default_render_options = apply_filters( 'tablepress_shortcode_table_default_shortcode_atts', $default_render_options ); $render_options = shortcode_atts( $default_render_options, $table['options'] ); /** This filter is documented in controllers/controller-frontend.php */ $render_options = apply_filters( 'tablepress_shortcode_table_shortcode_atts', $render_options ); $render_options['html_id'] = "tablepress-{$table['id']}"; $render_options['block_preview'] = true; $_render->set_input( $table, $render_options ); $view_data = array( 'table_id' => $table_id, 'head_html' => $_render->get_preview_css(), 'body_html' => $_render->get_output( 'html' ), 'site_used_editor' => TablePress::site_used_editor(), ); $custom_css = TablePress::$model_options->get( 'custom_css' ); $use_custom_css = ( TablePress::$model_options->get( 'use_custom_css' ) && '' !== $custom_css ); if ( $use_custom_css ) { $view_data['head_html'] .= "\n"; } // Prepare, initialize, and render the view. $this->view = TablePress::load_view( 'preview_table', $view_data ); $this->view->render(); } /** * Shows a list of tables in the Editor toolbar Thickbox (opened by TinyMCE or Quicktags button). * * @since 1.0.0 */ public function handle_get_action_editor_button_thickbox(): void { TablePress::check_nonce( 'editor_button_thickbox' ); if ( ! current_user_can( 'tablepress_list_tables' ) ) { wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 ); } $view_data = array( // Load all table IDs without priming the post meta cache, as table options/visibility are not needed. 'table_ids' => TablePress::$model_table->load_all( false ), ); set_current_screen( 'tablepress_editor_button_thickbox' ); // Prepare, initialize, and render the view. $this->view = TablePress::load_view( 'editor_button_thickbox', $view_data ); $this->view->render(); } /** * Uninstall TablePress, and delete all tables and options. * * @since 1.0.0 */ public function handle_get_action_uninstall_tablepress(): void { TablePress::check_nonce( 'uninstall_tablepress' ); $plugin = TABLEPRESS_BASENAME; if ( ! current_user_can( 'deactivate_plugin', $plugin ) || ! current_user_can( 'tablepress_edit_options' ) || ! current_user_can( 'tablepress_delete_tables' ) || is_plugin_active_for_network( $plugin ) ) { wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 ); } // Deactivate TablePress for the site (but not for the network). deactivate_plugins( $plugin, false, false ); update_option( 'recently_activated', array( $plugin => time() ) + (array) get_option( 'recently_activated', array() ) ); // Delete all tables, "Custom CSS" files, and options. TablePress::$model_table->delete_all(); $tablepress_css = TablePress::load_class( 'TablePress_CSS', 'class-css.php', 'classes' ); $css_files_deleted = $tablepress_css->delete_custom_css_files(); TablePress::$model_options->remove_access_capabilities(); TablePress::$model_table->destroy(); TablePress::$model_options->destroy(); $output = '' . __( 'TablePress was uninstalled successfully.', 'tablepress' ) . '

'; $output .= __( 'All tables, data, and options were deleted.', 'tablepress' ); if ( is_multisite() ) { $output .= ' ' . __( 'You may now ask the network admin to delete the plugin’s folder tablepress from the server, if no other site in the network uses it.', 'tablepress' ); } else { $output .= ' ' . __( 'You may now manually delete the plugin’s folder tablepress from the plugins directory on your server or use the “Delete” link for TablePress on the WordPress “Plugins” page.', 'tablepress' ); } if ( $css_files_deleted ) { $output .= ' ' . __( 'Your TablePress “Custom CSS” files have been deleted automatically.', 'tablepress' ); } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found if ( is_multisite() ) { $output .= ' ' . __( 'Please also ask him to delete your TablePress “Custom CSS” files from the server.', 'tablepress' ); } else { $output .= ' ' . __( 'You may now also delete your TablePress “Custom CSS” files in the wp-content folder.', 'tablepress' ); } } $output .= "

\n

"; if ( ! is_multisite() || is_super_admin() ) { $output .= '' . __( 'Go to “Plugins” page', 'tablepress' ) . ' '; } $output .= '' . __( 'Go to Dashboard', 'tablepress' ) . ''; wp_die( $output, __( 'Uninstall TablePress', 'tablepress' ), array( 'response' => 200, 'back_link' => false ) ); } } // class TablePress_Admin_Controller PK!+Cf4c c controllers/freemius-filters.phpnu[add_filter( 'plugin_icon', static function ( $icon ) /* No return type declaration or type hints, due to required PHP compatibility of this file! */ { $icon = dirname( __DIR__ ) . '/admin/img/tablepress-icon.svg'; return $icon; } // No trailing comma in function call, due to required PHP compatibility of this file! ); // Load the TablePress style customizations for the Freemius Pricing screen. tb_tp_fs()->add_filter( 'pricing/css_path', static function ( $path ) /* No return type declaration or type hints, due to required PHP compatibility of this file! */ { $path = dirname( __DIR__ ) . '/admin/css/build/freemius-pricing.css'; return $path; } // No trailing comma in function call, due to required PHP compatibility of this file! ); // Hide the "Pricing" menu entry for users with a valid and active premium license. tb_tp_fs()->add_filter( 'is_submenu_visible', static function ( $is_visible, $menu_id ) /* No return type declaration or type hints, due to required PHP compatibility of this file! */ { if ( 'pricing' === $menu_id ) { $is_visible = ! TABLEPRESS_IS_PLAYGROUND_PREVIEW && ! tb_tp_fs()->is_paying_or_trial() && current_user_can( 'tablepress_list_tables' ); } return $is_visible; }, 10, 2 // No trailing comma in function call, due to required PHP compatibility of this file! ); // Show only annual but not calculated monthly prices on the "Pricing" page. tb_tp_fs()->add_filter( 'pricing/show_annual_in_monthly', '__return_false' ); // Determine the default currency based on the top-level domain (TLD) of the site URL. tb_tp_fs()->add_filter( 'default_currency', static function ( $currency ) /* No return type declaration or type hints, due to required PHP compatibility of this file! */ { $host = wp_parse_url( site_url(), PHP_URL_HOST ); if ( is_string( $host ) ) { $tld = strtolower( pathinfo( $host, PATHINFO_EXTENSION ) ); if ( in_array( $tld, array( 'at', 'be', 'bg', 'cy', 'cz', 'de', 'dk', 'ee', 'es', 'fi', 'fr', 'gr', 'hu', 'ie', 'it', 'lt', 'lu', 'lv', 'mt', 'nl', 'pl', 'pt', 'ro', 'se', 'si', 'sk', 'uk' ), true ) ) { $currency = 'eur'; } } return $currency; } // No trailing comma in function call, due to required PHP compatibility of this file! ); // Hide the tabs navigation on Freemius screens. tb_tp_fs()->add_filter( 'hide_account_tabs', '__return_true' ); // Use different arrow icons in the admin menu. tb_tp_fs()->override_i18n( array( 'symbol_arrow-left' => '←', 'symbol_arrow-right' => '→', ) ); PK!j)P<<%controllers/controller-admin_ajax.phpnu[update( "message_{$message_item}", false ); wp_die( '1' ); } /** * Saves the table after the "Save Changes" button on the "Edit" screen has been clicked. * * @since 1.0.0 */ public function ajax_action_save_table(): void { if ( empty( $_POST['tablepress']['id'] ) ) { wp_die( '-1' ); } $edit_table = wp_unslash( $_POST['tablepress'] ); // Check if the submitted nonce matches the generated nonce we created earlier, dies -1 on failure. TablePress::check_nonce( 'edit', $edit_table['id'], '_ajax_nonce', true ); // Ignore the request if the current user doesn't have sufficient permissions. if ( ! current_user_can( 'tablepress_edit_table', $edit_table['id'] ) ) { wp_die( '-1' ); } // Default response data. $success = false; $message = 'error_save'; $error_details = ''; do { // To be able to "break;" (allows for better readable code). // Load table, without table data, but with options and visibility settings. $existing_table = TablePress::$model_table->load( $edit_table['id'], false, true ); if ( is_wp_error( $existing_table ) ) { $error = new WP_Error( 'ajax_save_table_load', '', $edit_table['id'] ); $error->merge_from( $existing_table ); $error_details = TablePress::get_wp_error_string( $error ); break; } // Check and convert all data that was transmitted as valid JSON. $keys = array( 'data', 'options', 'visibility' ); foreach ( $keys as $key ) { if ( empty( $edit_table[ $key ] ) ) { $error = new WP_Error( "ajax_save_table_{$key}_empty", '', $edit_table['id'] ); $error_details = TablePress::get_wp_error_string( $error ); break 2; } $edit_table[ $key ] = json_decode( $edit_table[ $key ], true ); if ( is_null( $edit_table[ $key ] ) ) { $error = new WP_Error( "ajax_save_table_{$key}_invalid_json", '', $edit_table['id'] ); $error_details = TablePress::get_wp_error_string( $error ); break 2; } $edit_table[ $key ] = (array) $edit_table[ $key ]; // Cast to array again, to catch strings, etc. } // Check consistency of new table, and then merge with existing table. $table = TablePress::$model_table->prepare_table( $existing_table, $edit_table, true ); if ( is_wp_error( $table ) ) { $error = new WP_Error( 'ajax_save_table_prepare', '', $edit_table['id'] ); $error->merge_from( $table ); $error_details = TablePress::get_wp_error_string( $error ); break; } // DataTables Custom Commands can only be edited by trusted users. if ( ! current_user_can( 'unfiltered_html' ) ) { $table['options']['datatables_custom_commands'] = $existing_table['options']['datatables_custom_commands']; } // Save updated table. $saved = TablePress::$model_table->save( $table ); if ( is_wp_error( $saved ) ) { $error = new WP_Error( 'ajax_save_table_save', '', $table['id'] ); $error->merge_from( $saved ); $error_details = TablePress::get_wp_error_string( $error ); break; } // At this point, the table was saved successfully, possible ID change remains. $success = true; $message = 'success_save'; // Check if ID change is desired. if ( $table['id'] === $table['new_id'] ) { // If not, we are done. break; } // Change table ID. if ( current_user_can( 'tablepress_edit_table_id', $table['id'] ) ) { $id_changed = TablePress::$model_table->change_table_id( $table['id'], $table['new_id'] ); if ( ! is_wp_error( $id_changed ) ) { $message = 'success_save_success_id_change'; $table['id'] = $table['new_id']; } else { $message = 'success_save_error_id_change'; $error = new WP_Error( 'ajax_save_table_id_change', '', $table['new_id'] ); $error->merge_from( $id_changed ); $error_details = TablePress::get_wp_error_string( $error ); } } else { $message = 'success_save_error_id_change'; $error_details = 'table_id_could_not_be_changed: capability_check_failed'; } // @phpstan-ignore doWhile.alwaysFalse } while ( false ); // Do-while-loop through this exactly once, to be able to "break;" early. // Generate the response. // Common data for all responses. $response = array( 'success' => $success, 'message' => $message, ); if ( $success ) { // For the phpstan ignores in the next lines: If this is reached, $table is guaranteed to exist and is a valid array. $response['table_id'] = $table['id']; // @phpstan-ignore offsetAccess.nonOffsetAccessible, variable.undefined $response['new_edit_nonce'] = wp_create_nonce( TablePress::nonce( 'edit', $table['id'] ) ); // @phpstan-ignore offsetAccess.nonOffsetAccessible, variable.undefined $response['new_preview_nonce'] = wp_create_nonce( TablePress::nonce( 'preview_table', $table['id'] ) ); // @phpstan-ignore offsetAccess.nonOffsetAccessible, variable.undefined $response['new_copy_nonce'] = wp_create_nonce( TablePress::nonce( 'copy_table', $table['id'] ) ); // @phpstan-ignore offsetAccess.nonOffsetAccessible, variable.undefined $response['new_delete_nonce'] = wp_create_nonce( TablePress::nonce( 'delete_table', $table['id'] ) ); // @phpstan-ignore offsetAccess.nonOffsetAccessible, variable.undefined $response['last_modified'] = TablePress::format_datetime( $table['last_modified'] ); // @phpstan-ignore offsetAccess.nonOffsetAccessible, variable.undefined $response['last_editor'] = TablePress::get_user_display_name( $table['options']['last_editor'] ); // @phpstan-ignore offsetAccess.nonOffsetAccessible, variable.undefined } if ( ! empty( $error_details ) ) { $response['error_details'] = esc_html( $error_details ); } // Buffer all outputs, to prevent errors/warnings being printed that make the JSON invalid. $output_buffer = ob_get_clean(); if ( ! empty( $output_buffer ) ) { $response['output_buffer'] = $output_buffer; } // Send the response. wp_send_json( $response ); } /** * Returns the live preview data of table that has non-saved changes. * * @since 1.0.0 */ public function ajax_action_preview_table(): void { if ( empty( $_POST['tablepress']['id'] ) ) { wp_die( '-1' ); } $preview_table = wp_unslash( $_POST['tablepress'] ); // Check if the submitted nonce matches the generated nonce we created earlier, dies -1 on failure. TablePress::check_nonce( 'preview_table', $preview_table['id'], '_ajax_nonce', true ); // Ignore the request if the current user doesn't have sufficient permissions. if ( ! current_user_can( 'tablepress_preview_table', $preview_table['id'] ) ) { wp_die( '-1' ); } // Default response data. $success = false; do { // To be able to "break;" (allows for better readable code). // Load table, without table data, but with options and visibility settings. $existing_table = TablePress::$model_table->load( $preview_table['id'], false, true ); if ( is_wp_error( $existing_table ) ) { break; } // Check and convert all data that was transmitted as valid JSON. $keys = array( 'data', 'options', 'visibility' ); foreach ( $keys as $key ) { if ( empty( $preview_table[ $key ] ) ) { break 2; } $preview_table[ $key ] = json_decode( $preview_table[ $key ], true ); if ( is_null( $preview_table[ $key ] ) ) { break 2; } $preview_table[ $key ] = (array) $preview_table[ $key ]; // Cast to array again, to catch strings, etc. } // Check consistency of new table, and then merge with existing table. $table = TablePress::$model_table->prepare_table( $existing_table, $preview_table, true ); if ( is_wp_error( $table ) ) { break; } // DataTables Custom Commands can only be edited by trusted users. if ( ! current_user_can( 'unfiltered_html' ) ) { $table['options']['datatables_custom_commands'] = $existing_table['options']['datatables_custom_commands']; } // If the ID has changed, and the new ID is valid, render with the new ID (important e.g. for CSS classes/HTML ID). if ( $table['id'] !== $table['new_id'] && 0 === preg_match( '/[^a-zA-Z0-9_-]/', $table['new_id'] ) ) { $table['id'] = $table['new_id']; } // Sanitize all table data to remove unsafe HTML from the preview output, if the user is not allowed to work with unfiltered HTML. if ( ! current_user_can( 'unfiltered_html' ) ) { $table = TablePress::$model_table->sanitize( $table ); } // At this point, the table data is valid and sanitized and can be rendered. $success = true; // @phpstan-ignore doWhile.alwaysFalse } while ( false ); // Do-while-loop through this exactly once, to be able to "break;" early. if ( $success ) { // Create a render class instance. $_render = TablePress::load_class( 'TablePress_Render', 'class-render.php', 'classes' ); // Merge desired options with default render options (see TablePress_Controller_Frontend::shortcode_table()). $default_render_options = $_render->get_default_render_options(); /** This filter is documented in controllers/controller-frontend.php */ $default_render_options = apply_filters( 'tablepress_shortcode_table_default_shortcode_atts', $default_render_options ); // For the phpstan ignores in the next lines: If this is reached, $table is guaranteed to exist and is a valid array. $render_options = shortcode_atts( $default_render_options, $table['options'] ); // @phpstan-ignore offsetAccess.nonOffsetAccessible, variable.undefined /** This filter is documented in controllers/controller-frontend.php */ $render_options = apply_filters( 'tablepress_shortcode_table_shortcode_atts', $render_options ); $render_options['html_id'] = "tablepress-{$table['id']}"; // @phpstan-ignore offsetAccess.nonOffsetAccessible, variable.undefined $render_options['block_preview'] = true; $_render->set_input( $table, $render_options ); // @phpstan-ignore variable.undefined $head_html = $_render->get_preview_css(); $custom_css = TablePress::$model_options->get( 'custom_css' ); $use_custom_css = ( TablePress::$model_options->get( 'use_custom_css' ) && '' !== $custom_css ); if ( $use_custom_css ) { $head_html .= "\n"; } $body_html = '

' . __( 'This is a preview of your table.', 'tablepress' ) . ' ' . __( 'Because of CSS styling in your theme, the table might look different on your page!', 'tablepress' ) . ' ' . __( 'The Table Features for Site Visitors, like sorting, filtering, and pagination, are also not available in this preview!', 'tablepress' ) . '
'; // Show the instructions string depending on whether the Block Editor is used on the site or not. if ( 'block' === TablePress::site_used_editor() ) { $body_html .= sprintf( __( 'To insert a table into a post or page, add a “%1$s” block in the block editor and select the desired table.', 'tablepress' ), __( 'TablePress table', 'tablepress' ) ); } elseif ( 'elementor' === TablePress::site_used_editor() ) { $body_html .= sprintf( __( 'To insert a table into a post or page, add a “%1$s” widget in the Elementor editor and select the desired table.', 'tablepress' ), __( 'TablePress table', 'tablepress' ) ); } else { $body_html .= __( 'To insert a table into a post or page, paste its Shortcode at the desired place in the editor.', 'tablepress' ) . ' ' . __( 'Each table has a unique ID that needs to be adjusted in that Shortcode.', 'tablepress' ); } $body_html .= '

' . $_render->get_output( 'html' ) . '
'; } else { $head_html = ''; $body_html = __( 'The preview could not be loaded.', 'tablepress' ); } // Generate the response. $response = array( 'success' => $success, 'head_html' => $head_html, 'body_html' => $body_html, ); // Buffer all outputs, to prevent errors/warnings being printed that make the JSON invalid. $output_buffer = ob_get_clean(); if ( ! empty( $output_buffer ) ) { $response['output_buffer'] = $output_buffer; } // Send the response. wp_send_json( $response ); } /** * Saves the screen options on the "Edit" screen when they are changed. * * @since 2.1.0 */ public function ajax_action_save_screen_options(): void { // Check if the submitted nonce matches the generated nonce we created earlier, dies -1 on failure. TablePress::check_nonce( 'screen_options', false, '_ajax_nonce', true ); if ( empty( $_POST['tablepress'] ) ) { wp_die( '-1' ); } $screen_options = wp_unslash( $_POST['tablepress'] ); // Sanitize and limit values to a minimum and a maximum. $new_screen_options = array(); if ( isset( $screen_options['table_editor_column_width'] ) ) { $new_screen_options['table_editor_column_width'] = absint( $screen_options['table_editor_column_width'] ); $new_screen_options['table_editor_column_width'] = max( $new_screen_options['table_editor_column_width'], 30 ); // Minimum width: 30 pixels. $new_screen_options['table_editor_column_width'] = min( $new_screen_options['table_editor_column_width'], 9999 ); // Maximum width: 9999 pixels. } if ( isset( $screen_options['table_editor_line_clamp'] ) ) { $new_screen_options['table_editor_line_clamp'] = absint( $screen_options['table_editor_line_clamp'] ); $new_screen_options['table_editor_line_clamp'] = min( $new_screen_options['table_editor_line_clamp'], 999 ); // Maximum lines: 999. Minimum of 0 (for all lines) is ensured by absint(). } if ( empty( $new_screen_options ) ) { wp_die( '-1' ); } TablePress::$model_options->update( $new_screen_options ); // Generate the response. $response = array( 'success' => true, ); // Buffer all outputs, to prevent errors/warnings being printed that make the JSON invalid. $output_buffer = ob_get_clean(); if ( ! empty( $output_buffer ) ) { $response['output_buffer'] = $output_buffer; } // Send the response. wp_send_json( $response ); } } // class TablePress_Admin_AJAX_Controller PK! H H &controllers/template-tag-functions.phpnu[ $table_query Query-string-like list or array of parameters for Shortcode "table" rendering. * @return string HTML of the rendered table. */ function tablepress_get_table( /* string|array */ $table_query ): string { if ( is_array( $table_query ) ) { $atts = $table_query; } else { parse_str( (string) $table_query, $atts ); } return TablePress::$controller->shortcode_table( $atts ); // @phpstan-ignore argument.type } /** * Provides template tag functionality for the "table" Shortcode, to be used anywhere in the template, echoes the table HTML. * * @since 1.0.0 * * @see tablepress_get_table() * * @param string|array $table_query Query-string-like list or array of parameters for Shortcode "table" rendering. */ function tablepress_print_table( /* string|array */ $table_query ): void { echo tablepress_get_table( $table_query ); } /** * Provides template tag functionality for the "table-info" Shortcode, to be used anywhere in the template, returns the info. * * @since 1.0.0 * * @param string|array $table_query Query-string-like list or array of parameters for Shortcode "table-info" rendering. * @return string Desired table information. */ function tablepress_get_table_info( /* string|array */ $table_query ): string { if ( is_array( $table_query ) ) { $atts = $table_query; } else { parse_str( (string) $table_query, $atts ); } return TablePress::$controller->shortcode_table_info( $atts ); // @phpstan-ignore argument.type } /** * Provides template tag functionality for the "table-info" Shortcode, to be used anywhere in the template, echoes the info. * * @since 1.0.0 * * @see tablepress_get_table_info() * * @param string|array $table_query Query-string-like list or array of parameters for Shortcode "table-info" rendering. */ function tablepress_print_table_info( /* string|array */ $table_query ): void { echo tablepress_get_table_info( $table_query ); } PK!FNFF license.txtnu[ GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. PK!Q8ͺclasses/class-render.phpnu[ */ protected array $table = array(); /** * Table options that influence the output result. * * @since 1.0.0 * @var array */ protected array $render_options = array(); /** * Rendered HTML code of the table or PHP array. * * @since 1.0.0 * @var string|array> */ protected $output; /** * Trigger words for colspan, rowspan, or the combination of both. * * @since 1.0.0 * @var array */ protected array $span_trigger = array( 'colspan' => '#colspan#', 'rowspan' => '#rowspan#', 'span' => '#span#', ); /** * Buffer to store the counts of rowspan per column, initialized in _render_table(). * * @since 1.0.0 * @var int[] */ protected array $rowspan = array(); /** * Buffer to store the counts of colspan per row, initialized in _render_table(). * * @since 1.0.0 * @var int[] */ protected array $colspan = array(); /** * Whether the table has connected cells (colspan or rowspan), set in _render_table(). * * @since 3.0.0 */ protected bool $tbody_has_connected_cells = false; /** * Index of the last row of the visible data in the table, set in _render_table(). * * @since 1.0.0 */ protected int $last_row_idx; /** * Index of the last column of the visible data in the table, set in _render_table(). * * @since 1.0.0 */ protected int $last_column_idx; /** * Class constructor. * * @since 1.0.0 */ public function __construct() { // Unused. } /** * Set the table (data, options, visibility, ...) that is to be rendered. * * @since 1.0.0 * * @param array $table Table to be rendered. * @param array $render_options Options for rendering, from both "Edit" screen and Shortcode. */ public function set_input( array $table, array $render_options ): void { $this->table = $table; $this->render_options = $render_options; /** * Filters the table before the render process. * * @since 1.0.0 * * @param array $table The table. * @param array $render_options The render options for the table. */ $this->table = apply_filters( 'tablepress_table_raw_render_data', $this->table, $this->render_options ); } /** * Process the table rendering and return the HTML output. * * @since 1.0.0 * @since 2.0.0 Add the $format parameter. * * @param string $format Optional. Output format, 'html' (default) or 'array'. * @return string|array> HTML code of the rendered table, or a PHP array, or an error message. */ public function get_output( string $format = 'html' ) /* : string|array */ { // Evaluate math expressions/formulas. $this->_evaluate_table_data(); // Remove hidden rows and columns. $this->_prepare_render_data(); if ( 'html' !== $format ) { add_filter( 'tablepress_cell_content', 'wptexturize' ); } // Evaluate Shortcodes and escape cell content. $this->_process_render_data(); if ( 'html' !== $format ) { remove_filter( 'tablepress_cell_content', 'wptexturize' ); } switch ( $format ) { case 'html': $this->_render_table(); break; case 'array': $this->output = $this->table['data']; break; } return $this->output; } /** * Loop through the table to evaluate math expressions/formulas. * * @since 1.0.0 */ protected function _evaluate_table_data(): void { $orig_table = $this->table; if ( $this->render_options['evaluate_formulas'] ) { $formula_evaluator = TablePress::load_class( 'TablePress_Evaluate', 'class-evaluate.php', 'classes' ); $this->table['data'] = $formula_evaluator->evaluate_table_data( $this->table['data'], $this->table['id'] ); } /** * Filters the table after evaluating formulas in the table. * * @since 1.0.0 * * @param array $table The table with evaluated formulas. * @param array $orig_table The table with unevaluated formulas. * @param array $render_options The render options for the table. */ $this->table = apply_filters( 'tablepress_table_evaluate_data', $this->table, $orig_table, $this->render_options ); } /** * Remove all cells from the data set that shall not be rendered, because they are hidden. * * @since 1.0.0 */ protected function _prepare_render_data(): void { $orig_table = $this->table; $num_rows = count( $this->table['data'] ); $num_columns = ( $num_rows > 0 ) ? count( $this->table['data'][0] ) : 0; // Evaluate show/hide_rows/columns parameters. $actions = array( 'show', 'hide' ); $elements = array( 'rows', 'columns' ); foreach ( $actions as $action ) { foreach ( $elements as $element ) { if ( empty( $this->render_options[ "{$action}_{$element}" ] ) ) { $this->render_options[ "{$action}_{$element}" ] = array(); continue; } // Add all rows/columns to array if "all" value set for one of the four parameters. if ( 'all' === $this->render_options[ "{$action}_{$element}" ] ) { $this->render_options[ "{$action}_{$element}" ] = range( 0, ${'num_' . $element} - 1 ); continue; } // We have a list of rows/columns (possibly with ranges in it). $this->render_options[ "{$action}_{$element}" ] = explode( ',', $this->render_options[ "{$action}_{$element}" ] ); // Support for ranges like 3-6 or A-BA. $range_cells = array(); foreach ( $this->render_options[ "{$action}_{$element}" ] as $key => $value ) { $range_dash = strpos( $value, '-' ); if ( false !== $range_dash ) { unset( $this->render_options[ "{$action}_{$element}" ][ $key ] ); $start = trim( substr( $value, 0, $range_dash ) ); if ( ! is_numeric( $start ) ) { $start = TablePress::letter_to_number( $start ); } $end = trim( substr( $value, $range_dash + 1 ) ); if ( ! is_numeric( $end ) ) { $end = TablePress::letter_to_number( $end ); } $current_range = range( $start, $end ); $range_cells = array_merge( $range_cells, $current_range ); } } $this->render_options[ "{$action}_{$element}" ] = array_merge( $this->render_options[ "{$action}_{$element}" ], $range_cells ); /* * Parse single letters and change from regular numbering to zero-based numbering, * as rows/columns are indexed from 0 internally, but from 1 externally. */ foreach ( $this->render_options[ "{$action}_{$element}" ] as $key => $value ) { $value = trim( $value ); if ( ! is_numeric( $value ) ) { $value = TablePress::letter_to_number( $value ); } $this->render_options[ "{$action}_{$element}" ][ $key ] = (int) $value - 1; } // Remove duplicate entries and sort the array. $this->render_options[ "{$action}_{$element}" ] = array_unique( $this->render_options[ "{$action}_{$element}" ] ); sort( $this->render_options[ "{$action}_{$element}" ], SORT_NUMERIC ); } } // Load information about hidden rows and columns. // Get indexes of hidden rows (array value of 0). $hidden_rows = array_keys( $this->table['visibility']['rows'], 0, true ); $hidden_rows = array_merge( $hidden_rows, $this->render_options['hide_rows'] ); $hidden_rows = array_diff( $hidden_rows, $this->render_options['show_rows'] ); // Get indexes of hidden columns (array value of 0). $hidden_columns = array_keys( $this->table['visibility']['columns'], 0, true ); $hidden_columns = array_merge( $hidden_columns, $this->render_options['hide_columns'] ); $hidden_columns = array_merge( array_diff( $hidden_columns, $this->render_options['show_columns'] ) ); // Remove hidden rows and re-index. foreach ( $hidden_rows as $row_idx ) { unset( $this->table['data'][ $row_idx ] ); } $this->table['data'] = array_merge( $this->table['data'] ); // Remove hidden columns and re-index. foreach ( $this->table['data'] as $row_idx => $row ) { foreach ( $hidden_columns as $col_idx ) { unset( $row[ $col_idx ] ); } $this->table['data'][ $row_idx ] = array_merge( $row ); } /** * Filters the table after processing the table visibility information. * * @since 1.0.0 * * @param array $table The processed table. * @param array $orig_table The unprocessed table. * @param array $render_options The render options for the table. */ $this->table = apply_filters( 'tablepress_table_render_data', $this->table, $orig_table, $this->render_options ); } /** * Generate the data that is to be rendered. * * @since 2.0.0 */ protected function _process_render_data(): void { $orig_table = $this->table; // Deactivate nl2br() for this render process, if "convert_line_breaks" Shortcode parameter is set to false. if ( ! $this->render_options['convert_line_breaks'] ) { add_filter( 'tablepress_apply_nl2br', '__return_false', 9 ); // Priority 9, so that this filter can easily be overwritten at the default priority. } foreach ( $this->table['data'] as $row_idx => $row ) { foreach ( $row as $col_idx => $cell_content ) { // Print formulas that are escaped with '= (like in Excel) as text. if ( str_starts_with( $cell_content, "'=" ) ) { $cell_content = substr( $cell_content, 1 ); } $cell_content = $this->safe_output( $cell_content ); if ( str_contains( $cell_content, '[' ) ) { $cell_content = do_shortcode( $cell_content ); } /** * Filters the content of a single cell, after formulas have been evaluated, the output has been sanitized, and Shortcodes have been evaluated. * * @since 1.0.0 * * @param string $cell_content The cell content. * @param string $table_id The current table ID. * @param int $row_idx The row number of the cell. * @param int $col_idx The column number of the cell. */ $cell_content = apply_filters( 'tablepress_cell_content', $cell_content, $this->table['id'], $row_idx + 1, $col_idx + 1 ); $this->table['data'][ $row_idx ][ $col_idx ] = $cell_content; } } // Re-instate nl2br() behavior after this render process, if "convert_line_breaks" Shortcode parameter is set to false. if ( ! $this->render_options['convert_line_breaks'] ) { remove_filter( 'tablepress_apply_nl2br', '__return_false', 9 ); // Priority 9, so that this filter can easily be overwritten at the default priority. } /** * Filters the table after processing the table content handling. * * @since 2.0.0 * * @param array $table The processed table. * @param array $orig_table The unprocessed table. * @param array $render_options The render options for the table. */ $this->table = apply_filters( 'tablepress_table_content_render_data', $this->table, $orig_table, $this->render_options ); } /** * Generate the HTML output of the table. * * @since 1.0.0 */ protected function _render_table(): void { $num_rows = count( $this->table['data'] ); $num_columns = ( $num_rows > 0 ) ? count( $this->table['data'][0] ) : 0; // Check if there are rows and columns in the table (might not be the case after removing hidden rows/columns!). if ( 0 === $num_rows || 0 === $num_columns ) { $this->output = sprintf( __( '', 'tablepress' ), $this->table['id'] ); return; } // Counters for spans of rows and columns, init to 1 for each row and column (as that means no span). $this->rowspan = array_fill( 0, $num_columns, 1 ); $this->colspan = array_fill( 0, $num_rows, 1 ); /** * Filters the trigger keywords for "colspan" and "rowspan" * * @since 1.0.0 * * @param array $span_trigger The trigger keywords for combining table cells. * @param string $table_id The current table ID. */ $this->span_trigger = apply_filters( 'tablepress_span_trigger_keywords', $this->span_trigger, $this->table['id'] ); // Explode from string to array. $this->render_options['column_widths'] = ( ! empty( $this->render_options['column_widths'] ) ) ? explode( '|', $this->render_options['column_widths'] ) : array(); // Make array $this->render_options['column_widths'] have $columns entries. $this->render_options['column_widths'] = array_pad( $this->render_options['column_widths'], $num_columns, '' ); $output = ''; if ( $this->render_options['print_name'] ) { /** * Filters the HTML tag that wraps the printed table name. * * @since 1.0.0 * * @param string $tag The HTML tag around the table name. Default h2. * @param string $table_id The current table ID. */ $name_html_tag = apply_filters( 'tablepress_print_name_html_tag', 'h2', $this->table['id'] ); $name_attributes = array(); if ( ! empty( $this->render_options['html_id'] ) ) { $name_attributes['id'] = "{$this->render_options['html_id']}-name"; } /** * Filters the class attribute for the printed table name. * * @since 1.0.0 * @deprecated 1.13.0 Use {@see 'tablepress_table_name_tag_attributes'} instead. * * @param string $class The class attribute for the table name that can be used in CSS code. * @param string $table_id The current table ID. */ $name_attributes['class'] = apply_filters_deprecated( 'tablepress_print_name_css_class', array( "tablepress-table-name tablepress-table-name-id-{$this->table['id']}", $this->table['id'] ), 'TablePress 1.13.0', 'tablepress_table_name_tag_attributes' ); /** * Filters the attributes for the table name (HTML h2 element, by default). * * @since 1.13.0 * * @param array $name_attributes The attributes for the table name element. * @param array $table The current table. * @param array $render_options The render options for the table. */ $name_attributes = apply_filters( 'tablepress_table_name_tag_attributes', $name_attributes, $this->table, $this->render_options ); $name_attributes = $this->_attributes_array_to_string( $name_attributes ); $print_name_html = "<{$name_html_tag}{$name_attributes}>" . $this->safe_output( $this->table['name'] ) . "\n"; } if ( $this->render_options['print_description'] ) { /** * Filters the HTML tag that wraps the printed table description. * * @since 1.0.0 * * @param string $tag The HTML tag around the table description. Default span. * @param string $table_id The current table ID. */ $description_html_tag = apply_filters( 'tablepress_print_description_html_tag', 'span', $this->table['id'] ); $description_attributes = array(); if ( ! empty( $this->render_options['html_id'] ) ) { $description_attributes['id'] = "{$this->render_options['html_id']}-description"; } /** * Filters the class attribute for the printed table description. * * @since 1.0.0 * @deprecated 1.13.0 Use {@see 'tablepress_table_description_tag_attributes'} instead. * * @param string $class The class attribute for the table description that can be used in CSS code. * @param string $table_id The current table ID. */ $description_attributes['class'] = apply_filters_deprecated( 'tablepress_print_description_css_class', array( "tablepress-table-description tablepress-table-description-id-{$this->table['id']}", $this->table['id'] ), 'TablePress 1.13.0', 'tablepress_table_description_tag_attributes' ); /** * Filters the attributes for the table description (HTML span element, by default). * * @since 1.13.0 * * @param array $description_attributes The attributes for the table description element. * @param array $table The current table. * @param array $render_options The render options for the table. */ $description_attributes = apply_filters( 'tablepress_table_description_tag_attributes', $description_attributes, $this->table, $this->render_options ); $description_attributes = $this->_attributes_array_to_string( $description_attributes ); $print_description_html = "<{$description_html_tag}{$description_attributes}>" . $this->safe_output( $this->table['description'] ) . "\n"; } if ( $this->render_options['print_name'] && 'above' === $this->render_options['print_name_position'] ) { $output .= $print_name_html; } if ( $this->render_options['print_description'] && 'above' === $this->render_options['print_description_position'] ) { $output .= $print_description_html; } $thead = array(); $tfoot = array(); $tbody = array(); $this->last_row_idx = $num_rows - 1; $this->last_column_idx = $num_columns - 1; // Loop through rows in reversed order, to search for rowspan trigger keyword. $row_idx = $this->last_row_idx; // Render the table footer rows, if there is at least one extra row. if ( $this->render_options['table_foot'] > 0 && $num_rows >= $this->render_options['table_head'] + $this->render_options['table_foot'] ) { // @phpstan-ignore greaterOrEqual.invalid (`table_head` and `table_foot` are integers.) $last_tbody_idx = $this->last_row_idx - $this->render_options['table_foot']; while ( $row_idx > $last_tbody_idx ) { $tfoot[] = $this->_render_row( $row_idx, 'th' ); --$row_idx; } // Reverse rows because we looped through the rows in reverse order. $tfoot = array_reverse( $tfoot ); } // Render the table body rows. $last_thead_idx = $this->render_options['table_head'] - 1; while ( $row_idx > $last_thead_idx ) { $tbody[] = $this->_render_row( $row_idx, 'td' ); --$row_idx; } // Reverse rows because we looped through the rows in reverse order. $tbody = array_reverse( $tbody ); // Render the table header rows, if rows are left. while ( $row_idx > -1 ) { $thead[] = $this->_render_row( $row_idx, 'th' ); --$row_idx; } // Reverse rows because we looped through the rows in reverse order. $thead = array_reverse( $thead ); //
\n"; } // tag. $colgroup = ''; /** * Filters whether the HTML colgroup tag shall be added to the table output. * * @since 1.0.0 * * @param bool $print Whether the colgroup element shall be printed. * @param string $table_id The current table ID. */ if ( apply_filters( 'tablepress_print_colgroup_tag', false, $this->table['id'] ) ) { for ( $col_idx = 0; $col_idx < $num_columns; $col_idx++ ) { $attributes = ' class="colgroup-column-' . ( $col_idx + 1 ) . ' "'; /** * Filters the attributes of the HTML col tags in the HTML colgroup tag. * * @since 1.0.0 * * @param string $attributes The attributes in the col element. * @param string $table_id The current table ID. * @param int $col_idx The number of the column. */ $attributes = apply_filters( 'tablepress_colgroup_tag_attributes', $attributes, $this->table['id'], $col_idx + 1 ); $colgroup .= "\t\n"; } } if ( ! empty( $colgroup ) ) { $colgroup = "\n{$colgroup}\n"; } /* * , , and tags. */ if ( ! empty( $thead ) ) { $thead = "\n" . implode( '', $thead ) . "\n"; } else { $thead = ''; } if ( ! empty( $tfoot ) ) { $tfoot = "\n" . implode( '', $tfoot ) . "\n"; } else { $tfoot = ''; } $tbody_classes = array(); if ( $this->render_options['alternating_row_colors'] ) { $tbody_classes[] = 'row-striping'; } if ( $this->render_options['row_hover'] ) { $tbody_classes[] = 'row-hover'; } $tbody_class = implode( ' ', $tbody_classes ); if ( '' !== $tbody_class ) { $tbody_class = ' class="' . esc_attr( $tbody_class ) . '"'; } $tbody = "\n" . implode( '', $tbody ) . "\n"; // Attributes for the table (HTML table element). $table_attributes = array(); // "id" attribute. if ( ! empty( $this->render_options['html_id'] ) ) { $table_attributes['id'] = $this->render_options['html_id']; } // "class" attribute. $css_classes = array( 'tablepress', "tablepress-id-{$this->table['id']}", $this->render_options['extra_css_classes'], ); if ( $this->tbody_has_connected_cells ) { $css_classes[] = 'tbody-has-connected-cells'; } /** * Filters the CSS classes that are given to the HTML table element. * * @since 1.0.0 * * @param string[] $css_classes The CSS classes for the table element. * @param string $table_id The current table ID. */ $css_classes = apply_filters( 'tablepress_table_css_classes', $css_classes, $this->table['id'] ); // $css_classes might contain several classes in one array entry. $css_classes = explode( ' ', implode( ' ', $css_classes ) ); $css_classes = array_map( array( 'TablePress', 'sanitize_css_class' ), $css_classes ); $css_classes = array_unique( $css_classes ); $css_classes = array_filter( $css_classes ); // Remove empty entries. $css_classes = implode( ' ', $css_classes ); if ( '' !== $css_classes ) { $table_attributes['class'] = $css_classes; } // ARIA label attributes. if ( $this->render_options['print_name'] && ! empty( $this->render_options['html_id'] ) ) { $table_attributes['aria-labelledby'] = "{$this->render_options['html_id']}-name"; } if ( $this->render_options['print_description'] && ! empty( $this->render_options['html_id'] ) ) { $table_attributes['aria-describedby'] = "{$this->render_options['html_id']}-description"; } // "summary" attribute. $summary = ''; /** * Filters the content for the summary attribute of the HTML table element. * * The attribute is only added if it is not empty. * * @since 1.0.0 * * @param string $summary The content for the summary attribute of the table. Default empty. * @param array $table The current table. */ $summary = apply_filters( 'tablepress_print_summary_attr', $summary, $this->table ); if ( ! empty( $summary ) ) { $table_attributes['summary'] = esc_attr( $summary ); } // Legacy support for attributes that are not encouraged in HTML5. foreach ( array( 'cellspacing', 'cellpadding', 'border' ) as $attribute ) { if ( false !== $this->render_options[ $attribute ] ) { $table_attributes[ $attribute ] = (int) $this->render_options[ $attribute ]; } } /** * Filters the attributes for the table (HTML table element). * * @since 1.4.0 * * @param array $table_attributes The attributes for the table element. * @param array $table The current table. * @param array $render_options The render options for the table. */ $table_attributes = apply_filters( 'tablepress_table_tag_attributes', $table_attributes, $this->table, $this->render_options ); $table_attributes = $this->_attributes_array_to_string( $table_attributes ); $table_html = "\n"; $table_html .= $caption . $colgroup . $thead . $tbody . $tfoot; $table_html .= '
is_free_plan() ) { echo '

'; _e( 'This TablePress Extension was retired.', 'tablepress' ); echo ' '; _e( 'The plugin does no longer work with TablePress 3 and will no longer receive updates or support!', 'tablepress' ); echo '
'; _e( 'Keeping it activated can lead to errors on your website!', 'tablepress' ); echo ' ' . sprintf( __( 'Find out what you can do to continue using its features!', 'tablepress' ), 'https://tablepress.org/upgrade-extensions/?utm_source=plugin&utm_medium=textlink&utm_content=plugins-list-table' ) . ''; echo '

'; } ?>
tag. /** * Filters the content for the HTML caption element of the table. * * If the "Edit" link for a table is shown, it is also added to the caption element. * * @since 1.0.0 * * @param string $caption The content for the HTML caption element of the table. Default empty. * @param array $table The current table. */ $caption = apply_filters( 'tablepress_print_caption_text', '', $this->table ); $caption_style = ''; $caption_class = ''; if ( ! empty( $caption ) ) { /** * Filters the class attribute for the HTML caption element of the table. * * @since 1.0.0 * * @param string $class The class attribute for the HTML caption element of the table. * @param string $table_id The current table ID. */ $caption_class = apply_filters( 'tablepress_print_caption_class', "tablepress-table-caption tablepress-table-caption-id-{$this->table['id']}", $this->table['id'] ); $caption_class = ' class="' . $caption_class . '"'; } if ( ! empty( $this->render_options['edit_table_url'] ) ) { if ( empty( $caption ) ) { $caption_style = ' style="caption-side:bottom;text-align:left;border:none;background:none;margin:0;padding:0;"'; } else { $caption .= '
'; } $caption .= '' . __( 'Edit', 'default' ) . ''; } if ( ! empty( $caption ) ) { $caption = "{$caption}
'; /** * Filters the generated HTML code for the table, without HTML elements around it. * * @since 2.4.0 * * @param string $output The generated HTML for the table, without HTML elements around it. * @param array $table The current table. * @param array $render_options The render options for the table, without HTML elements around it. */ $table_html = apply_filters( 'tablepress_table_html', $table_html, $this->table, $this->render_options ); $output .= "\n{$table_html}\n"; unset( $table_html ); // Unset the potentially large variable to free up memory. // name/description below table (HTML already generated above). if ( $this->render_options['print_name'] && 'below' === $this->render_options['print_name_position'] ) { $output .= $print_name_html; // @phpstan-ignore variable.undefined (The variable is set above.) } if ( $this->render_options['print_description'] && 'below' === $this->render_options['print_description_position'] ) { $output .= $print_description_html; // @phpstan-ignore variable.undefined (The variable is set above.) } /** * Filters the generated HTML code for the table and HTML elements around it. * * @since 1.0.0 * * @param string $output The generated HTML for the table and HTML elements around it. * @param array $table The current table. * @param array $render_options The render options for the table and HTML elements around it. */ $this->output = apply_filters( 'tablepress_table_output', $output, $this->table, $this->render_options ); } /** * Generate the HTML of a row. * * @since 1.0.0 * * @param int $row_idx Index of the row to be rendered. * @param string $tag HTML tag to use for the cells (td or th). * @return string HTML for the row. */ protected function _render_row( int $row_idx, string $tag ): string { $row_cells = array(); // Loop through cells in reversed order, to search for colspan or rowspan trigger words. for ( $col_idx = $this->last_column_idx; $col_idx >= 0; $col_idx-- ) { $cell_content = $this->table['data'][ $row_idx ][ $col_idx ]; if ( $this->span_trigger['rowspan'] === $cell_content ) { // There will be a rowspan. if ( ! ( ( 0 === $row_idx ) // No rowspan inside first row. || ( $this->render_options['table_head'] === $row_idx ) // No rowspan into table head. || ( $this->last_row_idx - $this->render_options['table_foot'] + 1 === $row_idx ) // No rowspan out of table foot. ) ) { // Increase counter for rowspan in this column. ++$this->rowspan[ $col_idx ]; // Reset counter for colspan in this row, combined col- and rowspan might be happening. $this->colspan[ $row_idx ] = 1; continue; } // Invalid rowspan, so we set cell content from #rowspan# to empty. $cell_content = ''; } elseif ( $this->span_trigger['colspan'] === $cell_content ) { // There will be a colspan. if ( ! ( ( 0 === $col_idx ) // No colspan inside first column. || ( 1 === $col_idx && $this->render_options['first_column_th'] ) // No colspan into first column head. ) ) { // Increase counter for colspan in this row. ++$this->colspan[ $row_idx ]; // Reset counter for rowspan in this column, combined col- and rowspan might be happening. $this->rowspan[ $col_idx ] = 1; continue; } // Invalid colspan, so we set cell content from #colspan# to empty. $cell_content = ''; } elseif ( $this->span_trigger['span'] === $cell_content ) { // There will be a combined col- and rowspan. if ( ! ( ( 0 === $row_idx ) // No rowspan inside first row. || ( $this->render_options['table_head'] === $row_idx ) // No rowspan into table head. || ( $this->last_row_idx - $this->render_options['table_foot'] + 1 === $row_idx ) // No rowspan out of table foot. ) && ! ( ( 0 === $col_idx ) // No colspan inside first column. || ( 1 === $col_idx && $this->render_options['first_column_th'] ) // No colspan into first column head. ) ) { continue; } // Invalid span, so we set cell content from #span# to empty. $cell_content = ''; } // Attributes for the table cell (HTML td or th element). $tag_attributes = array(); // "colspan" and "rowspan" attributes. if ( $this->colspan[ $row_idx ] > 1 ) { // We have colspaned cells. $tag_attributes['colspan'] = (string) $this->colspan[ $row_idx ]; if ( ! $this->tbody_has_connected_cells && $row_idx > $this->render_options['table_head'] - 1 && $row_idx < $this->last_row_idx - $this->render_options['table_foot'] + 1 ) { // Set flag that there are connected cells in the tbody. $this->tbody_has_connected_cells = true; } } if ( $this->rowspan[ $col_idx ] > 1 ) { // We have rowspaned cells. $tag_attributes['rowspan'] = (string) $this->rowspan[ $col_idx ]; if ( ! $this->tbody_has_connected_cells && $row_idx > $this->render_options['table_head'] - 1 && $row_idx < $this->last_row_idx - $this->render_options['table_foot'] + 1 ) { // Set flag that there are connected cells in the tbody. $this->tbody_has_connected_cells = true; } } // "class" attribute. $cell_class = 'column-' . ( $col_idx + 1 ); /** * Filters the CSS classes that are given to a single cell (HTML td element) of a table. * * @since 1.0.0 * * @param string $cell_class The CSS classes for the cell. * @param string $table_id The current table ID. * @param string $cell_content The cell content. * @param int $row_idx The row number of the cell. * @param int $col_idx The column number of the cell. * @param int $colspan_row The number of combined columns for this cell. * @param int $rowspan_col The number of combined rows for this cell. */ $cell_class = apply_filters( 'tablepress_cell_css_class', $cell_class, $this->table['id'], $cell_content, $row_idx + 1, $col_idx + 1, $this->colspan[ $row_idx ], $this->rowspan[ $col_idx ] ); if ( ! empty( $cell_class ) ) { $tag_attributes['class'] = $cell_class; } // "style" attribute. if ( ( 0 === $row_idx ) && ! empty( $this->render_options['column_widths'][ $col_idx ] ) ) { $tag_attributes['style'] = 'width:' . preg_replace( '#[^0-9a-z.%]#', '', $this->render_options['column_widths'][ $col_idx ] ) . ';'; } /** * Filters the attributes for the table cell (HTML td or th element). * * @since 1.4.0 * * @param array $tag_attributes The attributes for the td or th element. * @param string $table_id The current table ID. * @param string $cell_content The cell content. * @param int $row_idx The row number of the cell. * @param int $col_idx The column number of the cell. * @param int $colspan_row The number of combined columns for this cell. * @param int $rowspan_col The number of combined rows for this cell. */ $tag_attributes = apply_filters( 'tablepress_cell_tag_attributes', $tag_attributes, $this->table['id'], $cell_content, $row_idx + 1, $col_idx + 1, $this->colspan[ $row_idx ], $this->rowspan[ $col_idx ] ); $tag_attributes = $this->_attributes_array_to_string( $tag_attributes ); if ( '' === $cell_content ) { $cell_tag = 'td'; // For accessibility, empty cells should use `td` and not `th` tags. } elseif ( $this->render_options['first_column_th'] && 0 === $col_idx ) { $cell_tag = 'th'; // Non-empty cells in the first column should use `th` tags, if enabled. } else { $cell_tag = $tag; // Otherwise, use the tag that was passed in as the default for the row. } $row_cells[] = "<{$cell_tag}{$tag_attributes}>{$cell_content}"; $this->colspan[ $row_idx ] = 1; // Reset. $this->rowspan[ $col_idx ] = 1; // Reset. } // Attributes for the table row (HTML tr element). $tr_attributes = array(); // "class" attribute. $row_classes = 'row-' . ( $row_idx + 1 ); /** * Filters the CSS classes that are given to a row (HTML tr element) of a table. * * @since 1.0.0 * * @param string $row_classes The CSS classes for the row. * @param string $table_id The current table ID. * @param string[] $row_cells The HTML code for the cells of the row. * @param int $row_idx The row number. * @param string[] $row_data The content of the cells of the row. */ $row_classes = apply_filters( 'tablepress_row_css_class', $row_classes, $this->table['id'], $row_cells, $row_idx + 1, $this->table['data'][ $row_idx ] ); if ( ! empty( $row_classes ) ) { $tr_attributes['class'] = $row_classes; } /** * Filters the attributes for the table row (HTML tr element). * * @since 1.4.0 * * @param array $tr_attributes The attributes for the tr element. * @param string $table_id The current table ID. * @param int $row_idx The row number. * @param string[] $row_data The content of the cells of the row. */ $tr_attributes = apply_filters( 'tablepress_row_tag_attributes', $tr_attributes, $this->table['id'], $row_idx + 1, $this->table['data'][ $row_idx ] ); $tr_attributes = $this->_attributes_array_to_string( $tr_attributes ); // Reverse rows because we looped through the cells in reverse order. $row_cells = array_reverse( $row_cells ); return "\n\t" . implode( '', $row_cells ) . "\n\n"; } /** * Convert an array of HTML tag attributes to a string. * * @since 1.4.0 * * @param array $attributes Attributes for the HTML tag in the array keys, and their values in the array values. * @return string The attributes as a string for usage in a HTML element. */ protected function _attributes_array_to_string( array $attributes ): string { $attributes_string = ''; foreach ( $attributes as $attribute => $value ) { $attributes_string .= " {$attribute}=\"{$value}\""; } return $attributes_string; } /** * Possibly replace certain HTML entities and replace line breaks with HTML. * * @since 1.0.0 * * @param string $text The string to process. * @return string Processed string for output. */ protected function safe_output( string $text ): string { /* * Replace any & with & that is not already an encoded entity (from function htmlentities2 in WP 2.8). * A complete htmlentities2() or htmlspecialchars() would encode tags, which we don't want. */ $text = (string) preg_replace( '/&(?![A-Za-z]{0,4}\w{2,3};|#[0-9]{2,4};)/', '&', $text ); /** * Filters whether line breaks in the cell content shall be replaced with HTML br tags. * * @since 1.0.0 * * @param bool $replace Whether to replace line breaks with HTML br tags. Default true. * @param string $table_id The current table ID. */ if ( apply_filters( 'tablepress_apply_nl2br', true, $this->table['id'] ) ) { $text = nl2br( $text ); } return $text; } /** * Get the default render options, null means: Use option from "Edit" screen. * * @since 1.0.0 * * @return array Default render options. */ public function get_default_render_options(): array { // Attention: Array keys have to be lowercase, otherwise they won't match the Shortcode attributes, which will be passed in lowercase by WP. return array( 'alternating_row_colors' => null, 'block_preview' => false, 'border' => false, 'cache_table_output' => true, 'cellpadding' => false, 'cellspacing' => false, 'column_widths' => '', 'convert_line_breaks' => true, 'datatables_custom_commands' => null, 'datatables_datetime' => '', 'datatables_filter' => null, 'datatables_info' => null, 'datatables_lengthchange' => null, 'datatables_locale' => get_locale(), 'datatables_paginate' => null, 'datatables_paginate_entries' => null, 'datatables_scrollx' => null, 'datatables_scrolly' => false, 'datatables_sort' => null, 'evaluate_formulas' => true, 'extra_css_classes' => null, 'first_column_th' => false, 'hide_columns' => '', 'hide_rows' => '', 'id' => '', 'print_description' => null, 'print_description_position' => null, 'print_name' => null, 'print_name_position' => null, 'row_hover' => null, 'shortcode_debug' => false, 'show_columns' => '', 'show_rows' => '', 'table_foot' => null, 'table_head' => null, 'use_datatables' => null, ); } /** * Get the CSS code for the Preview iframe. * * @since 1.0.0 * * @return string CSS for the Preview iframe. */ public function get_preview_css(): string { $is_rtl = is_rtl(); $tablepress_css = TablePress::load_class( 'TablePress_CSS', 'class-css.php', 'classes' ); $default_css_minified = $tablepress_css->load_default_css_from_file( $is_rtl ); if ( false === $default_css_minified ) { $default_css_minified = ''; } $rtl_direction = $is_rtl ? "\ndirection: rtl;" : ''; return << /* iframe */ body { margin: 10px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;{$rtl_direction} } p { font-size: 13px; } {$default_css_minified} CSS; } } // class TablePress_Render PK!"&&)classes/class-evaluate-phpspreadsheet.phpnu[> $table_data Table data in which formulas shall be evaluated. * @param string $table_id ID of the passed table. * @return array> Table data with evaluated formulas. */ public function evaluate_table_data( array $table_data, string $table_id ): array { $table_has_formulas = false; // Loop through all cells to check for formulas and convert notations. foreach ( $table_data as &$row ) { foreach ( $row as &$cell_content ) { if ( '' === $cell_content || '=' === $cell_content || '=' !== $cell_content[0] ) { continue; } $table_has_formulas = true; // Convert legacy "formulas in text" notation (`=Text {A3+B3} Text`) to standard Excel notation (`="Text "&A3+B3&" Text"`). if ( 1 === preg_match( '#{(.+?)}#', $cell_content ) ) { $cell_content = str_replace( '"', '""', $cell_content ); // Preserve existing quotation marks in text around formulas. $cell_content = '="' . substr( $cell_content, 1 ) . '"'; // Wrap the whole cell content in quotation marks, as there will be text around formulas. $cell_content = (string) preg_replace( '#{(.+?)}#', '"&$1&"', $cell_content, -1, $count ); // Convert all wrapped formulas to standard Excel notation. } } } unset( $row, $cell_content ); // Unset use-by-reference parameters of foreach loops. // No need to use the PHPSpreadsheet Calculation engine if the table does not contain formulas. if ( ! $table_has_formulas ) { return $table_data; } try { $spreadsheet = new \TablePress\PhpOffice\PhpSpreadsheet\Spreadsheet(); $worksheet = $spreadsheet->setActiveSheetIndex( 0 ); $worksheet->fromArray( /* $source */ $table_data, /* $nullValue */ '' ); // Don't allow cyclic references. \TablePress\PhpOffice\PhpSpreadsheet\Calculation\Calculation::getInstance( $spreadsheet )->cyclicFormulaCount = 0; /* * Register variables as Named Formulas. * The variables `ROW`, `COLUMN`, `CELL`, `PI`, and `E` should be considered deprecated and only their formulas should be used. */ $spreadsheet->addNamedFormula( new \TablePress\PhpOffice\PhpSpreadsheet\NamedFormula( 'TABLE_ID', $worksheet, $table_id ) ); $num_rows = (string) count( $table_data ); $spreadsheet->addNamedFormula( new \TablePress\PhpOffice\PhpSpreadsheet\NamedFormula( 'NUM_ROWS', $worksheet, $num_rows ) ); $num_columns = (string) count( $table_data[0] ); $spreadsheet->addNamedFormula( new \TablePress\PhpOffice\PhpSpreadsheet\NamedFormula( 'NUM_COLUMNS', $worksheet, $num_columns ) ); $spreadsheet->addNamedFormula( new \TablePress\PhpOffice\PhpSpreadsheet\NamedFormula( 'ROW', $worksheet, '=ROW()' ) ); $spreadsheet->addNamedFormula( new \TablePress\PhpOffice\PhpSpreadsheet\NamedFormula( 'COLUMN', $worksheet, '=COLUMN()' ) ); $spreadsheet->addNamedFormula( new \TablePress\PhpOffice\PhpSpreadsheet\NamedFormula( 'CELL', $worksheet, '=ADDRESS(ROW(),COLUMN(),4)' ) ); $spreadsheet->addNamedFormula( new \TablePress\PhpOffice\PhpSpreadsheet\NamedFormula( 'PI', $worksheet, '=PI()' ) ); $spreadsheet->addNamedFormula( new \TablePress\PhpOffice\PhpSpreadsheet\NamedFormula( 'E', $worksheet, '=EXP(1)' ) ); // Loop through all table cells and replace formulas with evaluated values. $cell_collection = $worksheet->getCellCollection(); foreach ( $table_data as $row_idx => &$row ) { foreach ( $row as $column_idx => &$cell_content ) { if ( strlen( $cell_content ) > 1 && '=' === $cell_content[0] ) { // Adapted from \TablePress\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::rangeToArray(). $cell_reference = \TablePress\PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex( $column_idx + 1 ) . ( $row_idx + 1 ); if ( $cell_collection->has( $cell_reference ) ) { $cell = $cell_collection->get( $cell_reference ); try { $cell_content = (string) $cell->getCalculatedValue(); // Convert hyperlinks, e.g. generated via `=HYPERLINK()` to HTML code. $cell_has_hyperlink = $worksheet->hyperlinkExists( $cell_reference ) && ! $worksheet->getHyperlink( $cell_reference )->isInternal(); if ( $cell_has_hyperlink ) { $url = $worksheet->getHyperlink( $cell_reference )->getUrl(); if ( '' !== $url ) { $url = esc_url( $url ); $cell_content = "{$cell_content}"; } } // Sanitize the output of the evaluated formula. $cell_content = wp_kses_post( $cell_content ); // Equals wp_filter_post_kses(), but without the unnecessary slashes handling. } catch ( \TablePress\PhpOffice\PhpSpreadsheet\Calculation\Exception $exception ) { $message = str_replace( 'Worksheet!', '', $exception->getMessage() ); $cell_content = "!ERROR! {$message}"; } } } } } unset( $row, $cell_content ); // Unset use-by-reference parameters of foreach loops. // Save PHP memory. $spreadsheet->disconnectWorksheets(); unset( $cell_collection, $worksheet, $spreadsheet ); } catch ( \TablePress\PhpOffice\PhpSpreadsheet\Calculation\Exception $exception ) { $message = str_replace( 'Worksheet!', '', $exception->getMessage() ); $table_data = array( array( "!ERROR! {$message}" ) ); } return $table_data; } } // class TablePress_Evaluate_PHPSpreadsheet PK!d??classes/class-css.phpnu[" with ">", but ">" is valid in CSS selectors. $css = str_replace( '>', '>', $css ); // strip_tags again, because of the just added ">" (KSES for a second time would again bring the ">" problem). $css = strip_tags( $css ); } $csstidy->set_cfg( 'remove_bslash', false ); $csstidy->set_cfg( 'compress_colors', false ); $csstidy->set_cfg( 'compress_font-weight', false ); $csstidy->set_cfg( 'lowercase_s', false ); $csstidy->set_cfg( 'optimise_shorthands', false ); $csstidy->set_cfg( 'remove_last_;', false ); $csstidy->set_cfg( 'case_properties', false ); $csstidy->set_cfg( 'sort_properties', false ); $csstidy->set_cfg( 'sort_selectors', false ); $csstidy->set_cfg( 'discard_invalid_selectors', false ); $csstidy->set_cfg( 'discard_invalid_properties', true ); $csstidy->set_cfg( 'merge_selectors', false ); $csstidy->set_cfg( 'css_level', 'CSS3.0' ); $csstidy->set_cfg( 'preserve_css', true ); $csstidy->set_cfg( 'timestamp', false ); $csstidy->set_cfg( 'template', 'default' ); $csstidy->parse( $css ); return $csstidy->print->plain(); } /** * Minify a string of CSS code, that should have been sanitized/tidied before. * * @since 1.1.0 * * @param string $css CSS code. * @return string Minified CSS code. */ public function minify_css( string $css ): string { $csstidy = TablePress::load_class( 'TablePress_CSSTidy', 'class.csstidy.php', 'libraries/csstidy' ); $csstidy->set_cfg( 'remove_bslash', false ); $csstidy->set_cfg( 'compress_colors', true ); $csstidy->set_cfg( 'compress_font-weight', true ); $csstidy->set_cfg( 'lowercase_s', false ); $csstidy->set_cfg( 'optimise_shorthands', 1 ); $csstidy->set_cfg( 'remove_last_;', true ); $csstidy->set_cfg( 'case_properties', false ); $csstidy->set_cfg( 'sort_properties', false ); $csstidy->set_cfg( 'sort_selectors', false ); $csstidy->set_cfg( 'discard_invalid_selectors', false ); $csstidy->set_cfg( 'discard_invalid_properties', true ); $csstidy->set_cfg( 'merge_selectors', false ); $csstidy->set_cfg( 'css_level', 'CSS3.0' ); $csstidy->set_cfg( 'preserve_css', false ); $csstidy->set_cfg( 'timestamp', false ); $csstidy->set_cfg( 'template', 'highest' ); $csstidy->parse( $css ); $css = $csstidy->print->plain(); // Remove all CSS comments from the minified CSS code, as CSSTidy does not remove those inside a CSS selector. return preg_replace( '!/\*[^*]*\*+([^/][^*]*\*+)*/\n?!', '', $css ); } /** * Get the location (file path or URL) of the "Custom CSS" file, depending on whether it's a Multisite install or not. * * @since 1.0.0 * * @param string $type "normal" version, "minified" version, or "combined" (with TablePress Default CSS) version. * @param string $location "path" or "url", for file path or URL. * @return string Full file path or full URL for the "Custom CSS" file. */ public function get_custom_css_location( string $type, string $location ): string { switch ( $type ) { case 'combined': $file = 'tablepress-combined.min.css'; break; case 'minified': $file = 'tablepress-custom.min.css'; break; case 'normal': default: $file = 'tablepress-custom.css'; break; } if ( is_multisite() ) { // Multisite installation: Use /wp-content/uploads/sites//. $upload_location = wp_upload_dir(); } else { // Singlesite installation: Use /wp-content/. $upload_location = array( 'basedir' => WP_CONTENT_DIR, 'baseurl' => content_url(), ); } switch ( $location ) { case 'url': $url = set_url_scheme( $upload_location['baseurl'] . '/' . $file ); /** * Filters the URL from which the "Custom CSS" file is loaded. * * @since 1.0.0 * * @param string $url URL of the "Custom CSS" file. * @param string $file File name of the "Custom CSS" file. * @param string $type Type of the "Custom CSS" file ("normal", "minified", or "combined"). */ $url = apply_filters( 'tablepress_custom_css_url', $url, $file, $type ); return $url; // break; // unreachable. case 'path': $path = $upload_location['basedir'] . '/' . $file; /** * Filters the file path on the server from which the "Custom CSS" file is loaded. * * @since 1.0.0 * * @param string $path File path of the "Custom CSS" file. * @param string $file File name of the "Custom CSS" file. * @param string $type Type of the "Custom CSS" file ("normal", "minified", or "combined"). */ $path = apply_filters( 'tablepress_custom_css_file_name', $path, $file, $type ); return $path; // break; // unreachable. default: // Return an empty string if no valid location was provided. return ''; // break; // unreachable. } } /** * Load the contents of the file with the "Custom CSS". * * @since 1.0.0 * * @param string $type Optional. Whether to load "normal" version or "minified" version. Default "normal". * @return string|false Custom CSS on success, false on error. */ public function load_custom_css_from_file( string $type = 'normal' ) /* : string|false */ { $filename = $this->get_custom_css_location( $type, 'path' ); // Check if file name is valid (0 means yes). if ( 0 !== validate_file( $filename ) ) { return false; } if ( ! @is_file( $filename ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged return false; } if ( ! @is_readable( $filename ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged return false; } return file_get_contents( $filename ); } /** * Loads the contents of the file with the TablePress Default CSS, * either for left-to-right (LTR) or right-to-left (RTL), in minified form. * * @since 1.1.0 * @since 2.0.0 Added the `$load_rtl` parameter. * * @param bool $load_rtl Optional. Whether to load LTR or RTL version. Default LTR. * @return string|false TablePress Default CSS on success, false on error. */ public function load_default_css_from_file( bool $load_rtl = false ) /* : string|false */ { $rtl = $load_rtl ? '-rtl' : ''; $filename = TABLEPRESS_ABSPATH . "css/build/default{$rtl}.css"; // Check if file name is valid (0 means yes). if ( 0 !== validate_file( $filename ) ) { return false; } if ( ! @is_file( $filename ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged return false; } if ( ! @is_readable( $filename ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged return false; } return file_get_contents( $filename ); } /** * Try to save "Custom CSS" to a file (requires "direct" method in WP_Filesystem, or stored FTP credentials). * * @since 1.1.0 * * @param string $custom_css_normal Custom CSS code to be saved. * @param string $custom_css_minified Minified CSS code to be saved. * @return bool True on success, false on failure. */ public function save_custom_css_to_file( string $custom_css_normal, string $custom_css_minified ): bool { /** * Filters whether the "Custom CSS" code shall be saved to a file on the server. * * @since 1.1.0 * * @param bool $save Whether to save the "Custom CSS" to a file. Default true. */ if ( ! apply_filters( 'tablepress_save_custom_css_to_file', true ) ) { return false; } // Start capturing the output, to later prevent it. ob_start(); $credentials = request_filesystem_credentials( '', '', false, '', null, false ); /* * Do we have credentials already? (Otherwise the form will have been rendered, which is not supported here.) * Or, if we have credentials, are they valid? */ if ( false === $credentials || ! WP_Filesystem( $credentials ) ) { // @phpstan-ignore argument.type ob_end_clean(); return false; } // We have valid access to the filesystem now -> try to save the files. return $this->_custom_css_save_helper( $custom_css_normal, $custom_css_minified ); } /** * Save "Custom CSS" to files, delete "Custom CSS" files, or return HTML for the credentials form. * * Only used from "Plugin Options" screen, save_custom_css_to_file() is used in cases where no form output/redirection is possible (e.g. during plugin updates). * * @since 1.0.0 * * @param string $custom_css_normal Custom CSS code to be saved. If empty, files will be deleted. * @param string $custom_css_minified Minified CSS code to be saved. * @return bool|string True on success, false on failure, or string of HTML for the credentials form for the WP_Filesystem API, if necessary. */ public function save_custom_css_to_file_plugin_options( string $custom_css_normal, string $custom_css_minified ) /* : bool|string */ { /** This filter is documented in classes/class-css.php */ if ( ! apply_filters( 'tablepress_save_custom_css_to_file', true ) ) { return false; } // Start capturing the output, to get HTML of the credentials form (if needed). ob_start(); $credentials = request_filesystem_credentials( '', '', false, '', null, false ); // Do we have credentials already? Otherwise the form will have been rendered already. if ( false === $credentials ) { $form_data = ob_get_clean(); $form_data = str_replace( 'name="upgrade" id="upgrade" class="button"', 'name="upgrade" id="upgrade" class="components-button is-primary"', $form_data ); // @phpstan-ignore argument.type return $form_data; } // We have received credentials, but don't know if they are valid yet. if ( ! WP_Filesystem( $credentials ) ) { // @phpstan-ignore argument.type // Credentials failed, so ask again (with $error flag true). request_filesystem_credentials( '', '', true, '', null, false ); $form_data = ob_get_clean(); $form_data = str_replace( 'name="upgrade" id="upgrade" class="button"', 'name="upgrade" id="upgrade" class="components-button is-primary"', $form_data ); // @phpstan-ignore argument.type return $form_data; } // We have valid access to the filesystem now -> try to save the files, or delete them if the "Custom CSS" is empty. if ( '' !== $custom_css_normal ) { return $this->_custom_css_save_helper( $custom_css_normal, $custom_css_minified ); } else { return $this->_custom_css_delete_helper(); } } /** * Save "Custom CSS" to files, if validated access to the WP_Filesystem exists. * * Helper function to prevent code duplication. * * @since 1.1.0 * * @global WP_Filesystem_* $wp_filesystem WordPress file system abstraction object. * @see save_custom_css_to_file() * @see save_custom_css_to_file_plugin_options() * * @param string $custom_css_normal Custom CSS code to be saved. * @param string $custom_css_minified Minified CSS code to be saved. * @return bool True on success, false on failure. */ protected function _custom_css_save_helper( string $custom_css_normal, string $custom_css_minified ): bool { global $wp_filesystem; /* * WP_CONTENT_DIR and (FTP-)Content-Dir can be different (e.g. if FTP working dir is /). * We need to account for that by replacing the path difference in the filename. */ $path_difference = str_replace( $wp_filesystem->wp_content_dir(), '', trailingslashit( WP_CONTENT_DIR ) ); $css_types = array( 'normal', 'minified', 'combined' ); $default_css_minified = $this->load_default_css_from_file( false ); if ( false === $default_css_minified ) { $default_css_minified = ''; } $file_content = array( 'normal' => $custom_css_normal, 'minified' => $custom_css_minified, 'combined' => $default_css_minified . $custom_css_minified, ); $total_result = true; // Whether all files were saved successfully. foreach ( $css_types as $css_type ) { $filename = $this->get_custom_css_location( $css_type, 'path' ); // Check if filename is valid (0 means yes). if ( 0 !== validate_file( $filename ) ) { $total_result = false; continue; } if ( '' !== $path_difference ) { $filename = str_replace( $path_difference, '', $filename ); } $result = $wp_filesystem->put_contents( $filename, $file_content[ $css_type ], FS_CHMOD_FILE ); $total_result = ( $total_result && $result ); } $this->_flush_caching_plugins_css_minify_caches(); return $total_result; } /** * Delete the "Custom CSS" files, if possible. * * @since 1.0.0 * * @return bool True on success, false on failure. */ public function delete_custom_css_files(): bool { // Start capturing the output, to later prevent it. ob_start(); $credentials = request_filesystem_credentials( '', '', false, '', null, false ); /* * Do we have credentials already? (Otherwise the form will have been rendered, which is not supported here.) * Or, if we have credentials, are they valid? */ if ( false === $credentials || ! WP_Filesystem( $credentials ) ) { // @phpstan-ignore argument.type ob_end_clean(); return false; } // We have valid access to the filesystem now -> try to delete the files. return $this->_custom_css_delete_helper(); } /** * Delete "Custom CSS" files, if validated access to the WP_Filesystem exists. * * Helper function to prevent code duplication * * @since 1.1.0 * * @global WP_Filesystem_* $wp_filesystem WordPress file system abstraction object. * @see delete_custom_css_files() * @see save_custom_css_to_file_plugin_options() * * @return bool True on success, false on failure. */ protected function _custom_css_delete_helper(): bool { global $wp_filesystem; /* * WP_CONTENT_DIR and (FTP-)Content-Dir can be different (e.g. if FTP working dir is /). * We need to account for that by replacing the path difference in the filename. */ $path_difference = str_replace( $wp_filesystem->wp_content_dir(), '', trailingslashit( WP_CONTENT_DIR ) ); $css_types = array( 'normal', 'minified', 'combined' ); $total_result = true; // Whether all files were deleted successfully. foreach ( $css_types as $css_type ) { $filename = $this->get_custom_css_location( $css_type, 'path' ); // Check if filename is valid (0 means yes). if ( 0 !== validate_file( $filename ) ) { $total_result = false; continue; } if ( '' !== $path_difference ) { $filename = str_replace( $path_difference, '', $filename ); } // We have valid access to the filesystem now -> try to delete the file. if ( $wp_filesystem->exists( $filename ) ) { $result = $wp_filesystem->delete( $filename ); $total_result = ( $total_result && $result ); } } $this->_flush_caching_plugins_css_minify_caches(); return $total_result; } /** * Flush the CSS minification caches of common caching plugins. * * @since 1.4.0 */ protected function _flush_caching_plugins_css_minify_caches(): void { /** This filter is documented in models/model-table.php */ if ( ! apply_filters( 'tablepress_flush_caching_plugins_caches', true ) ) { return; } // W3 Total Cache. if ( function_exists( 'w3tc_minify_flush' ) ) { w3tc_minify_flush(); } // WP Fastest Cache. if ( isset( $GLOBALS['wp_fastest_cache'] ) && is_callable( array( $GLOBALS['wp_fastest_cache'], 'deleteCache' ) ) ) { $GLOBALS['wp_fastest_cache']->deleteCache( true ); // @phpstan-ignore method.nonObject } } } // class TablePress_CSS PK!!oclasses/class-import-base.phpnu[> $an_array Two-dimensional array to be padded. */ public function pad_array_to_max_cols( array &$an_array ): void { $max_columns = $this->count_max_columns( $an_array ); // Extend the array to at least one column. $max_columns = max( 1, $max_columns ); array_walk( $an_array, static function ( array &$row, int $col_idx ) use ( $max_columns ): void { $row = array_pad( $row, $max_columns, '' ); }, ); } /** * Get the highest number of columns in the rows. * * @since 1.0.0 * * @param array> $an_array Two-dimensional array. * @return int Highest number of columns in the rows of the array. */ protected function count_max_columns( array $an_array ): int { $max_columns = 0; foreach ( $an_array as $row_idx => $row ) { $num_columns = count( $row ); $max_columns = max( $num_columns, $max_columns ); } return $max_columns; } } // class TablePress_Import_Base PK!שXclasses/class-wp_option.phpnu[option_name = $params['option_name']; $option_value = $this->_get_option( $this->option_name, null ); if ( ! is_null( $option_value ) ) { $this->option_value = (array) json_decode( $option_value, true ); } else { $this->option_value = $params['default_value']; } } /** * Check if Option is set. * * @since 1.0.0 * * @param string $name Name of the option to check. * @return bool Whether the option is set. */ public function is_set( string $name ): bool { return isset( $this->option_value[ $name ] ); } /** * Get a single Option, or get all Options. * * @since 1.0.0 * * @param string|false $name Optional. Name of a single option to get, or false for all options. * @param mixed $default_value Optional. Default value to return, if a single option $name does not exist. * @return mixed Value of the retrieved option $name or $default_value if it does not exist, or all options. */ public function get( /* string|false */ $name = false, /* string|int|float|bool|array|null */ $default_value = null ) /* : string|int|float|bool|array|null */ { if ( false === $name ) { return $this->option_value; } // Single Option wanted. if ( isset( $this->option_value[ $name ] ) ) { return $this->option_value[ $name ]; } else { return $default_value; } } /** * Update Option. * * @since 1.0.0 * * @param array $new_options New options (name => value). * @return bool True on success, false on failure. */ public function update( array $new_options ): bool { $this->option_value = $new_options; return $this->_update_option( $this->option_name, wp_json_encode( $this->option_value, TABLEPRESS_JSON_OPTIONS ) ); // @phpstan-ignore argument.type } /** * Delete Option. * * @since 1.0.0 * * @return bool True on success, false on failure. */ public function delete(): bool { return $this->_delete_option( $this->option_name ); } /* * Internal functions mapping - This needs to be re-defined by child classes. */ /** * Get the value of a WP Option with the WP Options API. * * @since 1.0.0 * * @param string $option_name Name of the WP Option. * @param mixed $default_value Default value of the WP Option. * @return mixed Current value of the WP Option, or $default_value if it does not exist. */ protected function _get_option( string $option_name, /* string|int|float|bool|array|null */ $default_value ) /* : string|int|float|bool|array|null */ { return get_option( $option_name, $default_value ); } /** * Update the value of a WP Option with the WP Options API. * * @since 1.0.0 * * @param string $option_name Name of the WP Option. * @param string $new_value New value of the WP Option. * @return bool True on success, false on failure. */ protected function _update_option( string $option_name, string $new_value ): bool { return update_option( $option_name, $new_value ); } /** * Delete a WP Option with the WP Options API. * * @since 1.0.0 * * @param string $option_name Name of the WP Option. * @return bool True on success, false on failure. */ protected function _delete_option( string $option_name ): bool { return delete_option( $option_name ); } } // class TablePress_WP_Option PK!WT""!classes/class-evaluate-legacy.phpnu[> */ protected array $table_data; /** * Storage for cell ranges that have been replaced in formulas. * * @since 1.0.0 * @var array */ protected array $known_ranges = array(); /** * Initialize the Formula Evaluation class, include the EvalMath class. * * @since 1.0.0 */ public function __construct() { $this->evalmath = TablePress::load_class( 'EvalMath', 'evalmath.class.php', 'libraries' ); // Don't raise PHP warnings. $this->evalmath->suppress_errors = true; } /** * Evaluate formulas in the passed table. * * @since 1.0.0 * * @param array> $table_data Table data in which formulas shall be evaluated. * @param string $table_id ID of the passed table. * @return array> Table data with evaluated formulas. */ public function evaluate_table_data( array $table_data, string $table_id ): array { $this->table_data = $table_data; $num_rows = count( $this->table_data ); // Exit early if there's no actual table data (e.g. after using the Row Filter module). if ( 0 === $num_rows ) { return $this->table_data; } $num_columns = count( $this->table_data[0] ); // Make fixed table data available as variables in formulas. $this->evalmath->variables['table_id'] = $table_id; $this->evalmath->variables['num_rows'] = $num_rows; $this->evalmath->variables['num_columns'] = $num_columns; // Use two for-loops instead of foreach here to be sure to always work on the "live" table data and not some in-memory copy. for ( $row_idx = 0; $row_idx < $num_rows; $row_idx++ ) { for ( $col_idx = 0; $col_idx < $num_columns; $col_idx++ ) { $this->table_data[ $row_idx ][ $col_idx ] = $this->_evaluate_cell( $this->table_data[ $row_idx ][ $col_idx ], $row_idx, $col_idx ); } } return $this->table_data; } /** * Parse and evaluate the content of a cell. * * @since 1.0.0 * * @param string $content Content of a cell. * @param int $row_idx Row index of the cell. * @param int $col_idx Column index of the cell. * @param string[] $parents Optional. List of cells that depend on this cell (to prevent circle references). * @return string Result of the parsing/evaluation. */ protected function _evaluate_cell( string $content, int $row_idx, int $col_idx, array $parents = array() ): string { if ( '' === $content || '=' === $content || '=' !== $content[0] ) { return $content; } // Cut off the leading =. $content = substr( $content, 1 ); // Support putting formulas in strings, like =Total: {A3+A4}. $expressions = array(); if ( preg_match_all( '#{(.+?)}#', $content, $expressions, PREG_SET_ORDER ) ) { $formula_in_string = true; } else { $formula_in_string = false; // Fill array so that it has the same structure as if it came from preg_match_all(). $expressions[] = array( $content, $content ); } foreach ( $expressions as $expression ) { $orig_expression = $expression[0]; $expression = $expression[1]; $replaced_references = array(); $replaced_ranges = array(); // Remove all whitespace characters. $expression = str_replace( array( "\n", "\r", "\t", ' ' ), '', $expression ); // Expand cell ranges (like A3:A6) to a list of single cells (like A3,A4,A5,A6). if ( preg_match_all( '#([A-Z]+)([0-9]+):([A-Z]+)([0-9]+)#', $expression, $referenced_cell_ranges, PREG_SET_ORDER ) ) { foreach ( $referenced_cell_ranges as $cell_range ) { if ( in_array( $cell_range[0], $replaced_ranges, true ) ) { continue; } $replaced_ranges[] = $cell_range[0]; if ( isset( $this->known_ranges[ $cell_range[0] ] ) ) { $expression = (string) preg_replace( '#(?known_ranges[ $cell_range[0] ], $expression ); continue; } // No -1 necessary for this transformation, as we don't actually access the table. $first_col = TablePress::letter_to_number( $cell_range[1] ); $first_row = (int) $cell_range[2]; $last_col = TablePress::letter_to_number( $cell_range[3] ); $last_row = (int) $cell_range[4]; $col_start = min( $first_col, $last_col ); $col_end = max( $first_col, $last_col ) + 1; // +1 for loop below $row_start = min( $first_row, $last_row ); $row_end = max( $first_row, $last_row ) + 1; // +1 for loop below $cell_list = array(); for ( $col = $col_start; $col < $col_end; $col++ ) { for ( $row = $row_start; $row < $row_end; $row++ ) { $column = TablePress::number_to_letter( $col ); $cell_list[] = "{$column}{$row}"; } } $cell_list = implode( ',', $cell_list ); $expression = (string) preg_replace( '#(?known_ranges[ $cell_range[0] ] = $cell_list; } } // Parse and evaluate single cell references (like A3 or XY312), while prohibiting circle references. if ( preg_match_all( '#([A-Z]+)([0-9]+)(?![0-9A-Z\(])#', $expression, $referenced_cells, PREG_SET_ORDER ) ) { foreach ( $referenced_cells as $cell_reference ) { if ( in_array( $cell_reference[0], $parents, true ) ) { return '!ERROR! Circle Reference'; } if ( in_array( $cell_reference[0], $replaced_references, true ) ) { continue; } $replaced_references[] = $cell_reference[0]; $ref_col = TablePress::letter_to_number( $cell_reference[1] ) - 1; $ref_row = (int) $cell_reference[2] - 1; if ( ! isset( $this->table_data[ $ref_row ][ $ref_col ] ) ) { return "!ERROR! Cell {$cell_reference[0]} does not exist"; } $ref_parents = $parents; $ref_parents[] = $cell_reference[0]; $result = $this->_evaluate_cell( $this->table_data[ $ref_row ][ $ref_col ], $ref_row, $ref_col, $ref_parents ); $this->table_data[ $ref_row ][ $ref_col ] = $result; // Bail if there was an error already. if ( str_contains( $result, '!ERROR!' ) ) { return $result; } // Remove all whitespace characters. $result = str_replace( array( "\n", "\r", "\t", ' ' ), '', $result ); // Treat empty cells as 0. if ( '' === $result ) { $result = '0'; } // Bail if the cell does not result in a number (meaning it was a number or expression before being evaluated). if ( ! is_numeric( $result ) ) { return "!ERROR! {$cell_reference[0]} does not contain a number or expression"; } $expression = (string) preg_replace( '#(?_evaluate_math_expression( $expression, $row_idx, $col_idx ); // Support putting formulas in strings, like =Total: {A3+A4}. if ( $formula_in_string ) { $content = str_replace( $orig_expression, $result, $content ); } else { $content = $result; } } return $content; } /** * Evaluate a math expression. * * @since 1.0.0 * * @param string $expression Math expression without leading = sign. * @param int $row_idx Row index of the cell with the expression. * @param int $col_idx Column index of the cell with the expression. * @return string Result of the evaluation. */ protected function _evaluate_math_expression( string $expression, int $row_idx, int $col_idx ): string { // Make current cell's name and row and column number available as variables in formulas. $this->evalmath->variables['row'] = $row_idx + 1; $this->evalmath->variables['column'] = $col_idx + 1; $this->evalmath->variables['cell'] = TablePress::number_to_letter( $this->evalmath->variables['column'] ) . $this->evalmath->variables['row']; // Straight up evaluation, without parsing of variable or function assignments (which is why we only need one instance of the object). $result = $this->evalmath->evaluate( $expression ); if ( false === $result ) { $result = '!ERROR! ' . $this->evalmath->last_error; } return (string) $result; } } // class TablePress_Evaluate_Legacy PK![|gAA'classes/class-import-phpspreadsheet.phpnu[|WP_Error Table array on success, WP_Error on error. */ public function import_table( File $file ) /* : array|WP_Error */ { $data = file_get_contents( $file->location ); if ( false === $data ) { return new WP_Error( 'table_import_phpspreadsheet_data_read', '', $file->location ); } // Remove a possible UTF-8 Byte-Order Mark (BOM). $bom = pack( 'CCC', 0xef, 0xbb, 0xbf ); if ( str_starts_with( $data, $bom ) ) { $data = substr( $data, 3 ); } if ( '' === $data ) { return new WP_Error( 'table_import_phpspreadsheet_data_empty', '', $file->location ); } $table = $this->_maybe_import_json( $data ); if ( is_array( $table ) ) { return $table; } $table = $this->_maybe_import_html( $data ); if ( is_array( $table ) ) { return $table; } return $this->_import_phpspreadsheet( $file ); } /** * Tries to import a table with the JSON format. * * @since 2.0.0 * * @param string $data Data to import. * @return array|false Table array on success, false if the file is not a JSON file. */ protected function _maybe_import_json( string $data ) /* : array|false */ { $data = trim( $data ); // If the file does not begin / end with [ / ] or { / }, it's not a supported JSON file. $first_character = $data[0]; $last_character = $data[-1]; if ( ! ( '[' === $first_character && ']' === $last_character ) && ! ( '{' === $first_character && '}' === $last_character ) ) { return false; } $json_table = json_decode( $data, true ); // Check if JSON could be decoded. If not, this is probably not a JSON file. if ( is_null( $json_table ) ) { return false; } // Specifically cast to an array again. $json_table = (array) $json_table; if ( isset( $json_table['data'] ) ) { // JSON data contained a full export. $table = $json_table; } else { // JSON data contained only the data of a table, but no options. $table = array( 'data' => array() ); foreach ( $json_table as $row ) { // Turn row into indexed arrays with numeric keys. $row = array_values( (array) $row ); // Remove entries of multi-dimensional arrays. foreach ( $row as &$cell ) { if ( is_array( $cell ) ) { $cell = ''; } } unset( $cell ); // Unset use-by-reference parameter of foreach loop. $table['data'][] = $row; } } $this->pad_array_to_max_cols( $table['data'] ); return $table; } /** * Tries to import a table with the HTML format. * * @since 2.0.0 * * @param string $data Data to import. * @return array|WP_Error Table array on success, WP_Error if the file is not an HTML file. */ protected function _maybe_import_html( string $data ) /* : array|false */ { TablePress::load_file( 'html-parser.class.php', 'libraries' ); $table = HTML_Parser::parse( $data ); // Check if the HTML code could be parsed. If not, this is probably not an HTML file. if ( is_wp_error( $table ) ) { return $table; } $this->pad_array_to_max_cols( $table['data'] ); return $table; } /** * Tries to import a table via PHPSpreadsheet. * * @since 2.0.0 * * @param File $file File to import. * @return array|WP_Error Table array on success, WP_Error on error. */ protected function _import_phpspreadsheet( File $file ) /* : array|WP_Error */ { // Rename the temporary file, as PHPSpreadsheet tries to infer the format from the file's extension. if ( '' !== $file->extension ) { $file_data = pathinfo( $file->location ); if ( ! isset( $file_data['extension'] ) || $file->extension !== $file_data['extension'] ) { $temp_file = wp_tempnam(); $new_location = "{$temp_file}.{$file->extension}"; if ( $file->keep_file ) { // Copy the file, as the original should be kept. if ( copy( $file->location, $new_location ) ) { $file->location = $new_location; $file->keep_file = false; // Delete the newly created file after the import. } } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found if ( rename( $file->location, $new_location ) ) { $file->location = $new_location; } } } } try { // Treat all cell values as strings, except for formulas (due to recognition of quoted/escaped formulas like `'=A2`). \TablePress\PhpOffice\PhpSpreadsheet\Cell\Cell::setValueBinder( new \TablePress\PhpOffice\PhpSpreadsheet\Cell\StringValueBinder() ); \TablePress\PhpOffice\PhpSpreadsheet\Cell\Cell::getValueBinder()->setFormulaConversion( false ); // @phpstan-ignore method.notFound /* * Try to detect a reader from the file extension and MIME type. * Fall back to CSV if no reader could be determined. */ try { $reader = \TablePress\PhpOffice\PhpSpreadsheet\IOFactory::createReaderForFile( $file->location ); } catch ( \TablePress\PhpOffice\PhpSpreadsheet\Reader\Exception $exception ) { $reader = \TablePress\PhpOffice\PhpSpreadsheet\IOFactory::createReader( 'Csv' ); // Change the file extension to .csv, so that \TablePress\PhpOffice\PhpSpreadsheet\Reader\Csv::canRead() returns true. $temp_file = wp_tempnam(); $new_location = "{$temp_file}.csv"; if ( $file->keep_file ) { // Copy the file, as the original should be kept. if ( copy( $file->location, $new_location ) ) { $file->location = $new_location; $file->keep_file = false; // Delete the newly created file after the import. } } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found if ( rename( $file->location, $new_location ) ) { $file->location = $new_location; } } } $class_name = get_class( $reader ); $class_type = explode( '\\', $class_name ); $detected_format = strtolower( array_pop( $class_type ) ); if ( 'csv' === $detected_format ) { $reader->setInputEncoding( \TablePress\PhpOffice\PhpSpreadsheet\Reader\Csv::GUESS_ENCODING ); // @phpstan-ignore method.notFound // @phpstan-ignore method.notFound, smaller.alwaysFalse (PHPStan thinks that the Composer minimum version will always be fulfilled.) $reader->setEscapeCharacter( ( PHP_VERSION_ID < 70400 ) ? "\x0" : '' ); // Disable the proprietary escape mechanism of PHP's fgetcsv() in PHP >= 7.4. } $reader->setIncludeCharts( false ); $reader->setReadEmptyCells( true ); // For non-Excel files, import only the data, but ignore formatting. if ( ! in_array( $detected_format, array( 'xlsx', 'xls' ), true ) ) { $reader->setReadDataOnly( true ); } // For formats where it's supported, import only the first sheet. if ( in_array( $detected_format, array( 'csv', 'html', 'slk' ), true ) ) { $reader->setSheetIndex( 0 ); // @phpstan-ignore method.notFound } $spreadsheet = $reader->load( $file->location ); $worksheet = $spreadsheet->getActiveSheet(); $cell_collection = $worksheet->getCellCollection(); $comments = $worksheet->getComments(); $table = array( 'data' => array(), ); $min_col = 'A'; $min_row = 1; $max_col = $worksheet->getHighestColumn(); $max_row = $worksheet->getHighestRow(); // Adapted from \TablePress\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::rangeToArray(). ++$max_col; // Due to for-loop with characters for columns. for ( $row = $min_row; $row <= $max_row; $row++ ) { $row_data = array(); for ( $col = $min_col; $col !== $max_col; $col++ ) { $cell_reference = $col . $row; if ( ! $cell_collection->has( $cell_reference ) ) { $row_data[] = ''; continue; } $cell = $cell_collection->get( $cell_reference ); $value = $cell->getValue(); if ( is_null( $value ) ) { $row_data[] = ''; continue; } $cell_has_hyperlink = $worksheet->hyperlinkExists( $cell_reference ) && ! $worksheet->getHyperlink( $cell_reference )->isInternal(); if ( $value instanceof \TablePress\PhpOffice\PhpSpreadsheet\RichText\RichText ) { $cell_data = $this->parse_rich_text( $value, $cell_has_hyperlink ); } else { $cell_data = (string) $value; } // Apply data type formatting. $style = $spreadsheet->getCellXfByIndex( $cell->getXfIndex() ); $format = $style->getNumberFormat()->getFormatCode() ?? \TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_GENERAL; /* * When cells in Excel files are formatted as "Text", quotation marks are removed, due to https://github.com/PHPOffice/PhpSpreadsheet/pull/3344. * Setting the format to "General" seems to prevent that. */ if ( \TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_TEXT === $format && ! is_numeric( $cell_data ) ) { $format = \TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_GENERAL; } // Fix floating point precision issues with numbers in the "General" Excel .xlsx format. if ( 'xlsx' === $detected_format && \TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_GENERAL === $format && is_numeric( $cell_data ) ) { $cell_data = (string) (float) $cell_data; // Type-cast strings to float and back. } $cell_data = \TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat::toFormattedString( $cell_data, $format, array( $this, 'format_color' ), ); if ( strlen( $cell_data ) > 1 && '=' === $cell_data[0] ) { if ( 'xlsx' === $detected_format && $style->getQuotePrefix() ) { // Prepend a ' to quoted/escaped formulas (so that they are shown as text). This is currently not supported (at least) for the XLS format. $cell_data = "'{$cell_data}"; } else { // Bail early, to not add inline HTML styling around formulas, as they won't work anymore then. $row_data[] = $cell_data; continue; } } $font = $style->getFont(); if ( $font->getSuperscript() ) { $cell_data = "{$cell_data}"; } if ( $font->getSubscript() ) { $cell_data = "{$cell_data}"; } if ( $font->getStrikethrough() ) { $cell_data = "{$cell_data}"; } if ( $font->getUnderline() !== \TablePress\PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_NONE && ! $cell_has_hyperlink ) { $cell_data = "{$cell_data}"; } if ( $font->getBold() ) { $cell_data = "{$cell_data}"; } if ( $font->getItalic() ) { $cell_data = "{$cell_data}"; } $color = $font->getColor()->getRGB(); if ( '' !== $color && '000000' !== $color && ! $cell_has_hyperlink ) { // Don't add the span if the color is black, as that's the default, or if it's in a hyperlink. $color_css = esc_attr( "color:#{$color};" ); $cell_data = "{$cell_data}"; } // Convert Hyperlinks to HTML code. if ( $cell_has_hyperlink ) { $url = $worksheet->getHyperlink( $cell_reference )->getUrl(); if ( '' !== $url ) { $title = $worksheet->getHyperlink( $cell_reference )->getTooltip(); if ( '' !== $title ) { $title = ' title="' . esc_attr( $title ) . '"'; } $url = esc_url( $url ); $cell_data = "{$cell_data}"; } } // Add comments. if ( isset( $comments[ $cell_reference ] ) ) { $sanitized_comment = esc_html( $worksheet->getComment( $cell_reference )->getText()->getPlainText() ); if ( '' !== $sanitized_comment ) { $cell_data .= '
' . $sanitized_comment . '
'; } } $row_data[] = $cell_data; } $table['data'][] = $row_data; } // Convert merged cells to trigger words. $merged_cells = $worksheet->getMergeCells(); foreach ( $merged_cells as $merged_cells_range ) { $cells = explode( ':', $merged_cells_range ); $first_cell = \TablePress\PhpOffice\PhpSpreadsheet\Cell\Coordinate::indexesFromString( $cells[0] ); $last_cell = \TablePress\PhpOffice\PhpSpreadsheet\Cell\Coordinate::indexesFromString( $cells[1] ); for ( $row_idx = $first_cell[1]; $row_idx <= $last_cell[1]; $row_idx++ ) { for ( $column_idx = $first_cell[0]; $column_idx <= $last_cell[0]; $column_idx++ ) { if ( $row_idx === $first_cell[1] && $column_idx === $first_cell[0] ) { continue; // Keep value of first cell. } elseif ( $row_idx === $first_cell[1] && $column_idx > $first_cell[0] ) { $table['data'][ $row_idx - 1 ][ $column_idx - 1 ] = '#colspan#'; } elseif ( $row_idx > $first_cell[1] && $column_idx === $first_cell[0] ) { $table['data'][ $row_idx - 1 ][ $column_idx - 1 ] = '#rowspan#'; } else { $table['data'][ $row_idx - 1 ][ $column_idx - 1 ] = '#span#'; } } } } // Save PHP memory. $spreadsheet->disconnectWorksheets(); unset( $comments, $cell_collection, $worksheet, $spreadsheet ); return $table; } catch ( \TablePress\PhpOffice\PhpSpreadsheet\Reader\Exception | \TablePress\PhpOffice\PhpSpreadsheet\Exception $exception ) { return new WP_Error( 'table_import_phpspreadsheet_failed', '', 'Exception: ' . $exception->getMessage() ); } } /** * Parses PHPSpreadsheet RichText elements and converts formatting to HTML tags. * * @param \TablePress\PhpOffice\PhpSpreadsheet\RichText\RichText $value RichText element. * @param bool $cell_has_hyperlink Whether the cell has a hyperlink. * @return string Cell value with HTML formatting. */ protected function parse_rich_text( \TablePress\PhpOffice\PhpSpreadsheet\RichText\RichText $value, bool $cell_has_hyperlink ): string { $cell_data = ''; $elements = $value->getRichTextElements(); foreach ( $elements as $element ) { $element_data = $element->getText(); // Rich text start? if ( $element instanceof \TablePress\PhpOffice\PhpSpreadsheet\RichText\Run ) { $font = $element->getFont(); if ( $font->getSuperscript() ) { $element_data = "{$element_data}"; } if ( $font->getSubscript() ) { $element_data = "{$element_data}"; } if ( $font->getStrikethrough() ) { $element_data = "{$element_data}"; } if ( $font->getUnderline() !== \TablePress\PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_NONE ) { $element_data = "{$element_data}"; } if ( $font->getBold() ) { $element_data = "{$element_data}"; } if ( $font->getItalic() ) { $element_data = "{$element_data}"; } $color = $font->getColor()->getRGB(); if ( '' !== $color && '000000' !== $color && ! $cell_has_hyperlink ) { // Don't add the span if the color is black, as that's the default, or if it's in a hyperlink. $color_css = esc_attr( "color:#{$color};" ); $element_data = "{$element_data}"; } } $cell_data .= $element_data; } return $cell_data; } /** * Adds color to formatted string as inline style, e.g. from conditional formatting. * * @param string $value Plain formatted value without color. * @param string $format_code Format code. * @return string Value with color format applied. */ public function format_color( string $value, string $format_code ): string { // Color information, e.g. [Red] is always at the beginning of the format code. $color = ''; if ( 1 === preg_match( '/^\\[[a-zA-Z]+\\]/', $format_code, $matches ) ) { $color = str_replace( array( '[', ']' ), '', $matches[0] ); $color = strtolower( $color ); } if ( '' !== $color ) { $color = esc_attr( "color:{$color};" ); $value = "{$value}"; } return $value; } } // class TablePress_Import_PHPSpreadsheet PK!7vUclasses/class-controller.phpnu[plugin_update_check(); } /** * Check if the plugin was updated and perform necessary actions, like updating the options. * * @since 1.0.0 */ protected function plugin_update_check(): void { // First activation or plugin update. $current_plugin_options_db_version = TablePress::$model_options->get( 'plugin_options_db_version' ); if ( $current_plugin_options_db_version < TablePress::db_version ) { // Allow more PHP execution time for update process. if ( function_exists( 'set_time_limit' ) ) { @set_time_limit( 300 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged } // Add TablePress capabilities to the WP_Roles objects, for new installations and all versions below 12. if ( $current_plugin_options_db_version < 12 ) { TablePress::$model_options->add_access_capabilities(); } if ( 0 === TablePress::$model_options->get( 'first_activation' ) ) { // Save initial set of plugin options, and time of first activation of the plugin, on first activation. TablePress::$model_options->update( array( 'first_activation' => time(), 'plugin_options_db_version' => TablePress::db_version, ) ); } else { // Update Plugin Options Options, if necessary. TablePress::$model_options->merge_plugin_options_defaults(); $updated_options = array( 'plugin_options_db_version' => TablePress::db_version, 'prev_tablepress_version' => TablePress::$model_options->get( 'tablepress_version' ), 'tablepress_version' => TablePress::version, 'message_plugin_update' => true, ); // If used, re-save "Custom CSS" to re-create all files (as TablePress Default CSS might have changed). $custom_css = TablePress::$model_options->get( 'custom_css' ); if ( TablePress::$model_options->get( 'use_custom_css' ) && '' !== $custom_css ) { /** * Load WP file functions to provide filesystem access functions early. */ require_once ABSPATH . 'wp-admin/includes/file.php'; // @phpstan-ignore requireOnce.fileNotFound (This is a WordPress core file that always exists.) /** * Load WP admin template functions to provide `submit_button()` which is necessary for `request_filesystem_credentials()`. */ require_once ABSPATH . 'wp-admin/includes/template.php'; // @phpstan-ignore requireOnce.fileNotFound (This is a WordPress core file that always exists.) $tablepress_css = TablePress::load_class( 'TablePress_CSS', 'class-css.php', 'classes' ); $custom_css_minified = TablePress::$model_options->get( 'custom_css_minified' ); // Update "Custom CSS" to be compatible with DataTables 2, introduced in TablePress 3.0. if ( $current_plugin_options_db_version < 96 ) { $old_custom_css = $custom_css; $custom_css = TablePress::convert_datatables_api_data( $custom_css ); if ( $old_custom_css !== $custom_css ) { $custom_css = $tablepress_css->sanitize_css( $custom_css ); $custom_css_minified = $tablepress_css->minify_css( $custom_css ); $updated_options['custom_css'] = $custom_css; $updated_options['custom_css_minified'] = $custom_css_minified; } unset( $old_custom_css ); } $result = $tablepress_css->save_custom_css_to_file( $custom_css, $custom_css_minified ); // If saving was successful, use "Custom CSS" file. $updated_options['use_custom_css_file'] = $result; // Increase the "Custom CSS" version number for cache busting. if ( $result ) { $updated_options['custom_css_version'] = TablePress::$model_options->get( 'custom_css_version' ) + 1; } } TablePress::$model_options->update( $updated_options ); // Clear table caches. TablePress::$model_table->invalidate_table_output_caches(); // Add mime type field to existing posts with the TablePress Custom Post Type, in TablePress 1.5. if ( $current_plugin_options_db_version < 25 ) { TablePress::$model_table->add_mime_type_to_posts(); } // Add new access capabilities that were introduced in TablePress 2.3.2. if ( $current_plugin_options_db_version < 77 ) { TablePress::$model_options->add_access_capabilities_tp232(); } // Update all tables' "Custom Commands" to be compatible with DataTables 2, introduced in TablePress 3.0. if ( $current_plugin_options_db_version < 96 ) { TablePress::$model_table->update_custom_commands_datatables_tp30(); } } } // Maybe update the table scheme in each existing table, independently from updating the plugin options. if ( TablePress::$model_options->get( 'table_scheme_db_version' ) < TablePress::table_scheme_version ) { TablePress::$model_table->merge_table_options_defaults(); TablePress::$model_options->update( 'table_scheme_db_version', TablePress::table_scheme_version ); } /* * Update User Options, if necessary. * User Options are not saved in DB until first change occurs. */ if ( is_user_logged_in() && TablePress::$model_options->get( 'user_options_db_version' ) < TablePress::db_version ) { TablePress::$model_options->merge_user_options_defaults(); $updated_options = array( 'user_options_db_version' => TablePress::db_version, 'message_superseded_extensions' => true, ); TablePress::$model_options->update( $updated_options ); } } } // class TablePress_Controller PK!h_OOclasses/class-model.phpnu[ */ public array $export_formats = array(); /** * Delimiters for the CSV export. * * @since 1.0.0 * @var array */ public array $csv_delimiters = array(); /** * Whether ZIP archive support is available in the PHP installation on the server. * * @since 1.0.0 */ public bool $zip_support_available = false; /** * Initialize the Export class. * * @since 1.0.0 */ public function __construct() { // Initiate here, because function call not possible outside a class method. $this->export_formats = array( 'csv' => __( 'CSV - Character-Separated Values', 'tablepress' ), 'html' => __( 'HTML - Hypertext Markup Language', 'tablepress' ), 'json' => __( 'JSON - JavaScript Object Notation', 'tablepress' ), ); $this->csv_delimiters = array( ';' => __( '; (semicolon)', 'tablepress' ), ',' => __( ', (comma)', 'tablepress' ), 'tab' => __( '\t (tabulator)', 'tablepress' ), ); if ( class_exists( 'ZipArchive', false ) ) { $this->zip_support_available = true; } } /** * Export a table. * * @since 1.0.0 * * @param array $table Table to be exported. * @param string $export_format Format for the export ('csv', 'html', 'json'). * @param string $csv_delimiter Delimiter for CSV export. * @return string Exported table (only data for CSV and HTML, full tables (including options) for JSON). */ public function export_table( array $table, string $export_format, string $csv_delimiter ): string { switch ( $export_format ) { case 'csv': $output = ''; if ( 'tab' === $csv_delimiter ) { $csv_delimiter = "\t"; } foreach ( $table['data'] as $row_idx => $row ) { $csv_row = array(); foreach ( $row as $column_idx => $cell_content ) { $csv_row[] = $this->csv_wrap_and_escape( $cell_content, $csv_delimiter ); } $output .= implode( $csv_delimiter, $csv_row ); $output .= "\n"; } break; case 'html': $num_rows = count( $table['data'] ); $last_row_idx = $num_rows - 1; $thead = ''; $tfoot = ''; $tbody = array(); foreach ( $table['data'] as $row_idx => $row ) { // Table head rows, but only if there's at least one additional row. if ( $row_idx < $table['options']['table_head'] && $num_rows > $table['options']['table_head'] ) { $thead = $this->html_render_row( $row, 'th' ); continue; } // Table foot rows, but only if there's at least one additional row. if ( $row_idx > $last_row_idx - $table['options']['table_foot'] && $num_rows > $table['options']['table_foot'] ) { $tfoot = $this->html_render_row( $row, 'th' ); continue; } // Neither first nor last row (with respective head/foot enabled), so render as body row. $tbody[] = $this->html_render_row( $row, 'td' ); } // , , and tags. if ( ! empty( $thead ) ) { $thead = "\t\n{$thead}\t\n"; } if ( ! empty( $tfoot ) ) { $tfoot = "\t\n{$tfoot}\t\n"; } $tbody = "\t\n" . implode( '', $tbody ) . "\t\n"; $output = "\n" . $thead . $tfoot . $tbody . "
\n"; break; case 'json': $output = wp_json_encode( $table, TABLEPRESS_JSON_OPTIONS ); if ( false === $output ) { $output = ''; } break; default: $output = ''; } return $output; } /** * Wrap and escape a cell for CSV export. * * @since 1.0.0 * * @param string $cell_content Content of a cell. * @param string $delimiter CSV delimiter character. * @return string Wrapped string for CSV export. */ protected function csv_wrap_and_escape( string $cell_content, string $delimiter ): string { // Return early if the cell is empty. No escaping or wrapping is needed then. if ( '' === $cell_content ) { return $cell_content; } // Escape potentially dangerous functions that could be used for CSV injection attacks in external spreadsheet software. $active_content_triggers = array( '=', '+', '-', '@' ); if ( in_array( $cell_content[0], $active_content_triggers, true ) ) { $functions_to_escape = array( 'cmd|', 'rundll32', 'DDE(', 'IMPORTXML(', 'IMPORTFEED(', 'IMPORTHTML(', 'IMPORTRANGE(', 'IMPORTDATA(', 'IMAGE(', 'HYPERLINK(', 'WEBSERVICE(', ); foreach ( $functions_to_escape as $function ) { if ( false !== stripos( $cell_content, $function ) ) { $cell_content = "'" . $cell_content; // Prepend a ' to indicate that the cell format is a text string. break; } } } // Escape CSV delimiter for RegExp (e.g. '|'). $delimiter = preg_quote( $delimiter, '#' ); if ( 1 === preg_match( '#' . $delimiter . '|"|\n|\r#i', $cell_content ) || str_starts_with( $cell_content, ' ' ) || str_ends_with( $cell_content, ' ' ) ) { // Escape single " as double "". $cell_content = str_replace( '"', '""', $cell_content ); // Wrap string in "". $cell_content = '"' . $cell_content . '"'; } return $cell_content; } /** * Generate the HTML of a row. * * @since 1.0.0 * * @param string[] $row Cells of the row to be rendered. * @param string $tag HTML tag to use for the cells (td or th). * @return string HTML code for the row. */ protected function html_render_row( array $row, string $tag ): string { $output = "\t\t\n"; array_walk( $row, array( $this, 'html_wrap_and_escape' ), $tag ); $output .= implode( '', $row ); $output .= "\t\t\n"; return $output; } /** * Wrap and escape a cell for HTML export. * * @since 1.0.0 * * @param string $cell_content Content of a cell. * @param int $column_idx Column index, or -1 if omitted. Unused, but defined to be able to use function as callback in array_walk(). * @param string $html_tag HTML tag that shall be used for the cell. */ protected function html_wrap_and_escape( string &$cell_content, int $column_idx, string $html_tag ): void { /* * Replace any & with & that is not already an encoded entity (from function htmlentities2 in WP 2.8). * A complete htmlentities2() or htmlspecialchars() would encode tags, which we don't want. */ $cell_content = (string) preg_replace( '/&(?![A-Za-z]{0,4}\w{2,3};|#[0-9]{2,4};)/', '&', $cell_content ); $cell_content = "\t\t\t<{$html_tag}>{$cell_content}\n"; } } // class TablePress_Export PK!@classes/class-import-file.phpnu[ $properties Array of file properties. */ public function __construct( array $properties ) { foreach ( $properties as $property => $value ) { if ( property_exists( $this, $property ) ) { $this->$property = $value; } } } } // class File PK!zb classes/class-wp_user_option.phpnu[ID, $this->option_name, false ); } } } // class TablePress_WP_User_Option PK!O[%%classes/class-import-legacy.phpnu[|false */ protected $imported_table = false; /** * Initialize the Import class. * * @since 1.0.0 */ public function __construct() { if ( class_exists( 'DOMDocument', false ) && function_exists( 'simplexml_import_dom' ) && function_exists( 'libxml_use_internal_errors' ) ) { $this->html_import_support_available = true; } if ( $this->html_import_support_available ) { $this->import_formats[] = 'html'; } } /** * Import a table. * * @since 1.0.0 * * @param string $format Import format. * @param string $data Data to import. * @return array|false Table array on success, false on error. */ public function import_table( string $format, string $data ) /* : array|false */ { /** * Filters the data that is to be imported. * * @since 1.8.1 * * @param string $data Data to import. * @param string $format Import format. */ $this->import_data = apply_filters( 'tablepress_import_table_data', $data, $format ); if ( ! in_array( $format, array( 'xlsx', 'xls' ), true ) ) { $this->fix_table_encoding(); } switch ( $format ) { case 'csv': $this->import_csv(); break; case 'html': $this->import_html(); break; case 'json': $this->import_json(); break; case 'xlsx': $this->import_xlsx(); break; case 'xls': $this->import_xls(); break; default: return false; } if ( false === $this->imported_table ) { return false; } // Make sure that cells are stored as strings. array_walk_recursive( $this->imported_table['data'], static function ( /* string|int|float|bool|null */ &$cell_content, int $col_idx ): void { $cell_content = (string) $cell_content; }, ); return $this->imported_table; } /** * Import CSV data. * * @since 1.0.0 */ protected function import_csv(): void { $csv_parser = TablePress::load_class( 'CSV_Parser', 'csv-parser.class.php', 'libraries' ); $csv_parser->load_data( $this->import_data ); $delimiter = $csv_parser->find_delimiter(); $data = $csv_parser->parse( $delimiter ); $this->pad_array_to_max_cols( $data ); $this->normalize_line_endings( $data ); $this->imported_table = array( 'data' => $data ); } /** * Import HTML data. * * @since 1.0.0 */ protected function import_html(): void { if ( ! $this->html_import_support_available ) { return; } TablePress::load_file( 'html-parser.class.php', 'libraries' ); $table = HTML_Parser::parse( $this->import_data ); if ( is_wp_error( $table ) ) { $this->imported_table = false; return; } $this->pad_array_to_max_cols( $table['data'] ); $this->normalize_line_endings( $table['data'] ); $this->imported_table = $table; } /** * Import JSON data. * * @since 1.0.0 */ protected function import_json(): void { $json_table = json_decode( $this->import_data, true ); // Check if JSON could be decoded. if ( is_null( $json_table ) ) { $json_error = json_last_error_msg(); $output = '' . __( 'The imported file contains errors:', 'tablepress' ) . "

JSON error: {$json_error}
"; wp_die( $output, 'Import Error', array( 'response' => 200, 'back_link' => true ) ); } // Specifically cast to an array again. $json_table = (array) $json_table; if ( isset( $json_table['data'] ) ) { // JSON data contained a full export. $table = $json_table; } else { // JSON data contained only the data of a table, but no options. $table = array( 'data' => array() ); foreach ( $json_table as $row ) { $table['data'][] = array_values( (array) $row ); } } $this->pad_array_to_max_cols( $table['data'] ); $this->imported_table = $table; } /** * Import Microsoft Excel 97-2003 data. * * @since 1.1.0 */ protected function import_xls(): void { $excel_reader = TablePress::load_class( 'Spreadsheet_Excel_Reader', 'excel-reader.class.php', 'libraries', $this->import_data ); // Loop through Excel file and retrieve value and colspan/rowspan properties for each cell. $sheet = 0; // 0 means first sheet of the Workbook $table = array(); $num_rows = $excel_reader->rowcount( $sheet ); $num_columns = $excel_reader->colcount( $sheet ); for ( $row = 1; $row <= $num_rows; $row++ ) { $table_row = array(); for ( $column = 1; $column <= $num_columns; $column++ ) { $cell = array(); $cell['rowspan'] = $excel_reader->rowspan( $row, $column, $sheet ); $cell['colspan'] = $excel_reader->colspan( $row, $column, $sheet ); $cell['val'] = $excel_reader->val( $row, $column, $sheet ); $table_row[] = $cell; } $table[] = $table_row; } // Transform colspan/rowspan properties to TablePress equivalent (cell content). foreach ( $table as $row_idx => $row ) { foreach ( $row as $col_idx => $cell ) { if ( 1 === $cell['rowspan'] && 1 === $cell['colspan'] ) { continue; } if ( 1 < $cell['colspan'] ) { for ( $i = 1; $i < $cell['colspan']; $i++ ) { $table[ $row_idx ][ $col_idx + $i ]['val'] = '#colspan#'; } } if ( 1 < $cell['rowspan'] ) { for ( $i = 1; $i < $cell['rowspan']; $i++ ) { $table[ $row_idx + $i ][ $col_idx ]['val'] = '#rowspan#'; } } if ( 1 < $cell['rowspan'] && 1 < $cell['colspan'] ) { for ( $i = 1; $i < $cell['rowspan']; $i++ ) { for ( $j = 1; $j < $cell['colspan']; $j++ ) { $table[ $row_idx + $i ][ $col_idx + $j ]['val'] = '#span#'; } } } } } // Flatten value property to two-dimensional array. foreach ( $table as &$row ) { foreach ( $row as &$cell ) { $cell = $cell['val']; } unset( $cell ); // Unset use-by-reference parameter of foreach loop. } unset( $row ); // Unset use-by-reference parameter of foreach loop. $this->imported_table = array( 'data' => $table ); } /** * Import Microsoft Excel 2007-2019 data. * * @since 1.1.0 */ protected function import_xlsx(): void { TablePress::load_file( 'simplexlsx.class.php', 'libraries' ); $xlsx_file = \Shuchkin\SimpleXLSX::parse( $this->import_data, true ); if ( ! $xlsx_file ) { $output = '' . __( 'The imported file contains errors:', 'tablepress' ) . '

' . \Shuchkin\SimpleXLSX::parseError() . '
'; wp_die( $output, 'Import Error', array( 'response' => 200, 'back_link' => true ) ); } $this->imported_table = array( 'data' => $xlsx_file->rows() ); } /** * Fixes the encoding to UTF-8 for the entire string that is to be imported. * * @since 1.0.0 * * @link http://stevephillips.me/blog/dealing-php-and-character-encoding */ protected function fix_table_encoding(): void { // Check and remove possible UTF-8 Byte-Order Mark (BOM). $bom = pack( 'CCC', 0xef, 0xbb, 0xbf ); if ( str_starts_with( $this->import_data, $bom ) ) { $this->import_data = substr( $this->import_data, 3 ); // If data has a BOM, it's UTF-8, so further checks unnecessary. return; } // Require the iconv() function for the following checks. if ( ! function_exists( 'iconv' ) ) { return; } // Check for possible UTF-16 BOMs ("little endian" and "big endian") and try to convert the data to UTF-8. if ( str_starts_with( $this->import_data, "\xFF\xFE" ) || str_starts_with( $this->import_data, "\xFE\xFF" ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged $data = @iconv( 'UTF-16', 'UTF-8', $this->import_data ); if ( false !== $data ) { $this->import_data = $data; return; } } // Detect the character encoding and convert to UTF-8, if it's different. if ( function_exists( 'mb_detect_encoding' ) ) { $current_encoding = mb_detect_encoding( $this->import_data, 'ASCII, UTF-8, ISO-8859-1' ); if ( 'UTF-8' !== $current_encoding ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged $data = @iconv( $current_encoding, 'UTF-8', $this->import_data ); // @phpstan-ignore argument.type if ( false !== $data ) { $this->import_data = $data; return; } } } } /** * Replaces Windows line breaks \r\n with Unix line breaks \n. * * This function uses call by reference to save PHP memory on large arrays. * * @since 2.0.0 * * @param array> $an_array Array in which Windows line breaks should be replaced by Unix line breaks. */ protected function normalize_line_endings( array &$an_array ): void { array_walk_recursive( $an_array, static function ( string &$cell_content, int $col_idx ): void { $cell_content = str_replace( "\r\n", "\n", $cell_content ); }, ); } } // class TablePress_Import_Legacy PK!classes/index.phpnu[ */ public static array $modules = array(); /** * Start-up TablePress (run on WordPress "init") and load the controller for the current state. * * @since 1.0.0 */ public static function run(): void { /** * Fires before TablePress is loaded. * * The `tablepress_loaded` action hook might be a better choice in most situations, as TablePress options will then be available. * * @since 1.0.0 */ do_action( 'tablepress_run' ); /** * Filters the string that is used as the [table] Shortcode. * * @since 1.0.0 * * @param string $shortcode The [table] Shortcode string. */ self::$shortcode = apply_filters( 'tablepress_table_shortcode', self::$shortcode ); /** * Filters the string that is used as the [table-info] Shortcode. * * @since 1.0.0 * * @param string $shortcode_info The [table-info] Shortcode string. */ self::$shortcode_info = apply_filters( 'tablepress_table_info_shortcode', self::$shortcode_info ); // Load modals for table and options, to be accessible from everywhere via `TablePress::$model_options` and `TablePress::$model_table`. self::$model_options = self::load_model( 'options' ); self::$model_table = self::load_model( 'table' ); // Exit early, i.e. before a controller is loaded, if TablePress functionality is likely not needed. $exit_early = false; if ( ( isset( $_SERVER['SCRIPT_FILENAME'] ) && 'wp-login.php' === basename( $_SERVER['SCRIPT_FILENAME'] ) ) // Detect the WordPress Login screen. || ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) || wp_doing_cron() ) { $exit_early = true; } /** * Filters whether TablePress should exit early, e.g. during wp-login.php, XML-RPC, and WP-Cron requests. * * @since 2.0.0 * * @param bool $exit_early Whether TablePress should exit early. */ if ( apply_filters( 'tablepress_exit_early', $exit_early ) ) { return; } if ( is_admin() ) { $controller = 'admin'; if ( wp_doing_ajax() ) { $controller = 'admin_ajax'; } self::load_controller( $controller ); } // Load the frontend controller in all scenarios, so that Shortcode render functions are always available. self::$controller = self::load_controller( 'frontend' ); // Add filters and actions for the integration into the WP WXR exporter and importer. add_action( 'wp_import_insert_post', array( TablePress::$model_table, 'add_table_id_on_wp_import' ), 10, 4 ); // phpcs:ignore Squiz.Classes.SelfMemberReference.NotUsed add_filter( 'wp_import_post_meta', array( TablePress::$model_table, 'prevent_table_id_post_meta_import_on_wp_import' ), 10, 3 ); // phpcs:ignore Squiz.Classes.SelfMemberReference.NotUsed add_filter( 'wxr_export_skip_postmeta', array( TablePress::$model_table, 'add_table_id_to_wp_export' ), 10, 3 ); // phpcs:ignore Squiz.Classes.SelfMemberReference.NotUsed /** * Fires after TablePress is loaded. * * The `tablepress_run` action hook can be used if code has to run before TablePress is loaded. * * @since 2.0.0 */ do_action( 'tablepress_loaded' ); } /** * Load a file with require_once(), after running it through a filter. * * @since 1.0.0 * * @param string $file Name of the PHP file. * @param string $folder Name of the folder with the file. */ public static function load_file( string $file, string $folder ): void { $full_path = TABLEPRESS_ABSPATH . $folder . '/' . $file; /** * Filters the full path of a file that shall be loaded. * * @since 1.0.0 * * @param string $full_path Full path of the file that shall be loaded. * @param string $file File name of the file that shall be loaded. * @param string $folder Folder name of the file that shall be loaded. */ $full_path = apply_filters( 'tablepress_load_file_full_path', $full_path, $file, $folder ); if ( $full_path ) { require_once $full_path; } } /** * Create a new instance of the $class_name, which is stored in $file in the $folder subfolder * of the plugin's directory. * * @since 1.0.0 * * @param string $class_name Name of the class. * @param string $file Name of the PHP file with the class. * @param string $folder Name of the folder with $class_name's $file. * @param mixed[]|string|null $params Optional. Parameters that are passed to the constructor of $class_name. * @return object Initialized instance of the class. */ public static function load_class( string $class_name, string $file, string $folder, /* ?array|string */ $params = null ): object { /** * Filters name of the class that shall be loaded. * * @since 1.0.0 * * @param string $class_name Name of the class that shall be loaded. */ $class_name = apply_filters( 'tablepress_load_class_name', $class_name ); if ( ! class_exists( $class_name, false ) ) { self::load_file( $file, $folder ); } $the_class = new $class_name( $params ); return $the_class; } /** * Create a new instance of the $model, which is stored in the "models" subfolder. * * @since 1.0.0 * * @param string $model Name of the model. * @return object Instance of the initialized model. */ public static function load_model( string $model ): object { // Model Base Class. self::load_file( 'class-model.php', 'classes' ); // Make first letter uppercase for a better looking naming pattern. $ucmodel = ucfirst( $model ); $the_model = self::load_class( "TablePress_{$ucmodel}_Model", "model-{$model}.php", 'models' ); return $the_model; } /** * Create a new instance of the $view, which is stored in the "views" subfolder, and set it up with $data. * * @since 1.0.0 * * @param string $view Name of the view to load. * @param array $data Optional. Parameters/PHP variables that shall be available to the view. * @return object Instance of the initialized view, already set up, just needs to be rendered. */ public static function load_view( string $view, array $data = array() ): object { // View Base Class. self::load_file( 'class-view.php', 'classes' ); // Make first letter uppercase for a better looking naming pattern. $ucview = ucfirst( $view ); $the_view = self::load_class( "TablePress_{$ucview}_View", "view-{$view}.php", 'views' ); $the_view->setup( $view, $data ); return $the_view; } /** * Create a new instance of the $controller, which is stored in the "controllers" subfolder. * * @since 1.0.0 * * @param string $controller Name of the controller. * @return object Instance of the initialized controller. */ public static function load_controller( string $controller ): object { // Controller Base Class. self::load_file( 'class-controller.php', 'classes' ); // Make first letter uppercase for a better looking naming pattern. $uccontroller = ucfirst( $controller ); $the_controller = self::load_class( "TablePress_{$uccontroller}_Controller", "controller-{$controller}.php", 'controllers' ); return $the_controller; } /** * Generate the complete nonce string, from the nonce base, the action and an item, e.g. tablepress_delete_table_3. * * @since 1.0.0 * * @param string $action Action for which the nonce is needed. * @param string|false $item Optional. Item for which the action will be performed, like "table". false if no item should be used in the nonce. * @return string The resulting nonce string. */ public static function nonce( string $action, /* string|false */ $item = false ): string { $nonce = "tablepress_{$action}"; if ( $item ) { $nonce .= "_{$item}"; } return $nonce; } /** * Check whether a nonce string is valid. * * @since 1.0.0 * * @param string $action Action for which the nonce should be checked. * @param string|false $item Optional. Item for which the action should be performed, like "table". false if no item should be used in the nonce. * @param string $query_arg Optional. Name of the nonce query string argument in $_POST. * @param bool $ajax Whether the nonce comes from an AJAX request. */ public static function check_nonce( string $action, /* string|false */ $item = false, string $query_arg = '_wpnonce', bool $ajax = false ): void { $nonce_action = self::nonce( $action, $item ); if ( $ajax ) { check_ajax_referer( $nonce_action, $query_arg ); } else { check_admin_referer( $nonce_action, $query_arg ); } } /** * Calculate the column index (number) of a column header string (example: A is 1, AA is 27, ...). * * For the opposite, @see number_to_letter(). * * @since 1.0.0 * * @param string $column Column string. * @return int Column number, 1-based. */ public static function letter_to_number( string $column ): int { $column = (string) preg_replace( '/[^A-Za-z]/', '', $column ); $column = strtoupper( $column ); $count = strlen( $column ); $number = 0; for ( $i = 0; $i < $count; $i++ ) { $number += ( ord( $column[ $count - 1 - $i ] ) - 64 ) * 26 ** $i; } return $number; } /** * "Calculate" the column header string of a column index (example: 2 is B, AB is 28, ...). * * For the opposite, @see letter_to_number(). * * @since 1.0.0 * * @param int $number Column number, 1-based. * @return string Column string. */ public static function number_to_letter( int $number ): string { $column = ''; while ( $number > 0 ) { $column = chr( 65 + ( ( $number - 1 ) % 26 ) ) . $column; $number = intdiv( $number - 1, 26 ); } return $column; } /** * Get a nice looking date and time string from the mySQL format of datetime strings for output. * * @since 1.0.0 * * @param string $datetime_string DateTime string, often in mySQL format.. * @param string $separator_or_format Optional. Separator between date and time, or format string. * @return string Nice looking string with the date and time. */ public static function format_datetime( string $datetime_string, string $separator_or_format = ' ' ): string { $timezone = wp_timezone(); $datetime = date_create( $datetime_string, $timezone ); if ( false === $datetime ) { return $datetime_string; } $timestamp = $datetime->getTimestamp(); switch ( $separator_or_format ) { case ' ': case '
': case '
': case '
': $date = wp_date( get_option( 'date_format' ), $timestamp, $timezone ); $time = wp_date( get_option( 'time_format' ), $timestamp, $timezone ); $output = "{$date}{$separator_or_format}{$time}"; break; default: $output = (string) wp_date( $separator_or_format, $timestamp, $timezone ); break; } return $output; } /** * Get the name from a WP user ID (used to store information on last editor of a table). * * @since 1.0.0 * * @param int $user_id WP user ID. * @return string Nickname of the WP user with the $user_id. */ public static function get_user_display_name( int $user_id ): string { $user = get_userdata( $user_id ); return $user->display_name ?? sprintf( '%s', __( 'unknown', 'tablepress' ) ); } /** * Sanitizes a CSS class to ensure it only contains valid characters. * * Strips the string down to A-Z, a-z, 0-9, :, _, -. * This is an extension to WP's `sanitize_html_class()`, to also allow `:` which are used in some CSS frameworks. * * @since 1.11.0 * * @param string $css_class The CSS class name to be sanitized. * @return string The sanitized CSS class. */ public static function sanitize_css_class( string $css_class ): string { // Strip out any %-encoded octets. $sanitized_css_class = (string) preg_replace( '|%[a-fA-F0-9][a-fA-F0-9]|', '', $css_class ); // Limit to A-Z, a-z, 0-9, ':', '_', and '-'. $sanitized_css_class = (string) preg_replace( '/[^A-Za-z0-9:_-]/', '', $sanitized_css_class ); return $sanitized_css_class; } /** * Extracts the top-level keys from a JavaScript object string. * * This function is used to extract the keys of the "Custom Commands" JavaScript object string, to check for overrides. * It covers most cases, like normal object properties with and without quotes, shorthand properties, and shorthand methods, * and also ignores single-line and multi-line comments. * It does not cover all possible JavaScript syntax (like template literals, special characters, ...), * but should be sufficient for the use case. * * @since 3.0.0 * * @param string $js_object_string A JavaScript object as a string. * @return string[] Array of top-level keys of the object. */ public static function extract_keys_from_js_object_string( string $js_object_string ): array { $object_keys = array(); $length = strlen( $js_object_string ); $depth = 0; $key_expected = true; $in_quotes = false; $quote_char = ''; $in_function_declaration = false; $in_single_line_comment = false; $in_multi_line_comment = false; $object_key = ''; for ( $i = 0; $i < $length; $i++ ) { $char = $js_object_string[ $i ]; // Skip parsing single-line comments. if ( $in_single_line_comment ) { if ( "\n" === $char ) { $in_single_line_comment = false; } continue; } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found if ( '/' === $char && $i + 1 < $length && '/' === $js_object_string[ $i + 1 ] ) { $in_single_line_comment = true; ++$i; // Skip the second '/'. continue; } } // Skip parsing multi-line comments. if ( $in_multi_line_comment ) { if ( '*' === $char && $i + 1 < $length && '/' === $js_object_string[ $i + 1 ] ) { $in_multi_line_comment = false; ++$i; // Skip the '/' that ends the multi-line comment. } continue; } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found if ( '/' === $char && $i + 1 < $length && '*' === $js_object_string[ $i + 1 ] ) { $in_multi_line_comment = true; ++$i; // Skip the '*'. continue; } } // Skip parsing while inside a quoted string. if ( $in_quotes ) { if ( $quote_char === $char ) { $in_quotes = false; } continue; } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found if ( '"' === $char || "'" === $char ) { $in_quotes = true; $quote_char = $char; continue; } } /* * Skip parsing while inside a `function abc( ... )` declaration string. * The `$key_expected` check limits search the "function" string to object values. * The check for the plain `f` reduces expensive `substr()` calls. */ if ( ! $key_expected ) { if ( $in_function_declaration ) { if ( ')' === $char ) { $in_function_declaration = false; } continue; } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found if ( 'f' === $char && 'function' === substr( $js_object_string, $i, 8 ) ) { $in_function_declaration = true; $i += 7; // Skip the rest of the "function" string. continue; } } } // Handle object depth, so that most parsing can be limited to the top level. if ( '{' === $char || '[' === $char ) { ++$depth; } // Extract only keys at the top level. if ( 1 === $depth ) { if ( $key_expected ) { if ( ':' === $char ) { // Check for normal keys, with value after :. // Go backwards to find the start of the key. $j = $i - 1; while ( $j >= 0 && preg_match( '/\s/', $js_object_string[ $j ] ) ) { --$j; } $key_end = $j; // Position of the last character of the key (potentially with quote). if ( '"' === $js_object_string[ $j ] || "'" === $js_object_string[ $j ] ) { // Quoted key. $quote_char = $js_object_string[ $j ]; --$j; while ( $j >= 0 && $quote_char !== $js_object_string[ $j ] ) { --$j; } $key_start = $j + 1; } else { // Unquoted key. while ( $j >= 0 && preg_match( '/[\w]/', $js_object_string[ $j ] ) ) { --$j; } $key_start = $j + 1; } $object_key = substr( $js_object_string, $key_start, $key_end - $key_start + 1 ); $object_key = trim( $object_key, "\"'" ); if ( '' !== $object_key && ! in_array( $object_key, $object_keys, true ) ) { $object_keys[] = $object_key; } $key_expected = false; } elseif ( ( ',' === $char || '}' === $char ) ) { // The `}` case is for the last key. // Check for shorthand properties (which must be unquoted). // Go backwards to find the start of the shorthand key. $j = $i - 1; while ( $j >= 0 && preg_match( '/\s/', $js_object_string[ $j ] ) ) { --$j; } $key_end = $j; // Position of the last character of the key (without a quote). while ( $j >= 0 && preg_match( '/[\w]/', $js_object_string[ $j ] ) ) { --$j; } $key_start = $j + 1; $object_key = substr( $js_object_string, $key_start, $key_end - $key_start + 1 ); if ( '' !== $object_key && ! in_array( $object_key, $object_keys, true ) ) { $object_keys[] = $object_key; } } elseif ( '(' === $char ) { // Detect shorthand method definitions. // Go back to find the start of the method name. $j = $i - 1; while ( $j >= 0 && preg_match( '/\s/', $js_object_string[ $j ] ) ) { --$j; } $key_end = $j; while ( $j >= 0 && preg_match( '/[\w]/', $js_object_string[ $j ] ) ) { --$j; } $key_start = $j + 1; $object_key = substr( $js_object_string, $key_start, $key_end - $key_start + 1 ); if ( '' !== $object_key && ! in_array( $object_key, $object_keys, true ) ) { $object_keys[] = $object_key; } } } // Reset the "key expected" flag after a comma or closing brace. if ( ',' === $char || '}' === $char ) { $key_expected = true; } } // Handle object depth. if ( '}' === $char || ']' === $char ) { --$depth; } } return $object_keys; } /** * Converts old DataTables 1.x CSS classes and parameters to the DataTables 2 variants. * * This function is used to modernize "Custom CSS" and "Custom Commands" for compatibility with DataTables 2.x. * It probably does not catch all possible cases. * * @since 3.0.0 * * @param string $code Code that contains DataTables 1.x CSS classes and parameters. * @return string Updated code with DataTables 2.x CSS classes and parameters. */ public static function convert_datatables_api_data( string $code ): string { /** * Mappings for DataTables 1.x CSS class or parameter to DataTables 2 variants. * As this array is used in `strtr()`, it's pre-sorted for descending string length of the array keys. */ static $datatables_api_data_mappings = array( // CSS classes. '.tablepress thead .sorting:hover' => '.tablepress thead .dt-orderable-asc:hover,.tablepress thead .dt-orderable-desc:hover', '.tablepress thead .sorting_desc' => '.tablepress thead .dt-ordering-desc', '.dataTables_filter label input' => '.dt-container .dt-search input', '.tablepress thead .sorting_asc' => '.tablepress thead .dt-ordering-asc', '.dataTables_scrollFootInner' => '.dt-scroll-footInner', '.dataTables_scrollHeadInner' => '.dt-scroll-headInner', '.tablepress thead .sorting' => '.tablepress thead .dt-orderable-asc,.tablepress thead .dt-orderable-desc', '.dataTables_processing' => '.dt-processing', '.dataTables_scrollBody' => '.dt-scroll-body', '.dataTables_scrollFoot' => '.dt-scroll-foot', '.dataTables_scrollHead' => '.dt-scroll-head', '.dataTables_paginate' => '.dt-paging', '.tablepress .even td' => '.tablepress>:where(tbody.row-striping)>:nth-child(odd)>*', '.dataTables_wrapper' => '.dt-container', '.tablepress .odd td' => '.tablepress>:where(tbody.row-striping)>:nth-child(even)>*', '.dataTables_filter' => '.dt-search', '.dataTables_length' => '.dt-length', '.dataTables_scroll' => '.dt-scroll', '.dataTables_empty' => '.dt-empty', '.dataTables_info' => '.dt-info', '.paginate_button' => '.dt-paging-button', // DataTables API functions. '$.fn.dataTable.' => 'DataTable.', ); $code = strtr( $code, $datatables_api_data_mappings ); // HTML ID mappings, which were removed. if ( str_contains( $code, '#tablepress-' ) ) { $code = (string) preg_replace( array( '/#tablepress-([A-Za-z1-9_-]|[A-Za-z0-9_-]{2,})_paginate/', '/#tablepress-([A-Za-z1-9_-]|[A-Za-z0-9_-]{2,})_filter/', '/#tablepress-([A-Za-z1-9_-]|[A-Za-z0-9_-]{2,})_length/', '/#tablepress-([A-Za-z1-9_-]|[A-Za-z0-9_-]{2,})_info/', ), array( '#tablepress-$1_wrapper .dt-paging', '#tablepress-$1_wrapper .dt-search', '#tablepress-$1_wrapper .dt-length', '#tablepress-$1_wrapper .dt-info', ), $code, ); } return $code; } /** * Retrieves all information of a WP_Error object as a string. * * @since 1.4.0 * * @param WP_Error $wp_error A WP_Error object. * @return string All error codes, messages, and data of the WP_Error. */ public static function get_wp_error_string( WP_Error $wp_error ): string { $error_strings = array(); $error_codes = $wp_error->get_error_codes(); // Reverse order to get latest errors first. $error_codes = array_reverse( $error_codes ); foreach ( $error_codes as $error_code ) { $error_strings[ $error_code ] = $error_code; $error_messages = $wp_error->get_error_messages( $error_code ); $error_messages = implode( ', ', $error_messages ); if ( ! empty( $error_messages ) ) { $error_strings[ $error_code ] .= " ({$error_messages})"; } $error_data = $wp_error->get_error_data( $error_code ); if ( is_string( $error_data ) ) { $error_strings[ $error_code ] .= " [{$error_data}]"; } elseif ( is_array( $error_data ) ) { foreach ( $error_data as $key => $value ) { $error_data[ $key ] = "{$key}: {$value}"; } $error_data = implode( ', ', $error_data ); $error_strings[ $error_code ] .= " [{$error_data}]"; } } return implode( ";\n", $error_strings ); } /** * Generate the action URL, to be used as a link within the plugin (e.g. in the submenu navigation or List of Tables). * * @since 1.0.0 * * @param array $params Optional. Parameters to form the query string of the URL. * @param bool $add_nonce Optional. Whether the URL shall be nonced by WordPress. * @param string $target Optional. Target File, e.g. "admin-post.php" for POST requests. * @return string The URL for the given parameters (already run through esc_url() with $add_nonce === true!). */ public static function url( array $params = array(), bool $add_nonce = false, string $target = '' ): string { // Default action is "list", if no action given. if ( ! isset( $params['action'] ) ) { $params['action'] = 'list'; } $nonce_action = $params['action']; if ( '' !== $target ) { $params['action'] = "tablepress_{$params['action']}"; } else { $params['page'] = 'tablepress'; // Top-level parent page needs special treatment for better action strings. if ( self::$controller->is_top_level_page ) { $target = 'admin.php'; if ( ! in_array( $params['action'], array( 'list', 'edit' ), true ) ) { $params['page'] = "tablepress_{$params['action']}"; } if ( ! in_array( $params['action'], array( 'edit' ), true ) ) { $params['action'] = false; } } else { $target = self::$controller->parent_page; } } // $default_params also determines the order of the values in the query string. $default_params = array( 'page' => false, 'action' => false, 'item' => false, ); $params = array_merge( $default_params, $params ); $url = add_query_arg( $params, admin_url( $target ) ); if ( $add_nonce ) { $url = wp_nonce_url( $url, self::nonce( $nonce_action, $params['item'] ) ); // wp_nonce_url() does esc_html(). } return $url; } /** * Create a redirect URL from the $target_parameters and redirect the user. * * @since 1.0.0 * * @param array $params Optional. Parameters from which the target URL is constructed. * @param bool $add_nonce Optional. Whether the URL shall be nonced by WordPress. */ public static function redirect( array $params = array(), bool $add_nonce = false ): void { $redirect = self::url( $params ); if ( $add_nonce ) { if ( ! isset( $params['item'] ) ) { $params['item'] = false; } // Don't use wp_nonce_url(), as that uses esc_html(). $redirect = add_query_arg( '_wpnonce', wp_create_nonce( self::nonce( $params['action'], $params['item'] ) ), $redirect ); } wp_redirect( $redirect ); exit; } /** * Determines the editor that the site uses, so that certain text and input fields referring to Shortcodes can be displayed or not. * * @since 3.1.0 * * @return string The editor that the site uses, either "block", "elementor", or "other". */ public static function site_used_editor(): string { if ( is_plugin_active( 'elementor/elementor.php' ) ) { return 'elementor'; } // Checking for Elementor is not needed anymore in this condition. $site_uses_block_editor = use_block_editor_for_post_type( 'post' ) && ! is_plugin_active( 'classic-editor/classic-editor.php' ) && ! is_plugin_active( 'classic-editor-addon/classic-editor-addon.php' ) && ! is_plugin_active( 'siteorigin-panels/siteorigin-panels.php' ) && ! is_plugin_active( 'beaver-builder-lite-version/fl-builder.php' ); /** * Filters the outcome of the check whether the site uses the block editor. * * This can be used when certain conditions (e.g. new site builders) are not (yet) accounted for. * * @since 2.0.1 * * @param bool $site_uses_block_editor True if the site uses the block editor, false otherwise. */ $site_uses_block_editor = (bool) apply_filters( 'tablepress_site_uses_block_editor', $site_uses_block_editor ); if ( $site_uses_block_editor ) { return 'block'; } return 'other'; } /** * Initializes the list of TablePress premium modules. * * @since 2.1.0 */ public static function init_modules(): void { if ( ! empty( self::$modules ) ) { return; } self::$modules = array( 'advanced-access-rights' => array( 'name' => __( 'Advanced Access Rights', 'tablepress' ), 'description' => __( 'Restrict access to individual tables for individual users.', 'tablepress' ), 'category' => 'backend', 'class' => 'TablePress_Module_Advanced_Access_Rights', 'incompatible_classes' => array( 'TablePress_Advanced_Access_Rights_Controller' ), 'minimum_plan' => 'max', 'default_active' => false, ), 'automatic-periodic-table-import' => array( 'name' => __( 'Automatic Periodic Table Import', 'tablepress' ), 'description' => __( 'Periodically update tables from a configured import source.', 'tablepress' ), 'category' => 'backend', 'class' => 'TablePress_Module_Automatic_Periodic_Table_Import', 'incompatible_classes' => array( 'TablePress_Table_Auto_Update' ), 'minimum_plan' => 'max', 'default_active' => true, ), 'automatic-table-export' => array( 'name' => __( 'Automatic Table Export', 'tablepress' ), 'description' => __( 'Export and save tables to files on the server after they were modified.', 'tablepress' ), 'category' => 'backend', 'class' => 'TablePress_Module_Automatic_Table_Export', 'incompatible_classes' => array(), 'minimum_plan' => 'pro', 'default_active' => false, ), 'cell-highlighting' => array( 'name' => __( 'Cell Highlighting', 'tablepress' ), 'description' => __( 'Add CSS classes to cells for highlighting based on their content.', 'tablepress' ), 'category' => 'frontend', 'class' => 'TablePress_Module_Cell_Highlighting', 'incompatible_classes' => array( 'TablePress_Cell_Highlighting' ), 'minimum_plan' => 'pro', 'default_active' => false, ), 'column-order' => array( 'name' => __( 'Column Order', 'tablepress' ), 'description' => __( 'Order the columns in different ways when a table is shown.', 'tablepress' ), 'category' => 'data-management', 'class' => 'TablePress_Module_Column_Order', 'incompatible_classes' => array( 'TablePress_Column_Order' ), 'minimum_plan' => 'pro', 'default_active' => false, ), 'datatables-advanced-loading' => array( 'name' => __( 'Advanced Loading', 'tablepress' ), 'description' => __( 'Load the table data from a JSON array for faster loading.', 'tablepress' ), 'category' => 'backend', 'class' => 'TablePress_Module_DataTables_Advanced_Loading', 'incompatible_classes' => array( 'TablePress_DataTables_Advanced_Loading' ), 'minimum_plan' => 'max', 'default_active' => false, ), 'datatables-alphabetsearch' => array( 'name' => __( 'Alphabet Search', 'tablepress' ), 'description' => __( 'Show Alphabet buttons above the table to filter rows by their first letter.', 'tablepress' ), 'category' => 'search-filter', 'class' => 'TablePress_Module_DataTables_Alphabetsearch', 'incompatible_classes' => array(), 'minimum_plan' => 'pro', 'default_active' => false, ), 'datatables-auto-filter' => array( 'name' => __( 'Automatic Filter', 'tablepress' ), 'description' => __( 'Pre-filter a table when it is shown.', 'tablepress' ), 'category' => 'search-filter', 'class' => 'TablePress_Module_DataTables_Auto_Filter', 'incompatible_classes' => array( 'TablePress_DataTables_Auto_Filter' ), 'minimum_plan' => 'pro', 'default_active' => false, ), 'datatables-buttons' => array( 'name' => __( 'User Action Buttons', 'tablepress' ), 'description' => __( 'Add buttons for downloading, copying, printing, and changing column visibility of tables.', 'tablepress' ), 'category' => 'frontend', 'class' => 'TablePress_Module_DataTables_Buttons', 'incompatible_classes' => array( 'TablePress_DataTables_Buttons' ), 'minimum_plan' => 'pro', 'default_active' => true, ), 'datatables-columnfilterwidgets' => array( 'name' => __( 'Column Filter Dropdowns', 'tablepress' ), 'description' => __( 'Add a search dropdown for each column above the table.', 'tablepress' ), 'category' => 'search-filter', 'class' => 'TablePress_Module_DataTables_ColumnFilterWidgets', 'incompatible_classes' => array(), 'minimum_plan' => 'pro', 'default_active' => true, ), 'datatables-column-filter' => array( 'name' => __( 'Individual Column Filtering', 'tablepress' ), 'description' => __( 'Add a search field for each column to the table head or foot row.', 'tablepress' ), 'category' => 'search-filter', 'class' => 'TablePress_Module_DataTables_Column_Filter', 'incompatible_classes' => array(), 'minimum_plan' => 'pro', 'default_active' => false, ), 'datatables-counter-column' => array( 'name' => __( 'Index Column', 'tablepress' ), 'description' => __( 'Make the first column an index or counter column with the row position.', 'tablepress' ), 'category' => 'frontend', 'class' => 'TablePress_Module_DataTables_Counter_Column', 'incompatible_classes' => array(), 'minimum_plan' => 'pro', 'default_active' => false, ), 'datatables-fixedheader-fixedcolumns' => array( 'name' => __( 'Fixed Rows and Columns', 'tablepress' ), 'description' => __( 'Fix the header and footer row and the first and last column when scrolling the table.', 'tablepress' ), 'category' => 'frontend', 'class' => 'TablePress_Module_DataTables_FixedHeader_FixedColumns', 'incompatible_classes' => array( 'TablePress_DataTables_FixedHeader', 'TablePress_DataTables_FixedColumns', ), 'minimum_plan' => 'pro', 'default_active' => true, ), 'datatables-layout' => array( 'name' => __( 'Table Layout', 'tablepress' ), 'description' => __( 'Customize the layout and position of features around a table.', 'tablepress' ), 'category' => 'frontend', 'class' => 'TablePress_Module_DataTables_Layout', 'incompatible_classes' => array(), 'minimum_plan' => 'pro', 'default_active' => true, ), 'datatables-fuzzysearch' => array( 'name' => __( 'Fuzzy Search', 'tablepress' ), 'description' => __( 'Let the search account for spelling mistakes and typos and find similar matches.', 'tablepress' ), 'category' => 'search-filter', 'class' => 'TablePress_Module_DataTables_FuzzySearch', 'incompatible_classes' => array(), 'minimum_plan' => 'max', 'default_active' => false, ), 'datatables-inverted-filter' => array( 'name' => __( 'Inverted Filtering', 'tablepress' ), 'description' => __( 'Turn the filtering into a search and hide the table if no search term is entered.', 'tablepress' ), 'category' => 'search-filter', 'class' => 'TablePress_Module_DataTables_Inverted_Filter', 'incompatible_classes' => array(), 'minimum_plan' => 'max', 'default_active' => false, ), 'datatables-pagination' => array( 'name' => __( 'Advanced Pagination Settings', 'tablepress' ), 'description' => __( 'Customize the pagination settings of the table.', 'tablepress' ), 'category' => 'frontend', 'class' => 'TablePress_Module_DataTables_Pagination', 'incompatible_classes' => array(), 'minimum_plan' => 'pro', 'default_active' => false, ), 'datatables-rowgroup' => array( 'name' => __( 'Row Grouping', 'tablepress' ), 'description' => __( 'Group table rows by a common keyword, category, or title.', 'tablepress' ), 'category' => 'frontend', 'class' => 'TablePress_Module_DataTables_RowGroup', 'incompatible_classes' => array( 'TablePress_DataTables_RowGroup' ), 'minimum_plan' => 'pro', 'default_active' => false, ), 'datatables-searchbuilder' => array( 'name' => __( 'Custom Search Builder', 'tablepress' ), 'description' => __( 'Show a search builder interface for filtering from groups and using conditions.', 'tablepress' ), 'category' => 'search-filter', 'class' => 'TablePress_Module_DataTables_SearchBuilder', 'incompatible_classes' => array(), 'minimum_plan' => 'max', 'default_active' => false, ), 'datatables-searchhighlight' => array( 'name' => __( 'Search Highlighting', 'tablepress' ), 'description' => __( 'Highlight found search terms in the table.', 'tablepress' ), 'category' => 'search-filter', 'class' => 'TablePress_Module_DataTables_SearchHighlight', 'incompatible_classes' => array(), 'minimum_plan' => 'pro', 'default_active' => false, ), 'datatables-searchpanes' => array( 'name' => __( 'Search Panes', 'tablepress' ), 'description' => __( 'Show panes for filtering the columns.', 'tablepress' ), 'category' => 'search-filter', 'class' => 'TablePress_Module_DataTables_SearchPanes', 'incompatible_classes' => array(), 'minimum_plan' => 'pro', 'default_active' => false, ), 'datatables-serverside-processing' => array( 'name' => __( 'Server-side Processing', 'tablepress' ), 'description' => __( 'Process sorting, filtering, and pagination on the server for faster loading of large tables.', 'tablepress' ), 'category' => 'backend', 'class' => 'TablePress_Module_DataTables_ServerSide_Processing', 'incompatible_classes' => array(), 'minimum_plan' => 'max', 'default_active' => true, ), 'default-style-customizer' => array( 'name' => __( 'Default Style Customizer', 'tablepress' ), 'description' => __( 'Change the default styling of your tables in the visual style customizer.', 'tablepress' ), 'category' => 'frontend', 'class' => 'TablePress_Module_Default_Style_Customizer', 'incompatible_classes' => array(), 'minimum_plan' => 'pro', 'default_active' => true, ), 'email-notifications' => array( 'name' => __( 'Email Notifications', 'tablepress' ), 'description' => __( 'Get email notifications when certain actions are performed on tables.', 'tablepress' ), 'category' => 'backend', 'class' => 'TablePress_Module_Email_Notifications', 'incompatible_classes' => array(), 'minimum_plan' => 'max', 'default_active' => false, ), 'responsive-tables' => array( 'name' => __( 'Responsive Tables', 'tablepress' ), 'description' => __( 'Make your tables look good on different screen sizes.', 'tablepress' ), 'category' => 'frontend', 'class' => 'TablePress_Module_Responsive_Tables', 'incompatible_classes' => array( 'TablePress_Responsive_Tables' ), 'minimum_plan' => 'pro', 'default_active' => true, ), 'rest-api' => array( 'name' => __( 'REST API', 'tablepress' ), 'description' => __( 'Read table data via the WordPress REST API, e.g. in external apps.', 'tablepress' ), 'category' => 'backend', 'class' => 'TablePress_Module_REST_API', 'incompatible_classes' => array( 'TablePress_REST_API_Controller' ), 'minimum_plan' => 'max', 'default_active' => false, ), 'row-filtering' => array( 'name' => __( 'Row Filtering', 'tablepress' ), 'description' => __( 'Show only table rows that contain defined keywords.', 'tablepress' ), 'category' => 'data-management', 'class' => 'TablePress_Module_Row_Filtering', 'incompatible_classes' => array( 'TablePress_Row_Filter' ), 'minimum_plan' => 'pro', 'default_active' => true, ), 'row-highlighting' => array( 'name' => __( 'Row Highlighting', 'tablepress' ), 'description' => __( 'Add CSS classes to rows for highlighting based on their content.', 'tablepress' ), 'category' => 'frontend', 'class' => 'TablePress_Module_Row_Highlighting', 'incompatible_classes' => array( 'TablePress_Row_Highlighting' ), 'minimum_plan' => 'pro', 'default_active' => false, ), 'row-order' => array( 'name' => __( 'Row Order', 'tablepress' ), 'description' => __( 'Order the rows in different ways when a table is shown.', 'tablepress' ), 'category' => 'data-management', 'class' => 'TablePress_Module_Row_Order', 'incompatible_classes' => array( 'TablePress_Row_Order' ), 'minimum_plan' => 'pro', 'default_active' => false, ), ); } } // class TablePress PK!~bRbRclasses/class-view.phpnu[ */ protected array $data = array(); /** * Number of screen columns for post boxes. * * @since 1.0.0 */ protected int $screen_columns = 0; /** * User action for this screen. * * @since 1.0.0 */ protected string $action = ''; /** * Instance of the Admin Page Helper Class, with necessary functions. * * @since 1.0.0 */ protected \TablePress_Admin_Page $admin_page; /** * List of text boxes (similar to post boxes, but just with text and without extra functionality). * * @since 1.0.0 * @var array>> */ protected array $textboxes = array(); /** * List of messages that are to be displayed as boxes below the page title. * * @since 1.0.0 * @var string[] */ protected array $header_messages = array(); /** * Whether there are post boxes registered for this screen, * is automatically set to true, when a meta box is added. * * @since 1.0.0 */ protected bool $has_meta_boxes = false; /** * List of WP feature pointers for this view. * * @since 1.0.0 * @var string[] */ protected array $wp_pointers = array(); /** * Initializes the View class, by setting the correct screen columns and adding help texts. * * @since 1.0.0 */ public function __construct() { $screen = get_current_screen(); if ( 0 !== $this->screen_columns ) { $screen->add_option( 'layout_columns', array( 'max' => $this->screen_columns ) ); // @phpstan-ignore method.nonObject } // Enable two column layout. add_filter( "get_user_option_screen_layout_{$screen->id}", array( $this, 'set_current_screen_layout_columns' ) ); // @phpstan-ignore property.nonObject $common_content = '

' . sprintf( __( 'More information about TablePress can be found on the plugin website or on its page in the WordPress Plugin Directory.', 'tablepress' ), 'https://tablepress.org/', 'https://wordpress.org/plugins/tablepress/' ) . '

'; $common_content .= '

' . sprintf( __( 'For technical information, please see the Documentation.', 'tablepress' ), 'https://tablepress.org/documentation/' ) . ' ' . sprintf( __( 'Common questions are answered in the FAQ.', 'tablepress' ), 'https://tablepress.org/faq/' ) . '

'; if ( tb_tp_fs()->is_free_plan() ) { $common_content .= '

' . sprintf( __( 'Support is provided through the WordPress Support Forums.', 'tablepress' ), 'https://tablepress.org/support/', 'https://wordpress.org/tags/tablepress' ) . ' ' . sprintf( __( 'Before asking for support, please carefully read the Frequently Asked Questions, where you will find answers to the most common questions, and search through the forums.', 'tablepress' ), 'https://tablepress.org/faq/' ) . '

'; $common_content .= '

' . sprintf( __( 'More great features for you and your site’s visitors and priority email support are available with a Premium license plan of TablePress. Go check them out!', 'tablepress' ), 'https://tablepress.org/premium/?utm_source=plugin&utm_medium=textlink&utm_content=help-tab' ) . '

'; } $screen->add_help_tab( array( // @phpstan-ignore method.nonObject 'id' => 'tablepress-help', // This should be unique for the screen. 'title' => __( 'TablePress Help', 'tablepress' ), 'content' => '

' . $this->help_tab_content() . '

' . $common_content, ) ); // "Sidebar" in the help tab. $screen->set_help_sidebar( // @phpstan-ignore method.nonObject '

' . __( 'For more information:', 'tablepress' ) . '

' . '

TablePress Website

' . '

TablePress FAQ

' . '

TablePress Documentation

' . '

TablePress Support

' ); } /** * Changes the value of the user option "screen_layout_{$screen->id}" through a filter. * * @since 1.0.0 * * @param int|false $result Current value of the user option. * @return int New value for the user option. */ public function set_current_screen_layout_columns( /* int|false */ $result ): int { if ( false === $result ) { // The user option does not yet exist. $result = $this->screen_columns; } elseif ( $result > $this->screen_columns ) { // The value of the user option is bigger than what is possible on this screen (e.g. because the number of columns was reduced in an update). $result = $this->screen_columns; } return $result; } /** * Sets up the view with data and do things that are necessary for all views. * * @since 1.0.0 * * @param string $action Action for this view. * @param array $data Data for this view. */ public function setup( /* string */ $action, array $data ) /* : void */ { // Don't use type hints (except array $data) in method declaration, as the method is extended in some TablePress Extensions which are no longer updated. $this->action = $action; $this->data = $data; // Set page title. $GLOBALS['title'] = sprintf( __( '%1$s ‹ %2$s', 'tablepress' ), $this->data['view_actions'][ $this->action ]['page_title'], 'TablePress' ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited // Admin page helpers, like script/style loading, could be moved to view. $this->admin_page = TablePress::load_class( 'TablePress_Admin_Page', 'class-admin-page-helper.php', 'classes' ); $this->admin_page->enqueue_style( 'common', array( 'wp-components' ) ); // RTL styles for the admin interface. if ( is_rtl() ) { $this->admin_page->enqueue_style( 'common-rtl', array( 'tablepress-common' ) ); } $this->admin_page->enqueue_script( 'common', array( 'jquery-core', 'postbox' ) ); $this->admin_page->add_admin_footer_text(); // Initialize WP feature pointers for TablePress. $this->_init_wp_pointers(); // Necessary fields for all views. $this->add_text_box( 'default_nonce_fields', array( $this, 'default_nonce_fields' ), 'header', false ); $this->add_text_box( 'action_nonce_field', array( $this, 'action_nonce_field' ), 'header', false ); $this->add_text_box( 'action_field', array( $this, 'action_field' ), 'header', false ); } /** * Registers a header message for the view. * * @since 1.0.0 * * @param string $text Text for the header message. * @param string $css_class Optional. Additional CSS class for the header message. * @param string $title Optional. Text for the header title. */ protected function add_header_message( string $text, string $css_class = 'is-success', string $title = '' ): void { if ( ! str_contains( $css_class, 'not-dismissible' ) ) { $css_class .= ' is-dismissible'; } if ( '' !== $title ) { $title = "

{$title}

"; } // Wrap the message text in HTML

tags if it does not already start with one (potentially with attributes), indicating custom message HTML. if ( '' !== $text && ! str_starts_with( $text, 'header_messages[] = "

{$title}{$text}
\n"; } /** * Processes header action messages, i.e. check if a message should be added to the page. * * @since 1.0.0 * * @param array $action_messages Action messages for the screen. */ protected function process_action_messages( array $action_messages ): void { if ( $this->data['message'] && isset( $action_messages[ $this->data['message'] ] ) ) { $class = ( str_starts_with( $this->data['message'], 'error' ) ) ? 'is-error' : 'is-success'; if ( '' !== $this->data['error_details'] ) { $this->data['error_details'] = '

' . sprintf( __( 'Error code: %s', 'tablepress' ), '' . esc_html( $this->data['error_details'] ) . '' ); } $this->add_header_message( "{$action_messages[ $this->data['message'] ]}{$this->data['error_details']}", $class ); } } /** * Registers a text box for the view. * * @since 1.0.0 * * @param string $id Unique HTML ID for the text box container (only visible with $wrap = true). * @param callable $callback Callback that prints the contents of the text box. * @param string $context Optional. Context/position of the text box (normal, side, additional, header, submit). * @param bool $wrap Whether the content of the text box shall be wrapped in a

container. */ protected function add_text_box( string $id, callable $callback, string $context = 'normal', bool $wrap = false ): void { if ( ! isset( $this->textboxes[ $context ] ) ) { $this->textboxes[ $context ] = array(); } $long_id = "tablepress_{$this->action}-{$id}"; $this->textboxes[ $context ][ $id ] = array( 'id' => $long_id, 'callback' => $callback, 'context' => $context, 'wrap' => $wrap, ); } /** * Registers a post meta box for the view, that is drag/droppable with WordPress functionality. * * @since 1.0.0 * * @param string $id Unique ID for the meta box. * @param string $title Title for the meta box. * @param callable $callback Callback that prints the contents of the post meta box. * @param 'normal'|'side'|'additional' $context Optional. Context/position of the post meta box (normal, side, additional). * @param 'core'|'default'|'high'|'low' $priority Optional. Order of the post meta box for the $context position (high, default, low). * @param mixed[]|null $callback_args Optional. Additional data for the callback function (e.g. useful when in different class). */ protected function add_meta_box( string $id, string $title, callable $callback, string $context = 'normal', string $priority = 'default', ?array $callback_args = null ): void { $this->has_meta_boxes = true; add_meta_box( "tablepress_{$this->action}-{$id}", $title, $callback, null, $context, $priority, $callback_args ); } /** * Renders all text boxes for the given context. * * @since 1.0.0 * * @param string $context Context (normal, side, additional, header, submit) for which registered text boxes shall be rendered. */ protected function do_text_boxes( string $context ): void { if ( empty( $this->textboxes[ $context ] ) ) { return; } foreach ( $this->textboxes[ $context ] as $box ) { if ( $box['wrap'] ) { echo "
\n"; } call_user_func( $box['callback'], $this->data, $box ); if ( $box['wrap'] ) { echo "
\n"; } } } /** * Renders all post meta boxes for the given context, if there are post meta boxes. * * @since 1.0.0 * * @param string $context Context (normal, side, additional) for which registered post meta boxes shall be rendered. */ protected function do_meta_boxes( string $context ): void { if ( $this->has_meta_boxes ) { do_meta_boxes( get_current_screen(), $context, $this->data ); // @phpstan-ignore argument.type } } /** * Prints hidden fields with nonces for post meta box AJAX handling, if there are post meta boxes on the screen. * * The check is possible as this function is executed after post meta boxes have to be registered. * * @since 1.0.0 * * @param array $data Data for this screen. * @param array $box Information about the text box. */ protected function default_nonce_fields( array $data, array $box ): void { if ( ! $this->has_meta_boxes ) { return; } wp_nonce_field( 'closedpostboxes', 'closedpostboxesnonce', false ); echo "\n"; wp_nonce_field( 'meta-box-order', 'meta-box-order-nonce', false ); echo "\n"; } /** * Prints hidden field with a nonce for the screen's action, to be transmitted in HTTP requests. * * @since 1.0.0 * * @param array $data Data for this screen. * @param array $box Information about the text box. */ protected function action_nonce_field( array $data, array $box ): void { wp_nonce_field( TablePress::nonce( $this->action ) ); echo "\n"; } /** * Prints hidden field with the screen action. * * @since 1.0.0 * * @param array $data Data for this screen. * @param array $box Information about the text box. */ protected function action_field( array $data, array $box ): void { echo "action}\">\n"; } /** * Renders the current view. * * @since 1.0.0 */ public function render(): void { ?>
print_nav_tab_menu(); ?>

header_messages as $message ) { echo $message; } // "Import" screen has file upload. $enctype = ( 'import' === $this->action ) ? ' enctype="multipart/form-data"' : ''; ?>
id="tablepress-page-form"> do_text_boxes( 'header' ); $hide_if_no_js = ( in_array( $this->action, array( 'export', 'import' ), true ) ) ? ' class="hide-if-no-js"' : ''; ?>
>
do_text_boxes( 'normal' ); $this->do_meta_boxes( 'normal' ); $this->do_text_boxes( 'additional' ); $this->do_meta_boxes( 'additional' ); // Print all submit buttons. $this->do_text_boxes( 'submit' ); ?>
do_text_boxes( 'side' ); $this->do_meta_boxes( 'side' ); ?>

<?php esc_attr_e( 'TablePress plugin logo', 'tablepress' ); ?>

is_free_plan() ) : ?>
$data Data for this screen. * @param array $box Information about the text box. */ public function textbox_no_javascript( array $data, array $box ): void { ?>


the instructions on how to enable JavaScript in your browser.', 'tablepress' ); ?>

'list' ) ) ) . '">' . __( 'Back to the List of Tables', 'tablepress' ) . ''; ?>

$data Data for this screen. * @param array $box Information about the text box. */ protected function textbox_submit_button( array $data, array $box ): void { ?>

$data Information about the text box. */ protected function print_script_data_json( string $variable, array $data ): void { echo "\n"; } /** * Returns the content for the help tab for this screen. * * Has to be implemented for every view that is visible in the WP Dashboard! * * @since 1.0.0 * * @return string Help tab content for the view. */ protected function help_tab_content(): string { // Has to be implemented for every view that is visible in the WP Dashboard! return ''; } /** * Initializes the WP feature pointers for TablePress. * * @since 1.0.0 */ protected function _init_wp_pointers(): void { // Check if there are WP pointers for this view. if ( empty( $this->wp_pointers ) ) { return; } // Get dismissed pointers. $dismissed = explode( ',', (string) get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ) ); $pointers_on_page = false; foreach ( array_diff( $this->wp_pointers, $dismissed ) as $pointer ) { // Bind pointer print function. add_action( "admin_footer-{$GLOBALS['hook_suffix']}", array( $this, 'wp_pointer_' . $pointer ) ); // @phpstan-ignore argument.type $pointers_on_page = true; } if ( $pointers_on_page ) { wp_enqueue_style( 'wp-pointer' ); wp_enqueue_script( 'wp-pointer' ); } } } // class TablePress_View PK!ǨSC|C|classes/class-import.phpnu[ */ protected array $import_config = array(); /** * Whether ZIP archive support is available (which it always is, as PclZip is used as a fallback). * * @since 1.0.0 * @deprecated 2.3.0 ZIP support is now always available, either through `ZipArchive` or through `PclZip`. */ public bool $zip_support_available = true; /** * List of table names/IDs for use when replacing/appending existing tables (except for the JSON format). * * @since 2.0.0 * @var array */ protected array $table_names_ids = array(); /** * Runs the import process for a given import configuration. * * @since 2.0.0 * * @param array $import_config Import configuration. * @return array{tables: array>, errors: File[]}|WP_Error List of imported tables on success, WP_Error on failure. */ public function run( array $import_config ) /* : array|WP_Error */ { // Unziping can use a lot of memory and execution time, but not this much hopefully. wp_raise_memory_limit( 'admin' ); if ( function_exists( 'set_time_limit' ) ) { @set_time_limit( 300 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged } $this->import_config = $import_config; $import_files = $this->get_files_to_import(); if ( is_wp_error( $import_files ) ) { return $import_files; } $import_files = $this->convert_zip_files( $import_files ); if ( in_array( $this->import_config['type'], array( 'replace', 'append' ), true ) ) { $this->table_names_ids = $this->get_list_of_table_names(); } return $this->import_files( $import_files ); } /** * Extracts the files that shall be imported from the import configuration. * * @since 2.0.0 * * @return File[]|WP_Error Array of files that shall be imported or WP_Error on failure. */ protected function get_files_to_import() /* : array|WP_Error */ { $import_files = array(); switch ( $this->import_config['source'] ) { case 'file-upload': foreach ( $this->import_config['file-upload']['error'] as $key => $error ) { $file = new File( array( 'location' => $this->import_config['file-upload']['tmp_name'][ $key ], 'name' => $this->import_config['file-upload']['name'][ $key ], ) ); if ( UPLOAD_ERR_OK !== $error ) { @unlink( $this->import_config['file-upload']['tmp_name'][ $key ] ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged $file->error = new WP_Error( 'table_import_file-upload_error', '', $error ); } $import_files[] = $file; } break; case 'url': $host = wp_parse_url( $this->import_config['url'], PHP_URL_HOST ); if ( empty( $host ) ) { return new WP_Error( 'table_import_url_host_invalid', '', $this->import_config['url'] ); } // Check the IP address of the host against a blocklist of hosts which should not be accessible, e.g. for security considerations. $ip = gethostbyname( $host ); // If no IP address can be found, this will return the host name, which will then be checked against the blocklist. $blocked_ips = array( '169.254.169.254', // Meta-data API for various cloud providers. '169.254.170.2', // AWS task metadata endpoint. '192.0.0.192', // Oracle Cloud endpoint. '100.100.100.200', // Alibaba Cloud endpoint. ); if ( in_array( $ip, $blocked_ips, true ) ) { return new WP_Error( 'table_import_url_host_blocked', '', array( 'url' => $this->import_config['url'], 'ip' => $ip ) ); } /** * Load WP file functions to be sure that `download_url()` exists, in particular during Cron requests. */ require_once ABSPATH . 'wp-admin/includes/file.php'; // @phpstan-ignore requireOnce.fileNotFound (This is a WordPress core file that always exists.) // Download URL to local file. $location = download_url( $this->import_config['url'] ); if ( is_wp_error( $location ) ) { $error = new WP_Error( 'table_import_url_download_failed', '', $this->import_config['url'] ); $error->merge_from( $location ); return $error; } $import_files[] = new File( array( 'location' => $location, 'name' => $this->import_config['url'], ) ); break; case 'server': if ( ABSPATH === $this->import_config['server'] ) { return new WP_Error( 'table_import_server_invalid', '', $this->import_config['server'] ); } if ( ! is_readable( $this->import_config['server'] ) ) { return new WP_Error( 'table_import_server_not_readable', '', $this->import_config['server'] ); } $import_files[] = new File( array( 'location' => $this->import_config['server'], 'name' => pathinfo( $this->import_config['server'], PATHINFO_BASENAME ), 'keep_file' => true, // Files on the server must not be deleted. ) ); break; case 'form-field': $location = wp_tempnam(); $num_written_bytes = file_put_contents( $location, $this->import_config['form-field'] ); if ( false === $num_written_bytes || 0 === $num_written_bytes ) { @unlink( $location ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged return new WP_Error( 'table_import_form-field_temp_file_not_written' ); } $import_files[] = new File( array( 'location' => $location, 'name' => __( 'Imported from Manual Input', 'tablepress' ), ) ); break; default: return new WP_Error( 'table_import_invalid_source', '', $this->import_config['source'] ); } return $import_files; } /** * Replaces ZIP archives in the import files with a list of their contents. * * ZIP files are removed from the list and their contents are added to the end of the list. * * @since 2.0.0 * * @param File[] $import_files Files that shall be imported, including ZIP archives. * @return File[] Files that shall be imported, with all ZIP archives recursively replaced by their contents. */ protected function convert_zip_files( array $import_files ): array { foreach ( $import_files as $key => &$file ) { // $file has to be used by reference, so that $key points to the correct element, due to array modification with `unset()` and `array_push()`. // Skip files that already have an error. if ( is_wp_error( $file->error ) ) { continue; } $file->extension = strtolower( pathinfo( $file->name, PATHINFO_EXTENSION ) ); if ( function_exists( 'mime_content_type' ) ) { $mime_type = mime_content_type( $file->location ); if ( false !== $mime_type ) { $file->mime_type = $mime_type; } } // Detect ZIP files from their file extension or MIME type. if ( 'zip' === $file->extension || 'application/zip' === $file->mime_type ) { $extracted_files = $this->extract_zip_file( $file ); if ( is_wp_error( $extracted_files ) ) { $file->error = $extracted_files; $this->maybe_unlink_file( $file ); continue; } if ( empty( $extracted_files ) ) { $file->error = new WP_Error( 'table_import_zip_file_empty', '', $file->name ); $this->maybe_unlink_file( $file ); continue; } /* * Remove the ZIP file from the list and instead append its contents. * Appending ensures recursiveness, as the appended files will be checked again. */ unset( $import_files[ $key ] ); array_push( $import_files, ...$extracted_files ); $this->maybe_unlink_file( $file ); } } unset( $file ); // Unset use-by-reference parameter of foreach loop. $import_files = array_merge( $import_files ); // Re-index. return $import_files; } /** * Extracts the files of a ZIP file and returns a list of files and their location. * * Depending on availability, either the PHP's ZipArchive class or WordPress' PclZip class is used. * * @since 2.0.0 * * @param File $zip_file File data of a ZIP file (likely in a temporary folder). * @return File[]|WP_Error List of files to import that were extracted from the ZIP file or WP_Error on failure. */ protected function extract_zip_file( File $zip_file ) /* : array|WP_Error */ { if ( class_exists( 'ZipArchive', false ) ) { $ziparchive_result = $this->extract_zip_file_ziparchive( $zip_file ); if ( is_array( $ziparchive_result ) ) { return $ziparchive_result; } } else { $ziparchive_result = new WP_Error( 'table_import_error_zip_open', '', array( 'ziparchive_error' => 'Class ZipArchive not available' ) ); } // Fall through to PclZip if ZipArchive is not available or encountered an error opening the file. $pclzip_result = $this->extract_zip_file_pclzip( $zip_file ); if ( is_wp_error( $pclzip_result ) ) { // Append the WP_Error from ZipArchive, to have all error information available. $pclzip_result->merge_from( $ziparchive_result ); } return $pclzip_result; } /** * Extracts the files of a ZIP file using the PHP ZipArchive class. * * The ZIP file is extracted to a temporary folder and a list of files and their location is returned. * * @since 2.3.0 * * @param File $zip_file File data of a ZIP file (likely in a temporary folder). * @return File[]|WP_Error List of files to import that were extracted from the ZIP file or WP_Error on failure. */ protected function extract_zip_file_ziparchive( File $zip_file ) /* : array|WP_Error */ { $archive = new ZipArchive(); $archive_opened = $archive->open( $zip_file->location, ZipArchive::CHECKCONS ); // If the ZIP file can't be opened with ZipArchive::CHECKCONS, try again without. if ( true !== $archive_opened ) { $archive_opened = $archive->open( $zip_file->location ); } // If the ZIP file can't even be opened without ZipArchive::CHECKCONS, bail. if ( true !== $archive_opened ) { return new WP_Error( 'table_import_error_zip_open', '', array( 'ziparchive_error' => $archive_opened ) ); } $files = array(); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase for ( $file_idx = 0; $file_idx < $archive->numFiles; $file_idx++ ) { $file_name = $archive->getNameIndex( $file_idx ); if ( false === $file_name ) { $files[] = new File( array( 'error' => new WP_Error( 'table_import_error_zip_stat', '', array( 'ziparchive_file_index' => $file_idx ) ), ) ); continue; } // Skip directories. if ( str_ends_with( $file_name, '/' ) ) { continue; } // Skip the __MACOSX directory that macOS adds to archives. if ( str_starts_with( $file_name, '__MACOSX/' ) ) { continue; } // Don't extract invalid files. if ( 0 !== validate_file( $file_name ) ) { continue; } $file_data = $archive->getFromIndex( $file_idx ); if ( false === $file_data ) { $files[] = new File( array( 'name' => $file_name, 'error' => new WP_Error( 'table_import_error_zip_get_data', '', array( 'ziparchive_file_index' => $file_idx, 'ziparchive_file_name' => $file_name ) ), ) ); continue; } $location = wp_tempnam(); $num_written_bytes = file_put_contents( $location, $file_data ); if ( false === $num_written_bytes || 0 === $num_written_bytes ) { @unlink( $location ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged $files[] = new File( array( 'name' => $file_name, 'error' => new WP_Error( 'table_import_error_zip_write_temp_data', '', array( 'ziparchive_file_index' => $file_idx, 'ziparchive_file_name' => $file_name ) ), ) ); continue; } $files[] = new File( array( 'location' => $location, 'name' => $file_name, ) ); } $archive->close(); return $files; } /** * Extracts the files of a ZIP file using WordPress' PclZip class. * * The ZIP file is extracted to a temporary folder and a list of files and their location is returned. * * @since 2.3.0 * * @param File $zip_file File data of a ZIP file (likely in a temporary folder). * @return File[]|WP_Error List of files to import that were extracted from the ZIP file or WP_Error on failure. */ protected function extract_zip_file_pclzip( File $zip_file ) /* : array|WP_Error */ { mbstring_binary_safe_encoding(); require_once ABSPATH . 'wp-admin/includes/class-pclzip.php'; // @phpstan-ignore requireOnce.fileNotFound (This is a WordPress core file that always exists.) $archive = new PclZip( $zip_file->location ); $archive_files = $archive->extract( PCLZIP_OPT_EXTRACT_AS_STRING ); // @phpstan-ignore arguments.count (PclZip::extract() uses `func_get_args()` to handle optional arguments.) reset_mbstring_encoding(); // If the ZIP file can't be opened, bail. if ( ! is_array( $archive_files ) ) { return new WP_Error( 'table_import_error_zip_open', '', array( 'pclzip_error' => $archive->errorInfo( true ) ) ); } $files = array(); foreach ( $archive_files as $file ) { // Skip directories. if ( $file['folder'] ) { continue; } // Skip the __MACOSX directory that macOS adds to archives. if ( str_starts_with( $file['filename'], '__MACOSX/' ) ) { continue; } // Don't extract invalid files. if ( 0 !== validate_file( $file['filename'] ) ) { continue; } $location = wp_tempnam(); $num_written_bytes = file_put_contents( $location, $file['content'] ); if ( false === $num_written_bytes || 0 === $num_written_bytes ) { @unlink( $location ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged $files[] = new File( array( 'name' => $file['filename'], 'error' => new WP_Error( 'table_import_error_zip_write_temp_data', '', array( 'ziparchive_file_index' => $file['index'], 'ziparchive_file_name' => $file['filename'] ) ), ) ); continue; } $files[] = new File( array( 'location' => $location, 'name' => $file['filename'], ) ); } return $files; } /** * Deletes a file unless the `keep_file` property is set to `true`. * * @since 2.0.0 * * @param File $file File that should maybe be deleted. */ protected function maybe_unlink_file( File $file ): void { if ( ! $file->keep_file && file_exists( $file->location ) ) { @unlink( $file->location ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged } } /** * Prepares a list of table names/IDs for use when replacing/appending existing tables (except for the JSON format). * * @since 2.0.0 * * @return array List of table names and IDs. */ protected function get_list_of_table_names(): array { $existing_tables = array(); // Load all table IDs and names for a comparison with the file name. $table_ids = TablePress::$model_table->load_all( false ); foreach ( $table_ids as $table_id ) { // Load table, without table data, options, and visibility settings. $table = TablePress::$model_table->load( $table_id, false, false ); if ( ! is_wp_error( $table ) ) { $existing_tables[ (string) $table['name'] ][] = $table_id; // Attention: The table name is not unique! } } return $existing_tables; } /** * Checks whether the requirements for the PHPSpreadsheet import class are fulfilled or if the legacy import class should be used. * * @since 2.0.0 * * @return bool Whether the legacy import class should be used. */ protected function should_use_legacy_import_class(): bool { // Allow overriding in the import config (coming e.g. from the import form UI). if ( $this->import_config['legacy_import'] ) { return true; } /** * Filters whether the Legacy Table Import class shall be used. * * @since 2.0.0 * * @param bool $use_legacy_class Whether to use the legacy table import class. Default false. */ if ( apply_filters( 'tablepress_use_legacy_table_import_class', false ) ) { return true; } // Use the legacy import class, if the requirements for PHPSpreadsheet are not fulfilled. $phpspreadsheet_requirements_fulfilled = extension_loaded( 'mbstring' ) && class_exists( 'ZipArchive', false ) && class_exists( 'DOMDocument', false ) && function_exists( 'simplexml_load_string' ) && ( function_exists( 'libxml_disable_entity_loader' ) || PHP_VERSION_ID >= 80000 ); // This function is only needed for older versions of PHP. if ( ! $phpspreadsheet_requirements_fulfilled ) { return true; } return false; } /** * Imports all found/extracted/configured files into TablePress. * * @since 2.0.0 * * @param File[] $import_files Files that shall be imported. * @return array{tables: array>, errors: File[]} Imported tables and files that caused errors. */ protected function import_files( array $import_files ): array { $tables = array(); $errors = array(); $use_legacy_import_class = $this->should_use_legacy_import_class(); // Load Import Base Class. TablePress::load_file( 'class-import-base.php', 'classes' ); // Choose the Table Import library based on the PHP version and the filter hook value. if ( $use_legacy_import_class ) { // @phpstan-ignore assign.propertyType (The `load_class()` method returns `object` and not a specific type.) $this->importer = TablePress::load_class( 'TablePress_Import_Legacy', 'class-import-legacy.php', 'classes' ); } else { // @phpstan-ignore assign.propertyType (The `load_class()` method returns `object` and not a specific type.) $this->importer = TablePress::load_class( 'TablePress_Import_PHPSpreadsheet', 'class-import-phpspreadsheet.php', 'classes' ); } // If there is more than one valid import file, ignore the chosen existing table for replacing/appending. if ( in_array( $this->import_config['type'], array( 'replace', 'append' ), true ) && '' !== $this->import_config['existing_table'] ) { $valid_import_files = 0; foreach ( $import_files as $file ) { if ( ! is_wp_error( $file->error ) ) { ++$valid_import_files; if ( $valid_import_files > 1 ) { $this->import_config['existing_table'] = ''; break; } } } } // Loop through all import files and import them. foreach ( $import_files as $file ) { if ( is_wp_error( $file->error ) ) { $errors[] = $file; continue; } // Use import method depending on chosen import class. if ( $use_legacy_import_class ) { $table = $this->load_table_from_file_legacy( $file ); } else { $table = $this->load_table_from_file_phpspreadsheet( $file ); } $this->maybe_unlink_file( $file ); if ( is_wp_error( $table ) ) { $file->error = $table; $errors[] = $file; continue; } $table = $this->save_imported_table( $table, $file ); if ( is_wp_error( $table ) ) { $file->error = $table; $errors[] = $file; continue; } $tables[] = $table; } return array( 'tables' => $tables, 'errors' => $errors, ); } /** * Loads a table from a file via the legacy import class. * * @since 2.0.0 * * @param File $file File with the table data. * @return array|WP_Error Loaded table on success (either with all properties or just 'data'), WP_Error on failure. */ protected function load_table_from_file_legacy( File $file ) /* : array|WP_Error */ { // Guess the import format from the file extension. switch ( $file->extension ) { case 'xlsx': // Excel (OfficeOpenXML) Spreadsheet. case 'xlsm': // Excel (OfficeOpenXML) Macro Spreadsheet (macros will be discarded). case 'xltx': // Excel (OfficeOpenXML) Template. case 'xltm': // Excel (OfficeOpenXML) Macro Template (macros will be discarded). $format = 'xlsx'; break; case 'xls': // Excel (BIFF) Spreadsheet. case 'xlt': // Excel (BIFF) Template. $format = 'xls'; break; case 'htm': case 'html': $format = 'html'; break; case 'csv': case 'tsv': $format = 'csv'; break; case 'json': $format = 'json'; break; default: // If no format was found, try finding the format from the first character below. $format = ''; } $data = file_get_contents( $file->location ); if ( false === $data ) { return new WP_Error( 'table_import_legacy_data_read', '', $file->location ); } if ( '' === $data ) { return new WP_Error( 'table_import_legacy_data_empty', '', $file->location ); } // If no format could be determined from the file extension, try guessing from the file content. if ( '' === $format ) { $data = trim( $data ); $first_character = $data[0]; $last_character = $data[-1]; if ( '<' === $first_character && '>' === $last_character ) { $format = 'html'; } elseif ( ( '[' === $first_character && ']' === $last_character ) || ( '{' === $first_character && '}' === $last_character ) ) { $json_table = json_decode( $data, true ); if ( ! is_null( $json_table ) ) { $format = 'json'; } } } // Fall back to CSV if no file format could be determined. if ( '' === $format ) { $format = 'csv'; } if ( ! in_array( $format, $this->importer->import_formats, true ) ) { // @phpstan-ignore property.notFound (`$this->importer` is an instance of `TablePress_Import_Legacy` which has the property `import_formats`.) return new WP_Error( 'table_import_legacy_unknown_format', '', $file->name ); } $table = $this->importer->import_table( $format, $data ); if ( false === $table ) { return new WP_Error( 'table_import_legacy_importer_failed', '', array( 'file_name' => $file->name, 'file_format' => $format ) ); } return $table; } /** * Loads a table from a file via the PHPSpreadsheet import class. * * @since 2.0.0 * * @param File $file File with the table data. * @return array|WP_Error Loaded table on success (either with all properties or just 'data'), WP_Error on failure. */ protected function load_table_from_file_phpspreadsheet( File $file ) /* : array|WP_Error */ { // Convert File object to array, as those are not yet used outside of this class. return $this->importer->import_table( $file ); // @phpstan-ignore return.type (This is an instance of TablePress_Import_PHPSpreadsheet which does not return false.) } /** * Imports a loaded table into TablePress. * * @since 2.0.0 * * @param array $table The table to be imported, either with properties or just the $table['data'] property set. * @param File $file File with the table data. * @return array|WP_Error Imported table on success, WP_Error on failure. */ protected function save_imported_table( array $table, File $file ) /* : array|WP_Error */ { // If name and description are imported from a new table, use those. if ( ! isset( $table['name'] ) ) { $table['name'] = $file->name; } if ( ! isset( $table['description'] ) ) { $table['description'] = $file->name; } $import_type = $this->import_config['type']; $existing_table_id = $this->import_config['existing_table']; // If no existing table ID has been set (or if we are importing multiple tables), try to find a potential existing table from the table ID in the import data or by comparing the file name with the table name. if ( in_array( $import_type, array( 'replace', 'append' ), true ) && '' === $existing_table_id ) { if ( isset( $table['id'] ) ) { // If the table already contained a table ID (e.g. for the JSON format), use that. $existing_table_id = $table['id']; } elseif ( isset( $this->table_names_ids[ $file->name ] ) && 1 === count( $this->table_names_ids[ $file->name ] ) ) { // Use the replace/append ID of tables where the table name matches the file name, but only if there was exactly one file name match. $existing_table_id = $this->table_names_ids[ $file->name ][0]; } } // If the table that is to be replaced or appended to does not exist, add the new table instead. if ( ! TablePress::$model_table->table_exists( $existing_table_id ) ) { $existing_table_id = ''; $import_type = 'add'; } $table = $this->import_tablepress_table( $table, $import_type, $existing_table_id ); return $table; } /** * Imports a table by either replacing or appending to an existing table or by adding it as a new table. * * @since 1.0.0 * * @param array $imported_table The table to be imported, either with properties or just the `name`, `description`, and `data` property set. * @param string $import_type What to do with the imported data: "add", "replace", "append". * @param string $existing_table_id Empty string if table shall be added as a new table, ID of the table to be replaced or appended to otherwise. * @return array|WP_Error Table on success, WP_Error on error. */ protected function import_tablepress_table( array $imported_table, string $import_type, string $existing_table_id ) /* : array|WP_Error */ { // Full JSON format table can contain a table ID, try to keep that, by later changing the imported table ID to this. $table_id_in_import = $imported_table['id'] ?? ''; // To be able to replace or append to a table, the user must be able to edit the table, or it must be a request via the Automatic Periodic Table Import module. if ( in_array( $import_type, array( 'replace', 'append' ), true ) && ! ( current_user_can( 'tablepress_edit_table', $existing_table_id ) || doing_action( 'tablepress_automatic_periodic_table_import_action' ) ) ) { return new WP_Error( 'table_import_replace_append_capability_check_failed', '', $existing_table_id ); } switch ( $import_type ) { case 'add': $existing_table = TablePress::$model_table->get_table_template(); // Import visibility information if it exists, usually only for the JSON format. if ( isset( $imported_table['visibility'] ) ) { $existing_table['visibility'] = $imported_table['visibility']; } break; case 'replace': // Load table, without table data, but with options and visibility settings. $existing_table = TablePress::$model_table->load( $existing_table_id, false, true ); if ( is_wp_error( $existing_table ) ) { $error = new WP_Error( 'table_import_replace_table_load', '', $existing_table_id ); $error->merge_from( $existing_table ); return $error; } // Don't change name and description when a table is replaced. $imported_table['name'] = $existing_table['name']; $imported_table['description'] = $existing_table['description']; // Replace visibility information if it exists. if ( isset( $imported_table['visibility'] ) ) { $existing_table['visibility'] = $imported_table['visibility']; } break; case 'append': // Load table, with table data, options, and visibility settings. $existing_table = TablePress::$model_table->load( $existing_table_id, true, true ); if ( is_wp_error( $existing_table ) ) { $error = new WP_Error( 'table_import_append_table_load', '', $existing_table_id ); $error->merge_from( $existing_table ); return $error; } if ( isset( $existing_table['is_corrupted'] ) && $existing_table['is_corrupted'] ) { return new WP_Error( 'table_import_append_table_load_corrupted', '', $existing_table_id ); } // Don't change name and description when a table is appended to. $imported_table['name'] = $existing_table['name']; $imported_table['description'] = $existing_table['description']; // Actual appending:. $imported_table['data'] = array_merge( $existing_table['data'], $imported_table['data'] ); $this->importer->pad_array_to_max_cols( $imported_table['data'] ); // Append visibility information for rows. if ( isset( $imported_table['visibility']['rows'] ) ) { $existing_table['visibility']['rows'] = array_merge( $existing_table['visibility']['rows'], $imported_table['visibility']['rows'] ); } // When appending, do not overwrite options, e.g. coming from a JSON file. unset( $imported_table['options'] ); break; default: return new WP_Error( 'table_import_import_type_invalid', '', $import_type ); } // Merge new or existing table with information from the imported table. $imported_table['id'] = $existing_table['id']; // Will be false for new table or the existing table ID. // Cut visibility array (if the imported table is smaller), and pad correctly if imported table is bigger than existing table (or new template). $num_rows = count( $imported_table['data'] ); $num_columns = count( $imported_table['data'][0] ); $imported_table['visibility'] = array( 'rows' => array_pad( array_slice( $existing_table['visibility']['rows'], 0, $num_rows ), $num_rows, 1 ), 'columns' => array_pad( array_slice( $existing_table['visibility']['columns'], 0, $num_columns ), $num_columns, 1 ), ); // Check if the new table data is valid and consistent. $table = TablePress::$model_table->prepare_table( $existing_table, $imported_table, false ); if ( is_wp_error( $table ) ) { $error = new WP_Error( 'table_import_table_prepare', '', $imported_table['id'] ); $error->merge_from( $table ); return $error; } // DataTables Custom Commands can only be edit by trusted users. if ( ! current_user_can( 'unfiltered_html' ) ) { $table['options']['datatables_custom_commands'] = $existing_table['options']['datatables_custom_commands']; } // Replace existing table or add new table. if ( in_array( $import_type, array( 'replace', 'append' ), true ) ) { // Replace existing table with imported/appended table. $table_id = TablePress::$model_table->save( $table ); } else { // Add the imported table (and get its first ID). $table_id = TablePress::$model_table->add( $table ); } if ( is_wp_error( $table_id ) ) { $error = new WP_Error( 'table_import_table_save_or_add', '', $table['id'] ); $error->merge_from( $table_id ); return $error; } // Try to use ID from imported file (e.g. in full JSON format table). if ( '' !== $table_id_in_import && $table_id !== $table_id_in_import && current_user_can( 'tablepress_edit_table_id', $table_id ) ) { $id_changed = TablePress::$model_table->change_table_id( $table_id, $table_id_in_import ); if ( ! is_wp_error( $id_changed ) ) { $table_id = $table_id_in_import; } } $table['id'] = $table_id; return $table; } /** * Imports a table in legacy versions of the Table Auto Update Extension. * * This method is deprecated and is only left for backward compatibility reasons. Do not use this in new code! * * @since 1.0.0 * @deprecated 2.0.0 Use `run()` instead. * * @param string $format Import format. * @param string $data Data to import. * @return array|WP_Error|false Table array on success, WP_Error or false on error. */ public function import_table( string $format, string $data ) /* : array|false */ { TablePress::load_file( 'class-import-base.php', 'classes' ); $importer = TablePress::load_class( 'TablePress_Import_Legacy', 'class-import-legacy.php', 'classes' ); return $importer->import_table( $format, $data ); } } // class TablePress_Import PK!i9!!(classes/class-elementor-widget-table.phpnu[> Widget stack. */ public function get_stack( $with_common_controls = true ) { $stack = parent::get_stack( $with_common_controls ); // @phpstan-ignore staticMethod.notFound (Elementor methods are not in the stubs.) unset( $stack['tabs']['advanced'] ); return $stack; } /** * Gets the widget upsale data. * * @since 3.1.0 * * @return array Widget upsale data. */ protected function get_upsale_data(): array { return array( 'condition' => tb_tp_fs()->is_free_plan(), 'image' => plugins_url( 'admin/img/tablepress.svg', TABLEPRESS__FILE__ ), 'image_alt' => esc_attr__( 'Upgrade to TablePress Pro!', 'tablepress' ), 'title' => esc_html__( 'Upgrade to TablePress Pro!', 'tablepress' ), 'description' => esc_html__( 'Check out the TablePress premium versions and give your tables super powers!', 'tablepress' ), 'upgrade_url' => 'https://tablepress.org/premium/?utm_source=plugin&utm_medium=button&utm_content=elementor-widget', 'upgrade_text' => esc_html__( 'Upgrade Now', 'tablepress' ), ); } /** * Gets whether the widget requires an inner wrapper. * * This is used to determine whether to optimize the DOM size. * * @since 3.1.0 * * @return bool Whether to optimize the DOM size. */ public function has_widget_inner_wrapper(): bool { return false; } /** * Gets whether the element returns dynamic content. * * This is used to determine whether to cache the element output or not. * * @since 3.1.0 * * @return bool Whether to cache the element output. */ protected function is_dynamic_content(): bool { return true; } /** * Registers the widget controls. * * Adds input fields to allow the user to customize the widget settings. * * @since 3.1.0 */ protected function register_controls(): void { $this->start_controls_section( // @phpstan-ignore method.notFound (Elementor methods are not in the stubs.) 'table', array( 'label' => esc_html__( 'Table', 'tablepress' ), 'tab' => \Elementor\Controls_Manager::TAB_CONTENT, // @phpstan-ignore classConstant.notFound (Elementor constants are not in the stubs.) ), ); $tables = array(); // Load all table IDs without priming the post meta cache, as table options/visibility are not needed. $table_ids = \TablePress::$model_table->load_all( false ); foreach ( $table_ids as $table_id ) { // Load table, without table data, options, and visibility settings. $table = \TablePress::$model_table->load( $table_id, false, false ); // Skip tables that could not be loaded. if ( is_wp_error( $table ) ) { continue; } if ( '' === trim( $table['name'] ) ) { $table['name'] = __( '(no name)', 'tablepress' ); } $tables[ $table_id ] = esc_html( sprintf( __( 'ID %1$s: %2$s', 'tablepress' ), $table_id, $table['name'] ) ); } /** * Filters the list of table IDs and names that is passed to the block editor, and is then used in the dropdown of the TablePress table block. * * @since 2.0.0 * * @param array $tables List of table names, the table ID is the array key. */ $tables = apply_filters( 'tablepress_block_editor_tables_list', $tables ); $this->add_control( // @phpstan-ignore method.notFound 'table_id', array( 'label' => esc_html__( 'Table:', 'tablepress' ), 'show_label' => false, 'label_block' => true, 'description' => esc_html__( 'Select the TablePress table that you want to embed.', 'tablepress' ) . ( current_user_can( 'tablepress_list_tables' ) ? sprintf( ' %2$s', esc_url( \TablePress::url( array( 'action' => 'list' ) ) ), esc_html__( 'Manage your tables.', 'tablepress' ) ) : '' ), 'type' => \Elementor\Controls_Manager::SELECT2, // @phpstan-ignore classConstant.notFound (Elementor constants are not in the stubs.) 'ai' => array( 'active' => false ), 'options' => $tables, ), ); $this->end_controls_section(); // @phpstan-ignore method.notFound (Elementor methods are not in the stubs.) $this->start_controls_section( // @phpstan-ignore method.notFound (Elementor methods are not in the stubs.) 'advanced', array( 'label' => esc_html__( 'Advanced', 'tablepress' ), 'tab' => \Elementor\Controls_Manager::TAB_CONTENT, // @phpstan-ignore classConstant.notFound (Elementor constants are not in the stubs.) 'condition' => array( 'id!' => '', ), ), ); $this->add_control( // @phpstan-ignore method.notFound (Elementor methods are not in the stubs.) 'parameters', array( 'label' => esc_html__( 'Configuration parameters:', 'tablepress' ), 'label_block' => true, 'description' => esc_html( __( 'These additional parameters can be used to modify specific table features.', 'tablepress' ) . ' ' . __( 'See the TablePress Documentation for more information.', 'tablepress' ) ), 'type' => \Elementor\Controls_Manager::TEXT, // @phpstan-ignore classConstant.notFound (Elementor constants are not in the stubs.) 'input_type' => 'text', 'placeholder' => '', 'ai' => array( 'active' => false ), ), ); $this->end_controls_section(); // @phpstan-ignore method.notFound (Elementor methods are not in the stubs.) } /** * Render oEmbed widget output on the frontend. * * Written in PHP and used to generate the final HTML. * * @since 3.1.0 */ protected function render(): void { $settings = $this->get_settings_for_display(); // @phpstan-ignore method.notFound (Elementor methods are not in the stubs.) // Don't return anything if no table was selected. if ( empty( $settings['table_id'] ) ) { /* * In TablePress 3.1 (before 3.1.1), the widget control was named "id" instead of "table_id", which however caused problems. * To ensure that tables will continue to be shown, if the widget was created with 3.1, the "table_id" is set to the "id" value, if only that exists. */ if ( empty( $settings['id'] ) ) { return; } else { $settings['table_id'] = $settings['id']; } } if ( '' !== trim( $settings['parameters'] ) ) { $render_attributes = shortcode_parse_atts( $settings['parameters'] ); } else { $render_attributes = array(); } $render_attributes['id'] = $settings['table_id']; /* * It would be nice to print only the Shortcode, for better data portability, e.g. if a site switches away from Elementor. * However, the editor will then only render the Shortcode itself, which is not very helpful. * Due to this, the table HTML code is rendered. * echo '[' . \TablePress::$shortcode . " id={$settings['table_id']} {$settings['parameters']} /]"; */ echo \TablePress::$controller->shortcode_table( $render_attributes ); } } // class TablePressTableWidget PK!G#classes/class-admin-page-helper.phpnu[ $script_data Optional. JS data that is printed to the page before the script is included. The array key will be used as the name, the value will be JSON encoded. */ public function enqueue_script( string $name, array $dependencies = array(), array $script_data = array() ): void { $js_file = "admin/js/build/{$name}.js"; $js_url = plugins_url( $js_file, TABLEPRESS__FILE__ ); $version = TablePress::version; // Load dependencies and version from the auto-generated asset PHP file. $script_asset_path = TABLEPRESS_ABSPATH . "admin/js/build/{$name}.asset.php"; if ( file_exists( $script_asset_path ) ) { $script_asset = require $script_asset_path; if ( isset( $script_asset['dependencies'] ) ) { $dependencies = array_merge( $dependencies, $script_asset['dependencies'] ); } if ( isset( $script_asset['version'] ) ) { $version = $script_asset['version']; } } /* * Register the `react-jsx-runtime` polyfill, if it is not already registered. * This is needed as a polyfill for WP < 6.6, and can be removed once WP 6.6 is the minimum requirement for TablePress. */ if ( ! wp_script_is( 'react-jsx-runtime', 'registered' ) ) { wp_register_script( 'react-jsx-runtime', plugins_url( 'admin/js/react-jsx-runtime.min.js', TABLEPRESS__FILE__ ), array( 'react' ), TablePress::version, true ); } /** * Filters the dependencies of a TablePress script file. * * @since 2.0.0 * * @param string[] $dependencies List of the dependencies that the $name script relies on. * @param string $name Name of the JS script, without extension. */ $dependencies = apply_filters( 'tablepress_admin_page_script_dependencies', $dependencies, $name ); wp_enqueue_script( "tablepress-{$name}", $js_url, $dependencies, $version, true ); // Load JavaScript translation files, for all scripts that rely on `wp-i18n`. if ( in_array( 'wp-i18n', $dependencies, true ) ) { wp_set_script_translations( "tablepress-{$name}", 'tablepress' ); } if ( ! empty( $script_data ) ) { foreach ( $script_data as $var_name => $var_data ) { $var_data = wp_json_encode( $var_data, JSON_FORCE_OBJECT | JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ); wp_add_inline_script( "tablepress-{$name}", "const tablepress_{$var_name} = {$var_data};", 'before' ); } } } /** * Register a filter hook on the admin footer. * * @since 1.0.0 */ public function add_admin_footer_text(): void { // Show admin footer message (only on TablePress admin screens). add_filter( 'admin_footer_text', array( $this, '_admin_footer_text' ) ); } /** * Adds a TablePress "Thank You" message to the admin footer content. * * @since 1.0.0 * * @param string $content Current admin footer content. * @return string New admin footer content. */ public function _admin_footer_text( /* string */ $content ): string { // Don't use a type hint in the method declaration as many WordPress plugins use the `admin_footer_text` filter in the wrong way. // Protect against other plugins not returning a string in their filter callbacks. if ( ! is_string( $content ) ) { // @phpstan-ignore function.alreadyNarrowedType (The `is_string()` check is needed as the input is coming from a filter hook.) $content = ''; } $content .= ' • ' . sprintf( __( 'Thank you for using TablePress.', 'tablepress' ), 'https://tablepress.org/' ); if ( tb_tp_fs()->is_free_plan() ) { $content .= ' ' . sprintf( __( 'Take a look at the Premium features!', 'tablepress' ), 'https://tablepress.org/premium/?utm_source=plugin&utm_medium=textlink&utm_content=admin-footer' ); } return $content; } /** * Print the JavaScript code for a WP feature pointer. * * @since 1.0.0 * * @param string $pointer_id The pointer ID. * @param string $selector The HTML elements, on which the pointer should be attached. * @param array $args Arguments to be passed to the pointer JS (see wp-pointer.js). */ public function print_wp_pointer_js( string $pointer_id, string $selector, array $args ): void { if ( empty( $pointer_id ) || empty( $selector ) || empty( $args['content'] ) ) { return; } /* * Print JS code for the feature pointers, extended with event handling for opened/closed "Screen Options", so that pointers can * be repositioned. 210 ms is slightly slower than jQuery's "fast" value, to allow all elements to reach their original position. */ ?> > $table_data Table data in which formulas shall be evaluated. * @param string $table_id ID of the passed table. * @return array> Table data with evaluated formulas. */ public function evaluate_table_data( array $table_data, string $table_id ): array { // Choose the Table Evaluate library based on the PHP version and the filter hook value. if ( $this->_should_use_legacy_evaluate_class() ) { $evaluate_class = TablePress::load_class( 'TablePress_Evaluate_Legacy', 'class-evaluate-legacy.php', 'classes' ); } else { $evaluate_class = TablePress::load_class( 'TablePress_Evaluate_PHPSpreadsheet', 'class-evaluate-phpspreadsheet.php', 'classes' ); } return $evaluate_class->evaluate_table_data( $table_data, $table_id ); } } // class TablePress_Evaluate PK! index.phpnu[ array('react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-i18n', 'wp-primitives', 'wp-server-side-render', 'wp-shortcode'), 'version' => '1c73b075950049289e13'); PK!#bblocks/table/build/index.jsnu[(()=>{"use strict";var e={n:t=>{var r=t&&t.__esModule?()=>t.default:()=>t;return e.d(r,{a:r}),r},d:(t,r)=>{for(var s in r)e.o(r,s)&&!e.o(t,s)&&Object.defineProperty(t,s,{enumerable:!0,get:r[s]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)};const t=window.wp.blocks,r=window.wp.shortcode;var s=e.n(r);const a=JSON.parse('{"UU":"tablepress/table"}'),l=e=>{let t=Object.entries(e.named).map((([e,t])=>{let r="";return t=t.replace(/“([^”]*)”/g,"$1"),(/\s/.test(t)||""===t)&&(r='"'),t.includes('"')&&(r="'"),`${e}=${r}${t}${r}`})).join(" ");return e.numeric.forEach((e=>{/\s/.test(e)?t+=' "'+e+'"':t+=" "+e})),t},n=e=>{delete e.named.id;let t=l(e);return t=t.replace(/=“([^”]*)”/g,'="$1"'),t=t.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll("[","[").replaceAll("]","]"),t},o=e=>{let r=s().next(tp.table.shortcode,e).shortcode.attrs;r={named:{...r.named},numeric:[...r.numeric]};const l=r.named.id,o=n(r);return(0,t.createBlock)(a.UU,{id:l,parameters:o})},i={from:[{type:"shortcode",tag:tp.table.shortcode,attributes:{id:{type:"string",shortcode:({named:{id:e=""}})=>e},parameters:{type:"string",shortcode:e=>(e={named:{...e.named},numeric:[...e.numeric]},n(e))}}},{type:"enter",regExp:s().regexp(tp.table.shortcode),transform:({content:e})=>o(e)},{type:"block",blocks:["core/shortcode"],transform:({text:e})=>o(e),isMatch:({text:e})=>void 0!==s().next(tp.table.shortcode,e),isMultiBlock:!1}],to:[{type:"block",blocks:["core/shortcode"],transform:({id:e,parameters:r})=>{""!==(r=r.trim())&&(r=(r+=" ").replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll("[","[").replaceAll("]","]"));const s=`[${tp.table.shortcode} id=${e} ${r}/]`;return(0,t.createBlock)("core/shortcode",{text:s})}}]},c=window.wp.i18n,p=window.wp.serverSideRender;var d=e.n(p);const b=window.wp.blockEditor,h=window.wp.components,m=window.wp.primitives,u=window.ReactJSXRuntime,x=(0,u.jsxs)(m.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"-32 -32 64 64",fill:"#50575e",children:[(0,u.jsx)(m.Path,{d:"M0-25.854h-25.854v51.708h51.708V0H21v21h-42v-42H0Z"}),(0,u.jsx)(m.Path,{d:"M-18-18h10v10h-10zM-18-5h10V5h-10zM-5-5H5V5H-5zM-18 8h10v10h-10zM-5 8H5v10H-5zM8 8h10v10H8zM5-31h6.18v6.18H5zM19-25h6.18v6.18H19zM0-15h3.82v3.82H0zM10-20h3.82v3.82H10zM25-12h3.82v3.82H25zM8-13h10v10H8z"})]}),w=Object.entries(tp.tables).map((([e,t])=>({value:e,label:(0,c.sprintf)((0,c.__)("ID %1$s: “%2$s”","tablepress"),e,t)}))),_=function(){return""!==tp.url&&(0,u.jsx)(h.ExternalLink,{href:tp.url,children:(0,c.__)("Manage your tables.","tablepress")})},v=window.wp.element;let g=null;const j=Object.keys(tp.tables);j.length&&(g={attributes:{id:j[Math.floor(Math.random()*j.length)],parameters:""}});const f=g;(0,t.registerBlockType)(a.UU,{transforms:i,edit:({attributes:e,setAttributes:t})=>{const r=(0,b.useBlockProps)();let n;if(e.id&&tp.tables.hasOwnProperty(e.id))n=(0,u.jsxs)("div",{...r,children:[tp.load_block_preview&&(0,u.jsx)(d(),{block:a.UU,attributes:{id:e.id,parameters:`block_preview=true ${e.parameters}`.trim()},className:"render-wrapper"}),(0,u.jsx)("div",{className:"table-overlay",children:(0,c.sprintf)((0,c.__)("TablePress table %1$s: “%2$s”","tablepress"),e.id,tp.tables[e.id])})]});else{let t=0{null!=e||(e=""),t({id:e.replace(/[^0-9a-zA-Z-_]/g,"")})}}):(0,u.jsxs)(u.Fragment,{children:[(0,c.__)("There are no TablePress tables on this site yet.","tablepress"),""!==tp.url&&" ",(0,u.jsx)(_,{})]})})}),e.id&&tp.tables.hasOwnProperty(e.id)&&(0,u.jsx)(b.InspectorAdvancedControls,{children:(0,u.jsx)(h.TextControl,{__nextHasNoMarginBottom:!0,__next40pxDefaultSize:!0,label:(0,c.__)("Configuration parameters:","tablepress"),help:(0,c.__)("These additional parameters can be used to modify specific table features.","tablepress")+" "+(0,c.__)("See the TablePress Documentation for more information.","tablepress"),value:e.parameters,onChange:e=>{e=(e=s().replace(tp.table.shortcode,e,(({attrs:e})=>(delete(e={named:{...e.named},numeric:[...e.numeric]}).named.id," "+l(e)+" ")))).replace(/=“([^”]*)”/g,'="$1"'),t({parameters:e})},onBlur:e=>{const r=e.target.value.trim();t({parameters:r})}})})]});return(0,u.jsxs)(u.Fragment,{children:[n,o]})},save:({attributes:{id:e="",parameters:t=""}})=>""===e?"":(""!==(t=t.trim())&&(t+=" "),(0,u.jsx)(v.RawHTML,{children:`[${tp.table.shortcode} id=${e} ${t}/]`})),example:f,icon:x})})();PK!yޡblocks/table/build/index.cssnu[.wp-block-tablepress-table>.render-wrapper{max-height:400px;min-height:64px;overflow:scroll}.wp-block-tablepress-table>.render-wrapper>.tablepress{margin:0 auto;pointer-events:none}.wp-block-tablepress-table>div>div>.render-wrapper{display:none}.wp-block-tablepress-table>div>div{margin-top:8px!important}.wp-block-tablepress-table .table-overlay{background-color:#ffffff80;box-shadow:inset 0 0 50px #ccc;box-sizing:border-box;font-weight:700;height:100%;padding:2em;top:0;width:100%}.block-editor-block-preview__content-iframe .wp-block-tablepress-table .table-overlay{display:none}.wp-block-tablepress-table .render-wrapper+.table-overlay{position:absolute}.wp-block-tablepress-table:hover .render-wrapper+.table-overlay{display:none}.wp-block-tablepress-table .components-placeholder__label{font-size:18px}.wp-block-tablepress-table-inspector-panel .components-checkbox-control__input[type=checkbox]:indeterminate{opacity:.4} PK!blocks/table/index.phpnu[, label: }, ... ]. const ComboboxControlOptions = Object.entries( tp.tables ).map( ( [ id, name ] ) => { return { value: id, label: sprintf( __( 'ID %1$s: “%2$s”', 'tablepress' ), id, name ), }; } ); /** * Custom component for the "Manage your tables." link. */ const ManageTablesLink = function () { return ( '' !== tp.url && { __( 'Manage your tables.', 'tablepress' ) } ); } /** * The edit function describes the structure of your block in the context of the * editor. This represents what the editor will render when the block is used. * * @param {Object} params Function parameters. * @param {Object} params.attributes Block attributes. * @param {Function} params.setAttributes Function to set block attributes. * @return {Element} Element to render. */ const TablePressTableEdit = ( { attributes, setAttributes } ) => { const blockProps = useBlockProps(); let blockMarkup; if ( attributes.id && tp.tables.hasOwnProperty( attributes.id ) ) { blockMarkup = (
{ tp.load_block_preview && }
{ sprintf( __( 'TablePress table %1$s: “%2$s”', 'tablepress' ), attributes.id, tp.tables[ attributes.id ] ) }
); } else { let instructions = 0 < ComboboxControlOptions.length ? __( 'Select the TablePress table that you want to embed in the Settings sidebar.', 'tablepress' ) : __( 'There are no TablePress tables on this site yet.', 'tablepress' ); if ( attributes.id ) { // Show an error message if a table could not be found (e.g. after a table was deleted). The tp.tables.hasOwnProperty( attributes.id ) check happens above. instructions = sprintf( __( 'There is a problem: The TablePress table with the ID “%1$s” could not be found.', 'tablepress' ), attributes.id ) + ' ' + instructions; } blockMarkup = (
} label={ __( 'TablePress table', 'tablepress' ) } instructions={ instructions } >
); } const sidebarMarkup = ( <> { 0 < ComboboxControlOptions.length ? { __( 'Select the TablePress table that you want to embed.', 'tablepress' ) } { '' !== tp.url && ' ' } } value={ attributes.id } options={ ComboboxControlOptions } onChange={ ( id ) => { id ??= ''; setAttributes( { id: id.replace( /[^0-9a-zA-Z-_]/g, '' ) } ); } } /> : <> { __( 'There are no TablePress tables on this site yet.', 'tablepress' ) } { '' !== tp.url && ' ' } } { attributes.id && tp.tables.hasOwnProperty( attributes.id ) && { parameters = shortcode.replace( tp.table.shortcode, parameters, ( { attrs: shortcodeAttrs } ) => { shortcodeAttrs = { named: { ...shortcodeAttrs.named }, numeric: [ ...shortcodeAttrs.numeric ] }; // Use object destructuring to get a clone of the object. delete shortcodeAttrs.named.id; return ' ' + shortcodeAttrsToString( shortcodeAttrs ) + ' '; // Add spaces around replacement text to have separation to possibly already existing parameters. } ); parameters = parameters.replace( /=“([^”]*)”/g, '="$1"' ); // Replace curly quotation marks around a value with normal ones. setAttributes( { parameters } ); } } onBlur={ ( event ) => { const parameters = event.target.value.trim(); // Remove leading and trailing whitespace from the parameter string. setAttributes( { parameters } ); } } /> } ); return ( <> { blockMarkup } { sidebarMarkup } ); }; export default TablePressTableEdit; PK!`iwblocks/table/src/icon.jsxnu[/** * JavaScript code for the TablePress table block icon in the block editor. * * @package TablePress * @subpackage Blocks * @author Tobias Bäthge * @since 3.0.0 */ /** * WordPress dependencies. */ import { SVG, Path } from '@wordpress/primitives'; const TablePressTableIcon = ( ); export default TablePressTableIcon; PK!07blocks/table/src/editor.scssnu[/** * CSS for the TablePress table block in the block editor. * * @package TablePress * @subpackage Blocks * @author Tobias Bäthge * @since 2.0.0 */ .wp-block-tablepress-table { $parent: &; /* Limit the height of server-side render preview in the block editor. */ > .render-wrapper { overflow: scroll; min-height: 64px; max-height: 400px; > .tablepress { /* Prevent interaction with elements like links inside tables, similar to the `Disabled` component from Gutenberg. */ pointer-events: none; /* Remove the bottom margin in the block preview. */ margin: 0 auto; } } /* Hide the server-side render wrapper while it's being refreshed. */ > div > div > .render-wrapper { display: none; } /* Move the loading spinner inside the table overlay. */ > div > div { margin-top: 8px !important; /* The !important flag is necessary to overwrite an inline style. */ } /* Show an overlay on top of the table, to indicate that it's not fully shown. */ @at-root .table-overlay { #{$parent} & { box-sizing: border-box; top: 0; width: 100%; height: 100%; box-shadow: inset 0 0 50px #cccccc; background-color: rgba(255, 255, 255, 0.5); padding: 2em; font-weight: bold; /* Hide the table overlay in the block preview. */ .block-editor-block-preview__content-iframe & { display: none; } } /* Position the table overlay above the table preview once it's loaded. */ #{$parent} .render-wrapper + & { position: absolute; } /* Hide the table overlay when it's hovered while a table preview is shown. */ #{$parent}:hover .render-wrapper + & { display: none; } } /* Increase the placeholder label font size. */ .components-placeholder__label { font-size: 18px; } /* Elements inside the InspectorControls PanelBodys. */ &-inspector-panel { /* Make the indeterminate state of a checkbox lighter. */ .components-checkbox-control__input[type="checkbox"] { &:indeterminate { opacity: 0.4; } } } } PK!tblocks/table/src/save.jsnu[/** * JavaScript code for the TablePress table block in the block editor. * * @package TablePress * @subpackage Blocks * @author Tobias Bäthge * @since 2.0.0 */ /** * WordPress dependencies */ import { RawHTML } from '@wordpress/element'; /** * The save function defines the way in which the different attributes should * be combined into the final markup, which is then serialized by the block * editor into `post_content`. * * @param {Object} params Function parameters. * @param {Object} params.attributes Block attributes. * @param {string} params.attributes.id Table ID. * @param {string} params.attributes.parameters Table render attributes. * @return {Element} Element to render. */ const save = ( { attributes: { id = '', parameters = '' } } ) => { if ( '' === id ) { return ''; } parameters = parameters.trim(); if ( '' !== parameters ) { parameters += ' '; } return { `[${ tp.table.shortcode } id=${ id } ${ parameters }/]` }; }; export default save; PK!blocks/table/src/index.phpnu[ { // Convert named attributes. let shortcodeAttrsString = Object.entries( shortcodeAttrs.named ).map( ( [ attribute, value ] ) => { let enclose = ''; // Don't enclose values by default. // Remove curly quotation marks around a value. value = value.replace( /“([^”]*)”/g, '$1' ); // Use " as delimiter if value contains whitespace or is empty. if ( /\s/.test( value ) || '' === value ) { enclose = '"'; } // Use ' as delimiter if value contains ". if ( value.includes( '"' ) ) { enclose = '\''; } return `${ attribute }=${ enclose }${ value }${ enclose }`; } ).join( ' ' ); // Convert numeric attributes. shortcodeAttrs.numeric.forEach( ( value ) => { if ( /\s/.test( value ) ) { shortcodeAttrsString += ' "' + value + '"'; } else { shortcodeAttrsString += ' ' + value; } } ); return shortcodeAttrsString; }; PK!!blocks/table/src/common/index.phpnu[ { // Remove the `id` attribute from the Shortcode parameters, as it is handled separately. delete shortcodeAttrs.named.id; let parameters = shortcodeAttrsToString( shortcodeAttrs ); // Replace curly quotation marks around a value with normal ones. parameters = parameters.replace( /=“([^”]*)”/g, '="$1"' ); // Decode HTML entities like `&`, `<`, `>`, `[`, and `]` that were encoded in the Shortcode. parameters = parameters.replaceAll( '&', '&' ).replaceAll( '<', '<' ).replaceAll( '>', '>' ).replaceAll( '[', '[' ).replaceAll( ']', ']' ); return parameters; }; /** * Converts a textual Shortcode to a TablePress table block. * * @param {string} content The Shortcode as a text string. * @return {Object} TablePress table block. */ const convertShortcodeTextToBlock = ( content ) => { let shortcodeAttrs = shortcode.next( tp.table.shortcode, content ).shortcode.attrs; shortcodeAttrs = { named: { ...shortcodeAttrs.named }, numeric: [ ...shortcodeAttrs.numeric ] }; // Use object destructuring to get a clone of the object. const id = shortcodeAttrs.named.id; const parameters = convertShortcodeAttrsToParametersString( shortcodeAttrs ); return createBlock( block.name, { id, parameters } ); }; const transforms = { from: [ // Detect table Shortcodes that are pasted into the block editor. { type: 'shortcode', tag: tp.table.shortcode, attributes: { id: { type: 'string', shortcode: ( { named: { id = '' } } ) => { return id; }, }, parameters: { type: 'string', shortcode: ( shortcodeAttrs ) => { shortcodeAttrs = { named: { ...shortcodeAttrs.named }, numeric: [ ...shortcodeAttrs.numeric ] }; // Use object destructuring to get a clone of the object. return convertShortcodeAttrsToParametersString( shortcodeAttrs ); }, }, }, }, // Detect table Shortcodes that are typed into the block editor. { type: 'enter', regExp: shortcode.regexp( tp.table.shortcode ), transform: ( { content } ) => convertShortcodeTextToBlock( content ), }, // Add conversion option from "Shortcode" to "TablePress table" block. { type: 'block', blocks: [ 'core/shortcode' ], transform: ( { text: content } ) => convertShortcodeTextToBlock( content ), isMatch: ( { text } ) => { return ( undefined !== shortcode.next( tp.table.shortcode, text ) ); }, isMultiBlock: false, }, ], to: [ // Add conversion option from "TablePress table" to "Shortcode" block. { type: 'block', blocks: [ 'core/shortcode' ], transform: ( { id, parameters } ) => { parameters = parameters.trim(); if ( '' !== parameters ) { parameters += ' '; // Encode characters that cause problems in Shortcodes, e.g. due to sanitization, like `&`, `<`, `>`, `[`, and `]` to HTML entities. parameters = parameters.replaceAll( '&', '&' ).replaceAll( '<', '<' ).replaceAll( '>', '>' ).replaceAll( '[', '[' ).replaceAll( ']', ']' ); } const text = `[${ tp.table.shortcode } id=${ id } ${ parameters }/]`; return createBlock( 'core/shortcode', { text } ); }, }, ], }; export default transforms; PK!blocks/index.phpnu[ array( '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'tablepress/table', 'version' => '3.1.2', 'title' => 'TablePress table', 'category' => 'media', 'icon' => 'list-view', 'description' => 'Embed a TablePress table.', 'keywords' => array( 'table' ), 'textdomain' => 'tablepress', 'attributes' => array( 'id' => array( 'type' => 'string', 'default' => '' ), 'parameters' => array( 'type' => 'string', 'default' => '' ) ), 'supports' => array( 'align' => false, 'html' => false, 'customClassName' => false ), 'editorScript' => 'file:build/index.js', 'editorStyle' => 'file:build/index.css' ) ); PK!22libraries/freemius/config.phpnu[table.widefat{border:none!important}#fs_sites .fs-scrollable-table .fs-main-column{width:100%}#fs_sites .fs-scrollable-table .fs-site-details td:first-of-type{color:gray;text-align:right;width:1px}#fs_sites .fs-scrollable-table .fs-site-details td:last-of-type{text-align:right}#fs_sites .fs-scrollable-table .fs-install-details table tr td{white-space:nowrap;width:1px}#fs_sites .fs-scrollable-table .fs-install-details table tr td:last-of-type{width:auto}#fs_addons h3{border:none;margin-bottom:0;padding:4px 5px}#fs_addons td{vertical-align:middle}#fs_addons thead{white-space:nowrap}#fs_addons td:first-child,#fs_addons th:first-child{font-weight:700;text-align:left}#fs_addons td:last-child,#fs_addons th:last-child{text-align:right}#fs_addons th{font-weight:700}#fs_billing_address{width:100%}#fs_billing_address tr td{padding:5px;width:50%}#fs_billing_address tr:first-of-type td{padding-top:0}#fs_billing_address span{font-weight:700}#fs_billing_address input,#fs_billing_address select{display:block;margin-top:5px;width:100%}#fs_billing_address input::-moz-placeholder,#fs_billing_address select::-moz-placeholder{color:transparent}#fs_billing_address input::placeholder,#fs_billing_address select::placeholder{color:transparent}#fs_billing_address input.fs-read-mode,#fs_billing_address select.fs-read-mode{background:none;border-color:transparent;border-bottom:1px dashed #ccc;color:#777;padding-left:0}#fs_billing_address.fs-read-mode td span{display:none}#fs_billing_address.fs-read-mode input,#fs_billing_address.fs-read-mode select{background:none;border-color:transparent;border-bottom:1px dashed #ccc;color:#777;padding-left:0}#fs_billing_address.fs-read-mode input::-moz-placeholder,#fs_billing_address.fs-read-mode select::-moz-placeholder{color:#ccc}#fs_billing_address.fs-read-mode input::placeholder,#fs_billing_address.fs-read-mode select::placeholder{color:#ccc}#fs_billing_address button{display:block;width:100%}@media screen and (max-width:639px){#fs_account .fs-header-actions{margin:0 0 12px;padding:0 15px 12px;position:static}#fs_account .fs-header-actions li{display:inline-block;float:none}#fs_account #fs_account_details,#fs_account #fs_account_details tbody,#fs_account #fs_account_details td,#fs_account #fs_account_details th,#fs_account #fs_account_details tr{display:block}#fs_account #fs_account_details tr td:first-child{text-align:left}#fs_account #fs_account_details tr td:nth-child(2){padding:0 12px}#fs_account #fs_account_details tr td:nth-child(2) code{margin:0;padding:0}#fs_account #fs_account_details tr td:nth-child(2) label{margin-left:0}#fs_account #fs_account_details tr td:nth-child(3){text-align:left}#fs_account #fs_account_details tr.fs-field-plan td:nth-child(2) .button-group{float:none;margin:12px 0}}PK!++/libraries/freemius/assets/css/admin/add-ons.cssnu[.fs-badge{background:#71ae00;border-radius:3px 0 0 3px;border-right:0;box-shadow:0 2px 1px -1px rgba(0,0,0,.3);color:#fff;font-weight:700;padding:5px 10px;position:absolute;right:0;text-transform:uppercase;top:10px}#fs_addons .fs-cards-list{list-style:none}#fs_addons .fs-cards-list .fs-card{border:1px solid #ddd;cursor:pointer;float:left;font-size:14px;height:152px;list-style:none;margin:0 0 30px 30px;padding:0;position:relative;width:310px}#fs_addons .fs-cards-list .fs-card .fs-overlay{bottom:0;left:0;position:absolute;right:0;top:0;z-index:9}#fs_addons .fs-cards-list .fs-card .fs-inner{background-color:#fff;height:100%;overflow:hidden;position:relative}#fs_addons .fs-cards-list .fs-card .fs-inner>ul{left:0;position:absolute;right:0;top:0;transition:all,.15s}#fs_addons .fs-cards-list .fs-card .fs-inner>ul>li{box-sizing:border-box;display:block;line-height:18px;list-style:none;padding:0 15px;width:100%}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-card-banner{background-repeat:repeat-x;background-size:100% 100%;display:block;height:100px;line-height:0;margin:0;padding:0;transition:all,.15s}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-card-banner .fs-badge.fs-installed-addon-badge{font-size:1.02em;line-height:1.3em}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-title{color:#000;font-weight:700;height:18px;margin:10px 0 0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-offer{font-size:.9em}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-description{background-color:#f9f9f9;border-top:1px solid #eee;color:#777;margin:0 0 10px;padding:10px 15px 100px}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-tag{background:#adff2f;box-shadow:1px 1px 1px rgba(0,0,0,.3);display:block;font-size:.9em;font-weight:700;padding:2px 10px;position:absolute;right:0;text-transform:uppercase;top:10px}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-cta .button,#fs_addons .fs-cards-list .fs-card .fs-inner .fs-cta .button-group{position:absolute;right:10px;top:112px}@media screen and (min-width:960px){#fs_addons .fs-cards-list .fs-card:hover .fs-overlay{border:2px solid #29abe1;margin-left:-1px;margin-top:-1px}#fs_addons .fs-cards-list .fs-card:hover .fs-inner ul{top:-100px}#fs_addons .fs-cards-list .fs-card:hover .fs-inner .fs-offer,#fs_addons .fs-cards-list .fs-card:hover .fs-inner .fs-title{color:#29abe1}}#TB_window,#TB_window iframe{width:821px!important}#plugin-information .fyi{width:266px!important}#plugin-information #section-holder{clear:none;margin-right:299px}#plugin-information #section-description b,#plugin-information #section-description blockquote,#plugin-information #section-description h2,#plugin-information #section-description h3,#plugin-information #section-description i,#plugin-information #section-description li,#plugin-information #section-description ol,#plugin-information #section-description p,#plugin-information #section-description ul{clear:none}#plugin-information #section-description iframe{max-width:100%}#plugin-information #section-description .fs-selling-points{border-bottom:1px solid #ddd;padding-bottom:10px}#plugin-information #section-description .fs-selling-points ul{margin:0}#plugin-information #section-description .fs-selling-points ul li{list-style:none outside none;padding:0}#plugin-information #section-description .fs-selling-points ul li i.dashicons{color:#71ae00;float:left;font-size:3em;line-height:30px;margin:0 0 0 -15px;vertical-align:middle}#plugin-information #section-description .fs-selling-points ul li h3{margin:1em 30px!important}#plugin-information #section-description .fs-screenshots:after{clear:both;content:"";display:table}#plugin-information #section-description .fs-screenshots ul{list-style:none;margin:0}#plugin-information #section-description .fs-screenshots ul li{box-sizing:content-box;float:left;height:225px;margin-bottom:20px;width:225px}#plugin-information #section-description .fs-screenshots ul li a{background-size:cover;border:1px solid;box-shadow:1px 1px 1px rgba(0,0,0,.2);display:block;height:100%;width:100%}#plugin-information #section-description .fs-screenshots ul li.odd{margin-right:20px}#plugin-information .plugin-information-pricing{border-bottom:1px solid #ddd;margin:-16px}#plugin-information .plugin-information-pricing .fs-plan h3{font-size:16px;margin-top:0;padding:20px}#plugin-information .plugin-information-pricing .fs-plan .nav-tab-wrapper{border-bottom:1px solid #ddd}#plugin-information .plugin-information-pricing .fs-plan .nav-tab-wrapper .nav-tab{cursor:pointer;font-size:.9em;padding:0 10px;position:relative}#plugin-information .plugin-information-pricing .fs-plan .nav-tab-wrapper .nav-tab label{background:#adff2f;border:1px solid #006400;bottom:100%;color:green;font-size:.9em;left:-1px;line-height:1em;padding:2px;position:absolute;right:-1px;text-align:center;text-transform:uppercase}#plugin-information .plugin-information-pricing .fs-plan .nav-tab-wrapper .nav-tab.nav-tab-active{background:#fffeec;border-bottom-color:#fffeec;cursor:default}#plugin-information .plugin-information-pricing .fs-plan.fs-single-cycle h3{background:#fffeec;color:#0073aa;margin:0;padding-bottom:0}#plugin-information .plugin-information-pricing .fs-plan.fs-single-cycle .fs-billing-frequency,#plugin-information .plugin-information-pricing .fs-plan.fs-single-cycle .nav-tab-wrapper{display:none}#plugin-information .plugin-information-pricing .fs-plan .fs-pricing-body{background:#fffeec;padding:20px}#plugin-information .plugin-information-pricing .fs-plan .button{font-size:1.1em;font-weight:700;text-align:center;text-transform:uppercase;width:100%}#plugin-information .plugin-information-pricing .fs-plan label{white-space:nowrap}#plugin-information .plugin-information-pricing .fs-plan var{font-style:normal}#plugin-information .plugin-information-pricing .fs-plan .fs-annual-discount,#plugin-information .plugin-information-pricing .fs-plan .fs-billing-frequency{background:#f3f3f3;border:1px solid #ccc;display:block;font-weight:700;margin-bottom:10px;padding:2px;text-align:center;text-transform:uppercase}#plugin-information .plugin-information-pricing .fs-plan .fs-annual-discount{background:#adff2f;color:green;text-transform:none}#plugin-information .plugin-information-pricing .fs-plan ul.fs-trial-terms{font-size:.9em}#plugin-information .plugin-information-pricing .fs-plan ul.fs-trial-terms i{float:left;margin:0 0 0 -15px}#plugin-information .plugin-information-pricing .fs-plan ul.fs-trial-terms li{margin:10px 0 0}#plugin-information #section-features .fs-features{margin:-20px -26px}#plugin-information #section-features table{border-collapse:separate;border-spacing:0;width:100%}#plugin-information #section-features table thead th{padding:10px 0}#plugin-information #section-features table thead .fs-price{color:#71ae00;display:block;font-weight:400;text-align:center}#plugin-information #section-features table tbody td{border-top:1px solid #ccc;color:#71ae00;padding:10px 0;text-align:center;width:100px}#plugin-information #section-features table tbody td:first-child{color:inherit;padding-left:26px;text-align:left;width:auto}#plugin-information #section-features table tbody tr.fs-odd td{background:#fefefe}#plugin-information #section-features .dashicons-yes{font-size:30px;height:30px;width:30px}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .button-group .button,#plugin-information .fs-dropdown .button-group .button{position:relative;right:0;top:0;width:auto}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .button-group .button:focus,#plugin-information .fs-dropdown .button-group .button:focus{z-index:10}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .button-group .fs-dropdown-arrow,#plugin-information .fs-dropdown .button-group .fs-dropdown-arrow{border-left:4px solid transparent;border-right:4px solid transparent;border-top:6px solid #fff;position:relative;top:12px}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown.active:not(.up) .button:not(.fs-dropdown-arrow-button),#plugin-information .fs-dropdown.active:not(.up) .button:not(.fs-dropdown-arrow-button){border-bottom-left-radius:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown.active:not(.up) .fs-dropdown-arrow-button,#plugin-information .fs-dropdown.active:not(.up) .fs-dropdown-arrow-button{border-bottom-right-radius:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown.active.up .button:not(.fs-dropdown-arrow-button),#plugin-information .fs-dropdown.active.up .button:not(.fs-dropdown-arrow-button){border-top-left-radius:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown.active.up .fs-dropdown-arrow-button,#plugin-information .fs-dropdown.active.up .fs-dropdown-arrow-button{border-top-right-radius:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .fs-dropdown-list,#plugin-information .fs-dropdown .fs-dropdown-list{background-color:#fff;border:1px solid #bfbfbf;box-shadow:0 2px 4px -1px rgba(0,0,0,.2),0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12);margin-left:auto;padding:3px 0;position:absolute;right:-1px;text-align:left;top:100%;width:230px;z-index:1}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .fs-dropdown-list li,#plugin-information .fs-dropdown .fs-dropdown-list li{margin:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .fs-dropdown-list li a,#plugin-information .fs-dropdown .fs-dropdown-list li a{display:block;padding:5px 10px;text-decoration:none;text-shadow:none}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .fs-dropdown-list li:hover,#plugin-information .fs-dropdown .fs-dropdown-list li:hover{background-color:#0074a3;color:#fff}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .fs-dropdown-list li:hover a,#plugin-information .fs-dropdown .fs-dropdown-list li:hover a{color:#fff}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown:not(.up) .fs-dropdown-list,#plugin-information .fs-dropdown:not(.up) .fs-dropdown-list{border-radius:3px 0 3px 3px}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown.up .fs-dropdown-list,#plugin-information .fs-dropdown.up .fs-dropdown-list{border-radius:3px 3px 0 3px}#plugin-information .fs-dropdown .button-group{width:100%}#plugin-information .fs-dropdown .button-group .button{float:none;font-size:14px;font-weight:400;text-transform:none}#plugin-information .fs-dropdown .fs-dropdown-list{margin-top:1px}#plugin-information .fs-dropdown.up .fs-dropdown-list{bottom:100%;margin-bottom:2px;top:auto}#plugin-information.wp-core-ui .fs-pricing-body .fs-dropdown .button-group{display:table;text-align:center}#plugin-information.wp-core-ui .fs-pricing-body .fs-dropdown .button-group .button{display:table-cell}#plugin-information.wp-core-ui .fs-pricing-body .fs-dropdown .button-group .button:not(.fs-dropdown-arrow-button){left:1px;width:100%}#plugin-information-footer .fs-dropdown,#plugin-information-footer>.button{position:relative;top:3px}#plugin-information-footer .fs-dropdown.left,#plugin-information-footer>.button.left{float:left}#plugin-information-footer .fs-dropdown,#plugin-information-footer>.right{float:right}@media screen and (max-width:961px){#fs_addons .fs-cards-list .fs-card{height:265px}}PK!P.8libraries/freemius/assets/css/admin/clone-resolution.cssnu[.fs-notice[data-id^=clone_resolution_options_notice]{color:inherit!important;padding:0}.fs-notice[data-id^=clone_resolution_options_notice] .fs-notice-body{margin-bottom:0;padding:0}.fs-notice[data-id^=clone_resolution_options_notice] .fs-notice-header{padding:5px 10px}.fs-notice[data-id^=clone_resolution_options_notice] ol{margin-bottom:0;margin-top:0}.fs-notice[data-id^=clone_resolution_options_notice] .fs-clone-resolution-options-container{display:flex;flex-direction:row;padding:0 10px 10px}@media(max-width:750px){.fs-notice[data-id^=clone_resolution_options_notice] .fs-clone-resolution-options-container{flex-direction:column}}.fs-notice[data-id^=clone_resolution_options_notice] .fs-clone-resolution-option{border:1px solid #ccc;flex:auto;margin:5px;padding:10px 10px 15px}.fs-notice[data-id^=clone_resolution_options_notice] .fs-clone-resolution-option:first-child{margin-left:0}.fs-notice[data-id^=clone_resolution_options_notice] .fs-clone-resolution-option:last-child{margin-right:0}.fs-notice[data-id^=clone_resolution_options_notice] .fs-clone-resolution-option strong{font-size:1.2em;line-height:1.5em;padding:2px}.fs-notice[data-id^=clone_resolution_options_notice] a{text-decoration:none}.fs-notice[data-id^=clone_resolution_options_notice] .button{margin-right:10px}.rtl .fs-notice[data-id^=clone_resolution_options_notice] .button{margin-left:10px;margin-right:0}.fs-notice[data-id^=clone_resolution_options_notice] .fs-clone-documentation-container{padding:0 10px 15px}.fs-notice[data-id=temporary_duplicate_notice] #fs_clone_resolution_error_message{background:#fee;border:1px solid #d3135a;color:#d3135a;padding:10px}.fs-notice[data-id=temporary_duplicate_notice] ol{margin-top:0}.fs-notice[data-id=temporary_duplicate_notice] a{position:relative}.fs-notice[data-id=temporary_duplicate_notice] a:focus{box-shadow:none}.fs-notice[data-id=temporary_duplicate_notice] a.disabled{color:gray}.fs-notice[data-id=temporary_duplicate_notice] a .fs-ajax-spinner{bottom:0;left:8px;margin-left:100%;position:absolute;right:0;top:-1px}PK!m2)2)/libraries/freemius/assets/css/admin/connect.cssnu[#fs_connect{margin:60px auto 20px;width:484px}#fs_connect a{color:inherit}#fs_connect a:not(.button){text-decoration:underline}#fs_connect .fs-box-container{background:#f0f0f1;border-radius:3px;box-shadow:0 1px 2px rgba(0,0,0,.3);overflow:hidden;padding-top:40px}@media screen and (max-width:483px){#fs_connect{margin:30px 0 0 -10px;width:auto}#fs_connect .fs-box-container{box-shadow:none}}#fs_connect .fs-content{background:#fff;padding:30px 20px}#fs_connect .fs-content .fs-error{background:snow;border:1px solid #d3135a;box-shadow:0 1px 1px 0 rgba(0,0,0,.1);color:#d3135a;margin-bottom:10px;padding:5px;text-align:center}#fs_connect .fs-content h2{line-height:1.5em}#fs_connect .fs-content p{font-size:1.2em;margin:0;padding:0}#fs_connect .fs-license-key-container{margin:10px auto 0;position:relative;width:280px}#fs_connect .fs-license-key-container input{width:100%}#fs_connect .fs-license-key-container .dashicons{position:absolute;right:5px;top:5px}#fs_connect.require-license-key .fs-content{padding-bottom:10px}#fs_connect.require-license-key .fs-actions{border-top:none}#fs_connect.require-license-key .fs-sites-list-container td{cursor:pointer}#fs_connect #delegate_to_site_admins{border-bottom:1px dashed;float:right;font-weight:700;height:26px;line-height:37px;margin-right:15px;text-decoration:none;vertical-align:middle}#fs_connect #delegate_to_site_admins.rtl{margin-left:15px;margin-right:0}#fs_connect .fs-actions{background:#fff;border-color:#f1f1f1;border-style:solid;border-width:1px 0;padding:10px 20px}#fs_connect .fs-actions .button{font-size:16px;height:37px;line-height:35px;margin-bottom:0;padding:0 10px 1px}#fs_connect .fs-actions .button .dashicons{font-size:37px;margin-left:-8px;margin-right:12px}#fs_connect .fs-actions .button.button-primary{padding-left:15px;padding-right:15px}#fs_connect .fs-actions .button.button-primary:after{content:" ➜"}#fs_connect .fs-actions .button.button-primary.fs-loading:after{content:""}#fs_connect .fs-actions .button.button-secondary{float:right}#fs_connect.fs-anonymous-disabled .fs-actions .button.button-primary{width:100%}#fs_connect .fs-permissions{background:#fff;padding:10px 20px;transition:background .5s ease}#fs_connect .fs-permissions .fs-license-sync-disclaimer{margin-top:0;text-align:center}#fs_connect .fs-permissions>.fs-trigger{display:block;font-size:.9em;text-align:center;text-decoration:none}#fs_connect .fs-permissions>.fs-trigger .fs-arrow:after{content:"→";display:inline-block;width:20px}#fs_connect .fs-permissions.fs-open>.fs-trigger .fs-arrow:after{content:"↓"!important}#fs_connect .fs-permissions ul li{padding-left:0;padding-right:0}@media screen and (max-width:483px){#fs_connect .fs-permissions ul{height:auto;margin:20px}}#fs_connect .fs-freemium-licensing{background:#777;color:#fff;padding:8px}#fs_connect .fs-freemium-licensing p{display:block;margin:0;padding:0;text-align:center}#fs_connect .fs-freemium-licensing a{color:inherit;text-decoration:underline}#fs_connect .fs-header{height:0;line-height:0;padding:0;position:relative}#fs_connect .fs-header .fs-connect-logo,#fs_connect .fs-header .fs-site-icon{border-radius:50%;position:absolute;top:-8px}#fs_connect .fs-header .fs-site-icon{left:152px}#fs_connect .fs-header .fs-connect-logo{right:152px}#fs_connect .fs-header .fs-site-icon,#fs_connect .fs-header img,#fs_connect .fs-header object{border-radius:50%;height:50px;width:50px}#fs_connect .fs-header .fs-plugin-icon{border-radius:50%;left:50%;margin-left:-44px;overflow:hidden;position:absolute;top:-23px;z-index:1}#fs_connect .fs-header .fs-plugin-icon,#fs_connect .fs-header .fs-plugin-icon img{height:80px;width:80px}#fs_connect .fs-header .dashicons-wordpress-alt{background:#01749a;border-radius:50%;color:#fff;font-size:40px;height:40px;padding:5px;width:40px}#fs_connect .fs-header .dashicons-plus{color:#bbb;font-size:30px;margin-top:-10px;position:absolute;top:50%}#fs_connect .fs-header .dashicons-plus.fs-first{left:28%}#fs_connect .fs-header .dashicons-plus.fs-second{left:65%}#fs_connect .fs-header .fs-connect-logo,#fs_connect .fs-header .fs-plugin-icon,#fs_connect .fs-header .fs-site-icon{background:#fff;border:1px solid #efefef;padding:3px}#fs_connect .fs-terms{font-size:.85em;padding:10px 5px;text-align:center}#fs_connect .fs-terms,#fs_connect .fs-terms a{color:#999}#fs_connect .fs-terms a{text-decoration:none}.fs-multisite-options-container{border:1px solid #ccc;margin-top:20px;padding:5px}.fs-multisite-options-container a{text-decoration:none}.fs-multisite-options-container a:focus{box-shadow:none}.fs-multisite-options-container a.selected{font-weight:700}.fs-multisite-options-container.fs-apply-on-all-sites{border:0;padding:0}.fs-multisite-options-container.fs-apply-on-all-sites .fs-all-sites-options{border-spacing:0}.fs-multisite-options-container.fs-apply-on-all-sites .fs-all-sites-options td:not(:first-child){display:none}.fs-multisite-options-container .fs-sites-list-container{display:none;overflow:auto}.fs-multisite-options-container .fs-sites-list-container table td{border-top:1px solid #ccc;padding:4px 2px}#fs_marketing_optin{border:1px solid #ccc;display:none;line-height:1.5em;margin-top:10px;padding:10px}#fs_marketing_optin .fs-message{display:block;font-size:1.05em;font-weight:600;margin-bottom:5px}#fs_marketing_optin.error{background:#fee;border:1px solid #d3135a}#fs_marketing_optin.error .fs-message{color:#d3135a}#fs_marketing_optin .fs-input-container{margin-top:5px}#fs_marketing_optin .fs-input-container label{display:block;margin-top:5px}#fs_marketing_optin .fs-input-container label input{float:left;margin:1px 0 0}#fs_marketing_optin .fs-input-container label:first-child{display:block;margin-bottom:2px}#fs_marketing_optin .fs-input-label{display:block;margin-left:20px}#fs_marketing_optin .fs-input-label .underlined{text-decoration:underline}.rtl #fs_marketing_optin .fs-input-container label input{float:right}.rtl #fs_marketing_optin .fs-input-label{margin-left:0;margin-right:20px}.rtl #fs_connect{border-radius:3px}.rtl #fs_connect .fs-actions{background:#c0c7ca;padding:10px 20px}.rtl #fs_connect .fs-actions .button .dashicons{font-size:37px;margin-left:-8px;margin-right:12px}.rtl #fs_connect .fs-actions .button.button-primary:after{content:" »"}.rtl #fs_connect .fs-actions .button.button-primary.fs-loading:after{content:""}.rtl #fs_connect .fs-actions .button.button-secondary{float:left}.rtl #fs_connect .fs-header .fs-site-icon{left:auto;right:20px}.rtl #fs_connect .fs-header .fs-connect-logo{left:20px;right:auto}.rtl #fs_connect .fs-permissions>.fs-trigger .fs-arrow:after{content:"←"}#fs_theme_connect_wrapper{background:rgba(0,0,0,.75);height:100%;overflow-y:auto;position:fixed;text-align:center;top:0;width:100%;z-index:99990}#fs_theme_connect_wrapper:before{content:"";display:inline-block;height:100%;vertical-align:middle}#fs_theme_connect_wrapper>button.close{background-color:transparent;border:0;color:#fff;cursor:pointer;height:40px;position:absolute;right:0;top:32px;width:40px}#fs_theme_connect_wrapper #fs_connect{display:inline-block;margin-bottom:20px;margin-top:0;text-align:left;top:0;vertical-align:middle}#fs_theme_connect_wrapper #fs_connect .fs-terms,#fs_theme_connect_wrapper #fs_connect .fs-terms a{color:#c5c5c5}.wp-pointer-content #fs_connect{box-shadow:none;margin:0}.fs-opt-in-pointer .wp-pointer-content{padding:0}.fs-opt-in-pointer.wp-pointer-top .wp-pointer-arrow{border-bottom-color:#dfdfdf}.fs-opt-in-pointer.wp-pointer-top .wp-pointer-arrow-inner{border-bottom-color:#fafafa}.fs-opt-in-pointer.wp-pointer-bottom .wp-pointer-arrow{border-top-color:#dfdfdf}.fs-opt-in-pointer.wp-pointer-bottom .wp-pointer-arrow-inner{border-top-color:#fafafa}.fs-opt-in-pointer.wp-pointer-left .wp-pointer-arrow{border-right-color:#dfdfdf}.fs-opt-in-pointer.wp-pointer-left .wp-pointer-arrow-inner{border-right-color:#fafafa}.fs-opt-in-pointer.wp-pointer-right .wp-pointer-arrow{border-left-color:#dfdfdf}.fs-opt-in-pointer.wp-pointer-right .wp-pointer-arrow-inner{border-left-color:#fafafa}#license_issues_link{display:block;font-size:.9em;margin-top:10px;text-align:center}.fs-tooltip-trigger{position:relative}.fs-tooltip-trigger:not(a){cursor:help}.fs-tooltip-trigger .dashicons{float:none!important}.fs-tooltip-trigger .fs-tooltip{background:rgba(0,0,0,.8);border-radius:5px;bottom:100%;box-shadow:1px 1px 1px rgba(0,0,0,.2);color:#fff!important;font-family:arial,serif;font-size:12px;font-weight:700;left:-17px;line-height:1.3em;margin-bottom:5px;opacity:0;padding:10px;position:absolute;right:0;text-align:left;text-transform:none!important;transition:opacity .3s ease-in-out;visibility:hidden;z-index:999999}.rtl .fs-tooltip-trigger .fs-tooltip{left:auto;right:-17px;text-align:right}.fs-tooltip-trigger .fs-tooltip:after{border-color:rgba(0,0,0,.8) transparent transparent;border-style:solid;border-width:5px 5px 0;content:" ";display:block;height:0;left:21px;position:absolute;top:100%;width:0}.rtl .fs-tooltip-trigger .fs-tooltip:after{left:auto;right:21px}.fs-tooltip-trigger:hover .fs-tooltip{opacity:1;visibility:visible}.fs-permissions .fs-permission.fs-disabled,.fs-permissions .fs-permission.fs-disabled .fs-permission-description span{color:#aaa}.fs-permissions .fs-permission .fs-switch-feedback{position:absolute;right:15px;top:52px}.fs-permissions ul{height:0;margin:0;overflow:hidden}.fs-permissions ul li{margin:0;padding:17px 15px;position:relative}.fs-permissions ul li>i.dashicons{float:left;font-size:30px;height:30px;padding:5px;width:30px}.fs-permissions ul li .fs-switch{float:right}.fs-permissions ul li .fs-permission-description{margin-left:55px}.fs-permissions ul li .fs-permission-description span{color:#23282d;font-size:14px;font-weight:500}.fs-permissions ul li .fs-permission-description .fs-tooltip{font-size:13px;font-weight:700}.fs-permissions ul li .fs-permission-description .fs-tooltip-trigger .dashicons{margin:-1px 2px 0}.fs-permissions ul li .fs-permission-description p{margin:2px 0 0}.fs-permissions.fs-open{background:#fff}.fs-permissions.fs-open ul{height:auto;margin:20px 0 10px;overflow:initial}.fs-permissions .fs-switch-feedback .fs-ajax-spinner{margin-right:10px}.fs-permissions .fs-switch-feedback.success{color:#71ae00}.rtl .fs-permissions .fs-switch-feedback{left:15px;right:auto}.rtl .fs-permissions .fs-switch-feedback .fs-ajax-spinner{margin-left:10px;margin-right:0}.rtl .fs-permissions ul li .fs-permission-description{margin-left:0;margin-right:55px}.rtl .fs-permissions ul li .fs-switch{float:left}.rtl .fs-permissions ul li i.dashicons{float:right}PK!E%v.libraries/freemius/assets/css/admin/common.cssnu[.fs-badge{background:#71ae00;border-radius:3px 0 0 3px;border-right:0;box-shadow:0 2px 1px -1px rgba(0,0,0,.3);color:#fff;font-weight:700;padding:5px 10px;position:absolute;right:0;text-transform:uppercase;top:10px}.theme-browser .theme .fs-premium-theme-badge-container{position:absolute;right:0;top:0}.theme-browser .theme .fs-premium-theme-badge-container .fs-badge{margin-top:10px;position:relative;text-align:center;top:0}.theme-browser .theme .fs-premium-theme-badge-container .fs-badge.fs-premium-theme-badge{font-size:1.1em}.theme-browser .theme .fs-premium-theme-badge-container .fs-badge.fs-beta-theme-badge{background:#00a0d2}.fs-switch{background:#ececec;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);box-shadow:0 0 4px rgba(0,0,0,.1),inset 0 1px 3px 0 rgba(0,0,0,.1);color:#ccc;cursor:pointer;display:inline-block;height:18px;padding:6px 6px 5px;position:relative;text-shadow:0 1px 1px hsla(0,0%,100%,.8)}.fs-switch span{display:inline-block;text-transform:uppercase;width:35px}.fs-switch .fs-toggle{background-color:#fff;background-image:linear-gradient(180deg,#ececec,#fff);border:1px solid rgba(0,0,0,.3);border-radius:4px;box-shadow:inset 0 1px 0 0 hsla(0,0%,100%,.5);height:25px;position:absolute;top:1px;transition:.4s cubic-bezier(.54,1.6,.5,1);width:37px;z-index:999}.fs-switch.fs-off .fs-toggle{left:2%}.fs-switch.fs-on .fs-toggle{left:54%}.fs-switch.fs-round{border-radius:24px;padding:4px 25px;top:8px}.fs-switch.fs-round .fs-toggle{border-radius:24px;height:24px;top:0;width:24px}.fs-switch.fs-round.fs-off .fs-toggle{left:-1px}.fs-switch.fs-round.fs-on{background:#0085ba}.fs-switch.fs-round.fs-on .fs-toggle{left:25px}.fs-switch.fs-small.fs-round{padding:1px 19px}.fs-switch.fs-small.fs-round .fs-toggle{border-radius:18px;height:18px;top:0;width:18px}.fs-switch.fs-small.fs-round.fs-on .fs-toggle{left:19px}body.fs-loading,body.fs-loading *{cursor:wait!important}#fs_frame{font-size:0;line-height:0}.fs-full-size-wrapper{margin:40px 0 -65px -20px}@media(max-width:600px){.fs-full-size-wrapper{margin:0 0 -65px -10px}}.fs-notice{position:relative}.fs-notice.fs-has-title{margin-bottom:30px!important}.fs-notice.success{color:green}.fs-notice.promotion{background-color:#f2fcff!important;border-color:#00a0d2!important}.fs-notice .fs-notice-body{margin:.5em 0;padding:2px}.fs-notice .fs-notice-body .fs-trial-message-container{align-items:center;display:flex;flex-wrap:wrap;gap:5px}.fs-notice .fs-close{color:#aaa;cursor:pointer;float:right}.fs-notice .fs-close:hover{color:#666}.fs-notice .fs-close>*{display:inline-block;margin-top:7px}.fs-notice label.fs-plugin-title{background:rgba(0,0,0,.3);border-radius:0 0 3px 3px;bottom:auto;color:#fff;cursor:auto;font-size:12px;font-weight:700;left:10px;padding:2px 10px;position:absolute;right:auto;top:100%}div.fs-notice.promotion,div.fs-notice.success,div.fs-notice.updated{display:block!important}#fs_connect .fs-error .fs-api-request-error-details,#fs_connect .fs-error .fs-api-request-error-show-details-link,#fs_connect .fs-error ol,.fs-modal .notice-error .fs-api-request-error-details,.fs-modal .notice-error .fs-api-request-error-show-details-link,.fs-modal .notice-error ol,.fs-notice.error .fs-api-request-error-details,.fs-notice.error .fs-api-request-error-show-details-link,.fs-notice.error ol{text-align:left}#fs_connect .fs-error ol,.fs-modal .notice-error ol,.fs-notice.error ol{list-style-type:disc}#fs_connect .fs-error .fs-api-request-error-show-details-link,.fs-modal .notice-error .fs-api-request-error-show-details-link,.fs-notice.error .fs-api-request-error-show-details-link{box-shadow:none;color:#2271b1;text-decoration:none}#fs_connect .fs-error .fs-api-request-error-details,.fs-modal .notice-error .fs-api-request-error-details,.fs-notice.error .fs-api-request-error-details{border:1px solid #ccc;max-height:150px;overflow:auto;padding:5px}.rtl .fs-notice .fs-close{float:left}.fs-secure-notice{background:#ebfdeb;box-shadow:0 2px 2px rgba(6,113,6,.3);color:green;left:160px;opacity:.95;padding:10px 20px;position:fixed;right:0;top:32px;z-index:9989}.fs-secure-notice:hover{opacity:1}.fs-secure-notice a.fs-security-proof{color:green;text-decoration:none}@media screen and (max-width:960px){.fs-secure-notice{left:36px}}@media screen and (max-width:600px){.fs-secure-notice{display:none}}@media screen and (max-width:1250px){#fs_promo_tab{display:none}}@media screen and (max-width:782px){.fs-secure-notice{left:0;text-align:center;top:46px}}span.fs-submenu-item.fs-sub:before{content:"↳";padding:0 5px}.rtl span.fs-submenu-item.fs-sub:before{content:"↲"}.fs-submenu-item.pricing.upgrade-mode{color:#adff2f}.fs-submenu-item.pricing.trial-mode{color:#83e2ff}#adminmenu .update-plugins.fs-trial{background-color:#00b9eb}.fs-ajax-spinner{background:url(/wp-admin/images/wpspin_light-2x.gif);background-size:contain;border:0;display:inline-block;height:20px;margin-bottom:-2px;margin-right:5px;vertical-align:sub;width:20px}.wrap.fs-section h2{text-align:left}.plugins p.fs-upgrade-notice{background-color:#d54e21;border:0;color:#f9f9f9;margin-top:10px;padding:10px}PK!̃((-libraries/freemius/assets/css/admin/debug.cssnu[label.fs-tag,span.fs-tag{background:#ffba00;border-radius:3px;color:#fff;display:inline-block;font-size:11px;line-height:11px;padding:5px;vertical-align:baseline}label.fs-tag.fs-warn,span.fs-tag.fs-warn{background:#ffba00}label.fs-tag.fs-info,span.fs-tag.fs-info{background:#00a0d2}label.fs-tag.fs-success,span.fs-tag.fs-success{background:#46b450}label.fs-tag.fs-error,span.fs-tag.fs-error{background:#dc3232}.fs-switch-label{font-size:20px;line-height:31px;margin:0 5px}#fs_log_book table{font-family:Consolas,Monaco,monospace;font-size:12px}#fs_log_book table th{color:#ccc}#fs_log_book table tr{background:#232525}#fs_log_book table tr.alternate{background:#2b2b2b}#fs_log_book table tr td.fs-col--logger{color:#5a7435}#fs_log_book table tr td.fs-col--type{color:#ffc861}#fs_log_book table tr td.fs-col--function{color:#a7b7b1;font-weight:700}#fs_log_book table tr td.fs-col--message,#fs_log_book table tr td.fs-col--message a{color:#9a73ac!important}#fs_log_book table tr td.fs-col--file{color:#d07922}#fs_log_book table tr td.fs-col--timestamp{color:#6596be}PK!fY/libraries/freemius/assets/css/admin/plugins.cssnu[label.fs-tag,span.fs-tag{background:#ffba00;border-radius:3px;color:#fff;display:inline-block;font-size:11px;line-height:11px;padding:5px;vertical-align:baseline}label.fs-tag.fs-warn,span.fs-tag.fs-warn{background:#ffba00}label.fs-tag.fs-info,span.fs-tag.fs-info{background:#00a0d2}label.fs-tag.fs-success,span.fs-tag.fs-success{background:#46b450}label.fs-tag.fs-error,span.fs-tag.fs-error{background:#dc3232}.wp-list-table.plugins .plugin-title span.fs-tag{display:inline-block;line-height:10px;margin-left:5px}PK! 40.libraries/freemius/assets/css/admin/optout.cssnu[.fs-tooltip-trigger{position:relative}.fs-tooltip-trigger:not(a){cursor:help}.fs-tooltip-trigger .dashicons{float:none!important}.fs-tooltip-trigger .fs-tooltip{background:rgba(0,0,0,.8);border-radius:5px;bottom:100%;box-shadow:1px 1px 1px rgba(0,0,0,.2);color:#fff!important;font-family:arial,serif;font-size:12px;font-weight:700;left:-17px;line-height:1.3em;margin-bottom:5px;opacity:0;padding:10px;position:absolute;right:0;text-align:left;text-transform:none!important;transition:opacity .3s ease-in-out;visibility:hidden;z-index:999999}.rtl .fs-tooltip-trigger .fs-tooltip{left:auto;right:-17px;text-align:right}.fs-tooltip-trigger .fs-tooltip:after{border-color:rgba(0,0,0,.8) transparent transparent;border-style:solid;border-width:5px 5px 0;content:" ";display:block;height:0;left:21px;position:absolute;top:100%;width:0}.rtl .fs-tooltip-trigger .fs-tooltip:after{left:auto;right:21px}.fs-tooltip-trigger:hover .fs-tooltip{opacity:1;visibility:visible}.fs-permissions .fs-permission.fs-disabled,.fs-permissions .fs-permission.fs-disabled .fs-permission-description span{color:#aaa}.fs-permissions .fs-permission .fs-switch-feedback{position:absolute;right:15px;top:52px}.fs-permissions ul{height:0;margin:0;overflow:hidden}.fs-permissions ul li{margin:0;padding:17px 15px;position:relative}.fs-permissions ul li>i.dashicons{float:left;font-size:30px;height:30px;padding:5px;width:30px}.fs-permissions ul li .fs-switch{float:right}.fs-permissions ul li .fs-permission-description{margin-left:55px}.fs-permissions ul li .fs-permission-description span{color:#23282d;font-size:14px;font-weight:500}.fs-permissions ul li .fs-permission-description .fs-tooltip{font-size:13px;font-weight:700}.fs-permissions ul li .fs-permission-description .fs-tooltip-trigger .dashicons{margin:-1px 2px 0}.fs-permissions ul li .fs-permission-description p{margin:2px 0 0}.fs-permissions.fs-open{background:#fff}.fs-permissions.fs-open ul{height:auto;margin:20px 0 10px;overflow:initial}.fs-permissions .fs-switch-feedback .fs-ajax-spinner{margin-right:10px}.fs-permissions .fs-switch-feedback.success{color:#71ae00}.rtl .fs-permissions .fs-switch-feedback{left:15px;right:auto}.rtl .fs-permissions .fs-switch-feedback .fs-ajax-spinner{margin-left:10px;margin-right:0}.rtl .fs-permissions ul li .fs-permission-description{margin-left:0;margin-right:55px}.rtl .fs-permissions ul li .fs-switch{float:left}.rtl .fs-permissions ul li i.dashicons{float:right}.fs-modal-opt-out .fs-modal-footer .fs-opt-out-button{line-height:30px;margin-right:10px}.fs-modal-opt-out .fs-permissions{margin-top:0!important}.fs-modal-opt-out .fs-permissions .fs-permissions-section--header .fs-group-opt-out-button{float:right;line-height:1.1em}.fs-modal-opt-out .fs-permissions .fs-permissions-section--header .fs-switch-feedback{float:right;line-height:1.1em;margin-right:10px}.fs-modal-opt-out .fs-permissions .fs-permissions-section--header .fs-switch-feedback .fs-ajax-spinner{margin:-2px 0 0}.fs-modal-opt-out .fs-permissions .fs-permissions-section--header-title{display:block;font-size:1.1em;font-weight:600;line-height:1.1em;margin:.5em 0;text-transform:uppercase}.fs-modal-opt-out .fs-permissions .fs-permissions-section--desc{margin-top:0}.fs-modal-opt-out .fs-permissions hr{border:0;border-top:1px solid #eee;margin:25px 0 20px}.fs-modal-opt-out .fs-permissions ul{border:1px solid #c3c4c7;border-radius:3px;box-shadow:0 1px 1px rgba(0,0,0,.04);margin:10px 0 0}.fs-modal-opt-out .fs-permissions ul li{border-bottom:1px solid #d7dde1;border-left:4px solid #72aee6}.rtl .fs-modal-opt-out .fs-permissions ul li{border-left:none;border-right:4px solid #72aee6}.fs-modal-opt-out .fs-permissions ul li.fs-disabled{border-left-color:rgba(114,174,230,0)}.fs-modal-opt-out .fs-permissions ul li:last-child{border-bottom:none}PK!p2WW-libraries/freemius/assets/css/admin/index.phpnu[h3>strong{font-size:1.3em}}.fs-modal.active,.fs-modal.active:before{display:block}.fs-modal.active .fs-modal-dialog{top:10%}.fs-modal.fs-success .fs-modal-header{border-bottom-color:#46b450}.fs-modal.fs-success .fs-modal-body{background-color:#f7fff7}.fs-modal.fs-warn .fs-modal-header{border-bottom-color:#ffb900}.fs-modal.fs-warn .fs-modal-body{background-color:#fff8e5}.fs-modal.fs-error .fs-modal-header{border-bottom-color:#dc3232}.fs-modal.fs-error .fs-modal-body{background-color:#ffeaea}.fs-modal .fs-modal-body,.fs-modal .fs-modal-footer{background:#fefefe;border:0;padding:20px}.fs-modal .fs-modal-header{background:#fbfbfb;border-bottom:1px solid #eee;margin-bottom:-10px;padding:15px 20px;position:relative}.fs-modal .fs-modal-header h4{color:#cacaca;font-size:1.2em;font-weight:700;letter-spacing:.6px;margin:0;padding:0;text-shadow:1px 1px 1px #fff;text-transform:uppercase;-webkit-font-smoothing:antialiased}.fs-modal .fs-modal-header .fs-close{border-radius:20px;color:#bbb;cursor:pointer;padding:3px;position:absolute;right:10px;top:12px;transition:all .2s ease-in-out}.fs-modal .fs-modal-header .fs-close:hover{background:#aaa;color:#fff}.fs-modal .fs-modal-header .fs-close .dashicons,.fs-modal .fs-modal-header .fs-close:hover .dashicons{text-decoration:none}.fs-modal .fs-modal-body{border-bottom:0}.fs-modal .fs-modal-body p{font-size:14px}.fs-modal .fs-modal-body h2{font-size:20px;line-height:1.5em}.fs-modal .fs-modal-body>div{margin-top:10px}.fs-modal .fs-modal-body>div h2{font-size:20px;font-weight:700;margin-top:0}.fs-modal .fs-modal-footer{border-top:1px solid #eee;text-align:right}.fs-modal .fs-modal-footer>.button{margin:0 7px}.fs-modal .fs-modal-footer>.button:last-of-type{margin:0}.fs-modal .fs-modal-panel>.notice.inline{display:none;margin:0}.fs-modal .fs-modal-panel:not(.active){display:none}.rtl .fs-modal .fs-modal-header .fs-close{left:20px;right:auto}.rtl .fs-modal .fs-modal-footer{text-align:left}body.has-fs-modal{overflow:hidden}.fs-modal.fs-modal-deactivation-feedback .internal-message,.fs-modal.fs-modal-deactivation-feedback .reason-input{margin:3px 0 3px 22px}.fs-modal.fs-modal-deactivation-feedback .internal-message input,.fs-modal.fs-modal-deactivation-feedback .internal-message textarea,.fs-modal.fs-modal-deactivation-feedback .reason-input input,.fs-modal.fs-modal-deactivation-feedback .reason-input textarea{width:100%}.fs-modal.fs-modal-deactivation-feedback li.reason.has-internal-message .internal-message{border:1px solid #ccc;display:none;padding:7px}@media(max-width:650px){.fs-modal.fs-modal-deactivation-feedback li.reason li.reason{margin-bottom:10px}.fs-modal.fs-modal-deactivation-feedback li.reason li.reason .internal-message,.fs-modal.fs-modal-deactivation-feedback li.reason li.reason .reason-input{margin-left:29px}.fs-modal.fs-modal-deactivation-feedback li.reason li.reason label{display:table}.fs-modal.fs-modal-deactivation-feedback li.reason li.reason label>span{display:table-cell;font-size:1.3em}}.fs-modal.fs-modal-deactivation-feedback .anonymous-feedback-label,.fs-modal.fs-modal-deactivation-feedback .feedback-from-snooze-label{float:left;line-height:30px}.rtl .fs-modal.fs-modal-deactivation-feedback .anonymous-feedback-label,.rtl .fs-modal.fs-modal-deactivation-feedback .feedback-from-snooze-label{float:right}.fs-modal.fs-modal-deactivation-feedback .fs-modal-panel{margin-top:0!important}.fs-modal.fs-modal-deactivation-feedback .fs-modal-panel h3{font-size:16px;line-height:1.5em;margin-bottom:0;margin-top:10px}#the-list .deactivate>.fs-slug{display:none}.fs-modal.fs-modal-subscription-cancellation .fs-price-increase-warning{color:red;font-weight:700;margin-bottom:0;padding:0 25px}.fs-modal.fs-modal-subscription-cancellation ul.subscription-actions label input{float:left;position:relative;top:5px}.rtl .fs-modal.fs-modal-subscription-cancellation ul.subscription-actions label input{float:right}.fs-modal.fs-modal-subscription-cancellation ul.subscription-actions label span{display:block;margin-left:24px}.rtl .fs-modal.fs-modal-subscription-cancellation ul.subscription-actions label span{margin-left:0;margin-right:24px}.fs-license-options-container table,.fs-license-options-container table .fs-available-license-key,.fs-license-options-container table select,.fs-modal.fs-modal-license-activation .fs-modal-body input.fs-license-key{width:100%}.fs-license-options-container table td:first-child{width:1%}.fs-license-options-container table .fs-other-license-key-container label{float:left;margin-right:5px;position:relative;top:6px}.fs-license-options-container table .fs-other-license-key-container div{display:block;height:30px;overflow:hidden;position:relative;top:2px;width:auto}.fs-license-options-container table .fs-other-license-key-container div input{margin:0}.fs-sites-list-container td{cursor:pointer}.fs-modal.fs-modal-user-change .fs-modal-body input#fs_other_email_address{width:100%}.fs-user-change-options-container table{border-collapse:collapse;width:100%}.fs-user-change-options-container table tr{display:block;margin-bottom:2px}.fs-user-change-options-container table .fs-email-address-container td{display:inline-block}.fs-user-change-options-container table .fs-email-address-container input[type=radio]{margin-bottom:0;margin-top:0}.fs-user-change-options-container table .fs-other-email-address-container{width:100%}.fs-user-change-options-container table .fs-other-email-address-container>div{display:table;width:100%}.fs-user-change-options-container table .fs-other-email-address-container>div label,.fs-user-change-options-container table .fs-other-email-address-container>div>div{display:table-cell}.fs-user-change-options-container table .fs-other-email-address-container>div label{padding-left:3px;padding-right:3px;width:1%}.fs-user-change-options-container table .fs-other-email-address-container>div>div{width:auto}.fs-modal.fs-modal-developer-license-debug-mode .fs-modal-body input.fs-license-or-user-key,.fs-user-change-options-container table .fs-other-email-address-container>div>div input{width:100%}.fs-multisite-options-container{border:1px solid #ccc;margin-top:20px;padding:5px}.fs-multisite-options-container a{text-decoration:none}.fs-multisite-options-container a:focus{box-shadow:none}.fs-multisite-options-container a.selected{font-weight:700}.fs-multisite-options-container.fs-apply-on-all-sites{border:0;padding:0}.fs-multisite-options-container.fs-apply-on-all-sites .fs-all-sites-options{border-spacing:0}.fs-multisite-options-container.fs-apply-on-all-sites .fs-all-sites-options td:not(:first-child){display:none}.fs-multisite-options-container .fs-sites-list-container{display:none;overflow:auto}.fs-multisite-options-container .fs-sites-list-container table td{border-top:1px solid #ccc;padding:4px 2px}.fs-modal.fs-modal-license-key-resend .email-address-container{overflow:hidden;padding-right:2px}.fs-modal.fs-modal-license-key-resend.fs-freemium input.email-address{width:300px}.fs-modal.fs-modal-license-key-resend.fs-freemium label{display:block;margin-bottom:10px}.fs-modal.fs-modal-license-key-resend.fs-premium input.email-address{width:100%}.fs-modal.fs-modal-license-key-resend.fs-premium .button-container{float:right;margin-left:7px}@media(max-width:650px){.fs-modal.fs-modal-license-key-resend.fs-premium .button-container{margin-top:2px}}.rtl .fs-modal.fs-modal-license-key-resend .fs-modal-body .input-container>.email-address-container{padding-left:2px;padding-right:0}.rtl .fs-modal.fs-modal-license-key-resend .fs-modal-body .button-container{float:left;margin-left:0;margin-right:7px}a.show-license-resend-modal{display:inline-block;margin-top:4px}.fs-modal.fs-modal-email-address-update .fs-modal-body input[type=text]{width:100%}.fs-modal.fs-modal-email-address-update p{margin-bottom:0}.fs-modal.fs-modal-email-address-update ul{margin:1em .5em}.fs-modal.fs-modal-email-address-update ul li label span{float:left;margin-top:0}.fs-modal.fs-modal-email-address-update ul li label span:last-child{display:block;float:none;margin-left:20px}.fs-ajax-loader{height:20px;margin:auto;position:relative;width:170px}.fs-ajax-loader .fs-ajax-loader-bar{animation-direction:normal;animation-duration:1.5s;animation-iteration-count:infinite;animation-name:bounce_ajaxLoader;background-color:#fff;height:20px;position:absolute;top:0;transform:scale(.3);width:20px}.fs-ajax-loader .fs-ajax-loader-bar-1{animation-delay:.6s;left:0}.fs-ajax-loader .fs-ajax-loader-bar-2{animation-delay:.75s;left:19px}.fs-ajax-loader .fs-ajax-loader-bar-3{animation-delay:.9s;left:38px}.fs-ajax-loader .fs-ajax-loader-bar-4{animation-delay:1.05s;left:57px}.fs-ajax-loader .fs-ajax-loader-bar-5{animation-delay:1.2s;left:76px}.fs-ajax-loader .fs-ajax-loader-bar-6{animation-delay:1.35s;left:95px}.fs-ajax-loader .fs-ajax-loader-bar-7{animation-delay:1.5s;left:114px}.fs-ajax-loader .fs-ajax-loader-bar-8{animation-delay:1.65s;left:133px}@keyframes bounce_ajaxLoader{0%{background-color:#0074a3;transform:scale(1)}to{background-color:#fff;transform:scale(.3)}}.fs-modal-auto-install #request-filesystem-credentials-form .request-filesystem-credentials-action-buttons,.fs-modal-auto-install #request-filesystem-credentials-form h2{display:none}.fs-modal-auto-install #request-filesystem-credentials-form input[type=email],.fs-modal-auto-install #request-filesystem-credentials-form input[type=password],.fs-modal-auto-install #request-filesystem-credentials-form input[type=text]{-webkit-appearance:none;max-width:100%;padding:10px 10px 5px;width:300px}.fs-modal-auto-install #request-filesystem-credentials-form fieldset,.fs-modal-auto-install #request-filesystem-credentials-form label,.fs-modal-auto-install #request-filesystem-credentials-form>div{display:block;margin:0 auto;max-width:100%;width:300px}.button-primary.warn{background:#f56a48;border-color:#ec6544 #d2593c #d2593c;box-shadow:0 1px 0 #d2593c;text-shadow:0 -1px 1px #d2593c,1px 0 1px #d2593c,0 1px 1px #d2593c,-1px 0 1px #d2593c}.button-primary.warn:hover{background:#fd6d4a;border-color:#d2593c}.button-primary.warn:focus{box-shadow:0 1px 0 #dd6041,0 0 2px 1px #e4a796}.button-primary.warn:active{background:#dd6041;border-color:#d2593c;box-shadow:inset 0 2px 0 #d2593c}.button-primary.warn.disabled{background:#e76444!important;border-color:#d85e40!important;color:#f5b3a1!important;text-shadow:0 -1px 0 rgba(0,0,0,.1)!important}PK!::9libraries/freemius/assets/css/admin/gdpr-optin-notice.cssnu[.fs-notice[data-id^=gdpr_optin_actions] .underlined{text-decoration:underline}.fs-notice[data-id^=gdpr_optin_actions] ul .action-description,.fs-notice[data-id^=gdpr_optin_actions] ul .button{vertical-align:middle}.fs-notice[data-id^=gdpr_optin_actions] ul .action-description{display:inline-block;margin-left:3px}PK!$Eii0libraries/freemius/assets/css/admin/checkout.cssnu[.fs-ajax-loader{height:20px;margin:auto;position:relative;width:170px}.fs-ajax-loader .fs-ajax-loader-bar{animation-direction:normal;animation-duration:1.5s;animation-iteration-count:infinite;animation-name:bounce_ajaxLoader;background-color:#fff;height:20px;position:absolute;top:0;transform:scale(.3);width:20px}.fs-ajax-loader .fs-ajax-loader-bar-1{animation-delay:.6s;left:0}.fs-ajax-loader .fs-ajax-loader-bar-2{animation-delay:.75s;left:19px}.fs-ajax-loader .fs-ajax-loader-bar-3{animation-delay:.9s;left:38px}.fs-ajax-loader .fs-ajax-loader-bar-4{animation-delay:1.05s;left:57px}.fs-ajax-loader .fs-ajax-loader-bar-5{animation-delay:1.2s;left:76px}.fs-ajax-loader .fs-ajax-loader-bar-6{animation-delay:1.35s;left:95px}.fs-ajax-loader .fs-ajax-loader-bar-7{animation-delay:1.5s;left:114px}.fs-ajax-loader .fs-ajax-loader-bar-8{animation-delay:1.65s;left:133px}@keyframes bounce_ajaxLoader{0%{background-color:#0074a3;transform:scale(1)}to{background-color:#fff;transform:scale(.3)}}@media screen and (max-width:782px){#wpbody-content{padding-bottom:0!important}}.fs-checkout-process-redirect{padding:40px;text-align:center}PK!:0$$-libraries/freemius/assets/img/plugin-icon.pngnu[PNG  IHDR?1tEXtSoftwareAdobe ImageReadyqe<$FIDATx}_[G !$ F`/=qǓޟ"$XH$E,=.ܐ ' P#KKK+˰~Hv#kYYmmkZZ[RJDbQ_lɢN.$HRs ͢Ș;Z.$Ojfff"p|e.bH.ϟ3rP@^;99 zAd̒EÅѱ1q4hoo/++(BӇޖN/?`#% JKCCź(}Ncc$޿_:;Ν;n$13888;3#mJzzznܼiZ%SSCCC{{{jQQQqֶ6IH$~{&JKe?xPYY) FGGFFR.2XVd]]]Hz*K>4z(06&''?~ ?/l6ȡVI]]]V]rI_|Nn:0ٜ1~-ꫯxV`<L1߱ z?ֲ*t***v{xʉPZuV*|Lo;cl[[[dr+\Y,}eKK$+ ]'Nv=h4#sq* C_~Y&]Xyc1|uuoRh$5 8 (3)AX,/ گMM0}@+Sq O SI pZZ[}2_&8~ A( 3o߾74tuu!362酅0zXxZNK 06::88ˁuftLFf"1555;;e%L}nKY̌OLlHܹsW ''V;0}|SJrqqnEʹ7o߸! ޾E}>ۥ39piiihppSN`_$UDZ1=ϭfS`~~4sVB$44422[ QRcp`b||tlLxw޽~$J|A@ _|!Nx~aaA{_}!Q0;;k~qvPٗUp@_} esBf}ri6KY\''&X^__/ `1MMM͗t -01P`ٞ>}}SgӍ`0xb:b o߾e]yrߺQ#p=܏fg߽{wr+ 74֭[>d|pp .7q }}iu:;;\5ĥ7o?mA?}T{>}'#}򄼭Qȇ3kkkY^^c<~L~渾>;;g>lwwǏYVV#}vvv3{vL&Bt:̠njj twWh_ͬkE;ymllk!`7Cnrr7I@h4:>>GC <|h` ɏ6:`0~|laanrs։gT %/"d2/~uiq3yf 333XxWw|^9e+Hy|5< >|!򞻏$0$:i 97R RVMOM]Y+JM;PXC!k||ښJ>oQYA?r=V,obEYڕ ZS彔HP"ӀR󛛉c0mbmmH1!?퀷wzVC$;`x).PM։㕸}m#dK$eee_>z$6aB÷V?|o_~I=Ngz?' 7YE<\ |(LPs~\7D+~ NBV:B;w!UQOOqZևۮ~fE2?>!w!fb|< Muj {iϑHW=iqB~?ի!$%&8JRVUUHdqxf"۷oMOMSa3eyu,gD{䱈3HC,u{zLFƅj>&9OMnoV{$(_%ZN<[IOqG*lKT3׉>3FXˠy tz; _<ZJP6469Y_`lOyDH̆?}BNUQQ y8FPSXٌ7q:_gss>_+++(nyT` e?;Jy3 {^@"SP*3:px4*+ &@֡>2[~_ȖApRb2/.p%eH`(deOdCa岵LMMOKwH^g+:]c*4D PgV_?ȱ*F,i)@9!x*:YM}}=$,:X8e:  W'8EFa:5Px{ m|,Tg>~7=MH `hҤs@ 5B AA+0x7݄&zJ_ggb9tBeeefH#]yTU HkN2p8!tTg6|ܻLfˏ55>B rdnR>j߻Nr!G/ L:US A^V/fZl3ongD9Nh@2_RP .0AY,J\~0g)+e|6\b::!;Urk_%ZHs=PEȝ^3i&'D5˛rhhH1rਨL tNLhr}pgr}nGAPP-j @ T|$7Xs/;s#U"Q Pj"D=٬Kh̒ !gt,kB7,? !}kk뤛[ S(X"|ׁI.p G_S:'Nz&ͬv` \FB ˂3EpfyXј~J>L Y%@>,(}΃ #߰L+㙊%)Bt0 __B$#j#I"IRH!qcS܎lgP=_b/HL{8A =X#Ka EB@x^2˲j8(Hv &z]]iB4 ]  f]$C9ZI$L !I) ' 4HBޢ(:8 @$,LL*LB!u5V> DSj 8KRjŽ$ȶ56%$NfuD{Q _!', PAlkDʪ*ؼP_sD  ʛ%jAB V%b{yMk_[K 7T !PA 0 ʁjBaBf1)nGdlwt2|>n,sS$1p*hFaʢd Pq>Ld:d3tm}k׮c݌oj-)`BWn] AYYYgWWi C)<^8j)w4v`cr !d$U@. ҬzzzJ,m&!KWVVH^K֒tT-/-` ΒrߟBDPW8,V+ #  I2{C]b:Q%tk&)Y^^&{zJMyl|BKKK$5pw,.,b~ x 8B 4 3YMՑ";7kk? !ƢWG 'z;VBSSS$e`5[oYALfAuvu[,#PZU)%bB?*>j4ZkJbϓ؊bDȨN {z:D` s =Ѱ(q{:@*%EWWW^UqxxHRAȍfZ\.7X0kv+|>!xbĄ=b @kű~,Q 7ɱ=͆@O:TJC1XBc "! b7ncollP]+VGtKDQq Gf+Enr:n-_Z\z)!#Q=# A4VT񯗄uV cJQP֓T08I}al~d^>O'n S[{;UW!0pA hݻb{F"[_l0"#2aCЧO"\}"s޽wY{j<>%j#xϜ2PvwwS,Ix޽{+].W"B!;婊XD=DaFa*A:pG˼ 6`k[LP|>;IGL>|(N***~kkk$Q &NCcQ7B3Kb  ۠w tvv r4VyD*ï!CL lc 4"L Ͳ`0Hj{'N6P~X[Z[}E__^ i.\ 791A ”pf<dEVk^lhp{mmm{qq0œgKOGGFH&hIgx<{I˛z-.dr^AyZ!_ ոWS.ޱnn '|*FLl+O$?~'[h ~-fgfrQ dZi#Zc"?߯f6 ׂh4Jxfptۛ7B4z/*yow]~:;;iu˗/E˂ bccc 䫫czCl^ZDX3eŝ痢 nliss3ct/GR«ֶ61l  \X&DZODf‚gv};? +>W"wS\ntI]]}swwPb#GI_t:=22Bimc" p1 O4rSoe,-hJt Z![A3vv}:;;YT*ϟL@ ěn_D#zHn6W?_)3ѻJZ?I8%/ l90LZ#*dtUOO΅i9kMMf(`abmH3& Rshpѵ-!֕BNF ݙL919ʤ5 RsǑ8G.]p8UTS{(.eZ]f$ y]^-28>R/^0U9r12!"PS AЋdo~Lj*@;>6byq)N%X LQ{h^xn;hLͺ*|y~2;[*bTVjxt+뽼Fww7#p4i`#t&gŻ/@SWmhnnF.{{{޿CxiolYIEx]@F_]a}8`|k-J4^ H盶wt\z`ʎWHR'\gRx޾&{ooo_q6RB^]0(h'dEJf̌Q_uUWvttlQ+e#5W" E0}. Gd",:\_'*fso0(" ̨ڛn.JɁ,޽} U(q)溺ׁf"D6R_߸g) `ʶ!>c۷o^8Uag뇜mnjŸ4^yx EXQQog#V9|۟''_&,/-)I!q{A)S"st HY(x^.g^i<mJ6񮛛]WFu;! BeA`rbBKSೝNgUU]fWT`TH9 ND8 D뽽Lu +ފr:00 ZUIɢ_\sW̚˻-E <@wqWA;ܿS޹{Wb{^~n 턌~!)ެqCY^얕A- ]]Es/@'Ƚ%zhtfzWWqR?ȩo ,..pd1ۖәGStuu5ߓt:=7;Btz `ʎj: DbvfFDC~Н$<$ U1&8*++۳[ TS@^{g"iڌ_Rva4S[[RbϪqBDbiy9^DWW, {& ! E?Vkk EsU9BEAM:Uι\% PAH+B#5N AA",E R:EEL&C X4M|x<LWLY.O$E=WVj+Xa1箮mbxPȐJ *UUKv\nGuoww;FX^Bn4xná̛06$)3 O}o;o2 2"#!@ 2d!C& 2dȐ CL2dȐ!@ 2d!C& 2dȐ CL2dȐ!@ 2dX#qtxx~\v_z ΋DIII %?[lr Yݵ{?Bε!%%ENHMM)!@lY[[[;^@C8 /j(y, wX_ܴZ ~BIVLt͆Gd6vwwC蓃y $ h_]Y1L!}/A\D˕ kkGGGavk :$33S~2˵7VKJK@ G:B" TVUeddD/v@!//O&@DtiiivfTTHNN}F ΢0m0_K IIIkzzLpsx SS_G+J,n߹# T;<}6p o6e  Qd2}/bcc/9n+*UI mmmڜtttta~gRjuWҞq2AO+?p8F}VuuuSssFJĻ_z{wvvxi  ζ6mww*Ά)HJJ 666u{ YY& 4"4)ommI8֖P`fff|lL*go:777///'78Lb4pB#VVVP!]YYG#l-,,iZ]XՕ5IoܼB!Av#υ/+/r( ٕe$ܿ?TΌC?}s0R,-tΪT*E(nq1]#uJǏC",@?RSSkjku:]8 AS43=-?y\`ggGqFQ/F`I<~$--M&O=Ay  -aX&E8E{aVvLGhOIIilj*((PDVWV&zr111YYY2j Z2Gg"6ONh=~fK @[P_VV/FptdQxd5sp]]]Ej,><4d#*&f]] /S]] ''|񱱹9T*w" 3Vv^tƆ6Dmnl 8N!. 3xD2G7oJ燆4M}@688*š>z9#+\XX~Yx;n*.)ֲu|+M1$oa! ޻'oăy%:>>j/_҂L&'𩧇zbNNN5{{o޼RRRZZ[?~&:\GKISJx,T䎌}0Ov1>zz'P<~)VĚ,LzX`QJKIt4BOKoR`|lljY*Uk_9;3@yqaaUՈ\-18>p# t#SU,.**z/tOa*#zp~Jsttt495=uP1C^^W*Dkxb_~e<~sVϔRWz=Nal)÷o"p?~Lȍ䫎U)`\ GF|eyyyFFJKK{ykOz-\pOBl6̦3??!rANEcc#o{ w[ ӹbJ%||P /rͶg/ Yxr~~z ^x-dB0@Q&8Jd M?;3e5ϢAzo$)5o#9\X!hcskao7$)@`@z{{)0tRF6^RZ.8`7nd4~jl]#`X91,?鶗qqqqM,)))?_I v7^.&uG?Ve y0--$]^\A3PI><ų@ɎՕ./KEErxx/تH^uu5|{{k?$RFew,GlZ SLP~?aoc_R.'t8!œ~&Klr+~sxsXk}>$FK E@ ?0z)A?wwsX}BGG\Tdfe\ [Ha॓`hBO.Fu< (twumfccc@?;5J.q.FJKs<O'KK1QЧR?etpOwvvl)H!opih.51>sPҎB'[_$a8tb2'--nWk?8p8`FCK_III~e OąuywJJ} vᡁ[ǣ?M&@i>UF6:(1,@:l+.( s\B 5! tY"vUKG UG%G.I[+6tU16F_g0g u o Pቹ\n N;%`cA2 e|䠘ľSlPTI6 ڳ)Q"2<;hPkQx]]߿~%؜Ĥ^ŏ$ςo?W:1{UBxз޸yMq$0:89&rC aq E3))b0˥ +Y6 x ǏF( y^qN'w rɔt:!Q՞#v~z D2 2-va-6!roX68\IÏH!@jgk4oݾ-\ZrIA8U0@/]BD³⩬ $ϘJ]]KM!!,)QDs8__NNN>յx&--iojǥ ߾}8%+++E> ~Pud|X+Bx 4&_| jׇwGGF[}w%fggONNjHl 9.IUX)TDK'f ްZoޔWT444h ܜIM>Aa HÜJAPC"  b~ny*g/UUU/ _@.$$n "Vh!X!![7os&_DQq$;m#!zfZ#--{9;⁏? QO0EeDLX/Z7zKklvFp]bi, f a+!" $$PxKKں:lhd aZ=VAyLდ6002ګՇRy޽~] `<$ M4Ȫ wk(0]]_0Y~pݻRW%9IA9N@I6|Z^ի5-66ŋc AQѣ_> i{C*%')ɣ#2:~YSqN˃)hni!|Chri{5@cb+I HB,Pznvmuu52))-e0p{Af ؠ~[o9t/l`, Nuڶ얺CT}˗/N O=k{*٦p~KT Y9uQzWǏW^M[ӵxŔggaXEG H ro sv!u 3-O9"l&X,E۷o](`!#": luEȸ!@͔fcq|6[WW׷_7AKRv-.TJRt:2[8'#2LMQw 'О ްȋ hz1MBN8'&&^z{'gmR- *""ST8Qy$(a(N˗ MWfs/hqaASVV&|948EA?O/??_m?vЅX,⏊,0!,`+h0kbq7EqE4E'%^-)/##oX8F슋j%Ly/--I +S$O==?}_W\oԤFeJ=ē'q$F!'DyPjB0o޼A2䯧V0nF&`ۧ i_3ҮTq ?{GGGS~>eegK] D4?9=tq''W.`"l6qxmȤ`GZ#kbi}}__/W/g ՞ku+(֦_UV^ 0R QF?&SuuFxI\J> 3G~X aczTjUҪ$.8==ﯕTaoW 7 @oNHn1yl6"j5;Ys G Q-D~q\.W}!RRxTj0. 1|O}}WPtd2aGT`1G² WJ(g"o 111E>aTlbDv=YYY icr,/-w(Jt"g?nu5u KeTEeVKPDp{4 Beee>1bij!) v(&e>LRRS⮬c9⯳qNNP4 {~߳Oqwd}`@{h\*cn/INOccb32iF*JI^F_:wxr_>>>q_^u V5]ڂ;0T p7MM!!B`fz(O' b-%7nue|jpQ5"Ꚛ(jŁd5d-CPxi |N#VTiX!LE, ƩC9A )//B|\8?TKh" 3+VƯKK`{s9x\wqhu❞ .uVoX'&~T!EZ;ϓj%l@TRj\engti/:†1?ZzM=ccc{|LZ~t:k:. y40޽rDbbb?;;;!j^uٗޟc\L1B(]HPBX[[KqOl6ߌ;%0)ْKSC{]t?;'@$YIbNޣE@RR.Hd &&&I̝o0VRUKw|.YEII %II* og?lmmMQ|fΔTH64vW@`L&tSSSun/1,>YH}7\ʪ*ič!ONN SSGv&H$^PPPptHRvpЫrҲT\Vw8FBX8|Ņ!Zr\τB(+*:>>7XE)D_בb&G099ڧTU+7vnK2 <隚%hh$H X!@LL T) PuB wltd4 9P_Z֛U")))1TJq z,]ffxHR_^ hlj :f(°Y.2)x>; \$Btfx& 8ݏWev D}A4̲gKdϦsp>EχYW0W !7OĄh{bA'JƜYχ]()Qh&_.|&WP jakOjHM(sONajJw`Zx"7NwhTSSuVqNw> º$@βk{vvG}}>?:##2&D p/ \EEER&t߲Z9!t8.MU*|}7$4~`j 6`.oLFgl63ᎎ,Hw4m0H*:(xBxI* NAL@¾2W =0?q1* ^ʋ$!|Z@r3w2 %ޮ$7mXhRx\Aw\\q.L!Ĉ_DAAA=#n(aj͓+'D*HM!q/p{SC!@@ Uo$Y^^BBn^C/fdXҲ2]ep"kn QNG)+}ӱQe2|e @W IK* ECcg|__LȨ°B#$;]Txa6^732JJJx}Th%O # [A0oEkS8H eeeEKv~.^'FkYeggn-"5`0JSPP9/;,~kHMME ܤR4L0j6~r|k!iֶ-$oBJێ ' Sd:tO!V( TM"Q&6.HOO ZDE%xrON`( xrrz7~6蘘X0q>/xL"@JJ38>¤ؓ_%{8߱'CF$@qaǞ HŅ{2dD"x .'MUNʈ\J u=2dP\,# S2"%]rJTF@!DeD8䔨&[%i.eprJTF@᝞"De\`=ܛ IENDB`PK!p2WW'libraries/freemius/assets/img/index.phpnu[ H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3-:2iTXtXML:com.adobe.xmp Adobe Photoshop CC 2015 (Windows) 2015-10-16T03:18:31+02:00 2015-10-16T03:18:31+02:00 2015-10-16T03:18:31+02:00 xmp.iid:ef239efa-82da-c848-94b0-ca4f1fdf5cef adobe:docid:photoshop:d01d5335-73a3-11e5-aacb-d07bc84db99c xmp.did:121375aa-2ccf-6f42-88ea-436ea40726f5 created xmp.iid:121375aa-2ccf-6f42-88ea-436ea40726f5 2015-10-16T03:18:31+02:00 Adobe Photoshop CC 2015 (Windows) saved xmp.iid:ef239efa-82da-c848-94b0-ca4f1fdf5cef 2015-10-16T03:18:31+02:00 Adobe Photoshop CC 2015 (Windows) / image/png 3 sRGB IEC61966-2.1 1 720000/10000 720000/10000 2 1 100 100 P cHRMz%u0`:o_FIDATxy\U?;ݝtw=ȚeDH!㊊(:ΆШG#0(K! Bξtګq_^UW;;tU{Fzn"Iٞ&ŕ:]j7ȡ }mRXؓ@:.[ ;ǑY9]*23)($y}(>'0tL!|_<הm!$%X_? `>$)$8j#xZ "g ਉ_7 a/=2gc cqۨRt9'r܇FYy]qpyɮ|g]Xے(0%9>;uyy?Оjq/N*bGM䬋͖3mWc1SԴIpQMXMHZq6D׭Buj{*j3qt[&J*$zʣz2";Rf V+H4hAV?s7 E4ڃ1WUħf򒙺HݑBܦMæoeW(|8bE;¬ܤܮ2J"E122!`1| qaov!zKi4DdGlPxZF_ `zL4b|t6X8޶PIּ 6fW9QMO Bbf>h(R9/\1<\0-P\I:hy>DBdƷϗ7FTq7X:" ÒfyL;E8؃1)s:@5Y6O_eTՆ-fI&XE3f5v}2r:dDU ۏH!vyPE jy3mJ (iXd/|G]u5Uo.BqD?^9^j 1 Lgڐ%`v`)!.b=|5Px#^ȗmi\ auCNS&-=`w'ɪ> Њ(6oHKF6M+Y฀c؃1 ĠDՉ#B);ȮRqmqE c˟sY`f*KBu3p7 ICm⬍KP0hS"N ZgMI[-0?~6-(Ff}Л+ITz7/i#@>k 弄*cTLT)-k͕3mH e[W}i[hW4 =ց"jj o~E9NpV{ .ŝ?7PٵD)*K@hzBp:Nnꇾ!ĵh0TnX1wk$B6LvxuO= ΃@ 0JF)6H*o ՠ5UgF I*N ؝YKmo1_ftF _)ҚmL(H8n G#vPkU o',!㮣?o_#*EL؀h:C1غ|?xqO•Kalh)Drk=f;i<+[$hzaZ 9nsT0aT{m߃Ct@feR"CFgѦ(|xw Tx᥽: 쌖Q %asLQ1/a2a 3˴) p?!t1IkCVۛW.:ZezHm5+[̝xhRi:G#<]02ZQ=l}9 , g%A/+冔00ל/ CkVA:]:1mGlZx˗jotlڵpUʏVn~T% `L0~I_pZ-8\A[VH-Փw[8~k A9 ~B-1Y66;u5L$&]|bt! qifn'\qnfˤĒp׃\gg `1sZ"eiIYsu 04mzX&-%7]V! n%rO&eQ3*y|.xE253 v8 F tovKYBtTޠQk7HF?Tlp@hyʷ+w@Т2^sZ!#/,?,2M7gπk-a37mDkY>wi i4_ʎdNS$;X\P/Cleo@1-*GZj¢61"kl&|!&9YnT60,'^ʚ(S^: 7/oI\awމ715~XHQgxic/疧%J[<BK {~8 aKtM7^_#[NQ\w;_ =e\޳|7Q"Iep{I[>E-g89e"0*Ǻ!$@)sמ)֤$@̔=ÑSS]T`$@v?MIk=|3+6~Cțnj;oj x3M}e$rD_wᑻ˴``N.0f*K9hVB6x%wt§_{T4ٍusՀa;9 ~q4}(ػsV̭/mG$W7cIK GO󻠳'`GV';GJ,岸.Oʅ+uN(25S}\1CaA鑍åK?˭`y`o6ylL9ʍU炫%<+eGGt R)1o4R[vWP4 6|qל<{νT;*_d^_߼ٜ4DOdJU/MD5N}3vBCX-kǩ5R'N暲9S;cQ¬}F{Y3aZhϴ/Z0zR1x/UOM4;hec*-QaQht%RGr^nxgt&f+T40zjhqyK{kaVŲIڊ<{G?/lÂ|pqЫm6x9| >^]ȯ'N!}D;p֚0Y1សGqvRy?c`57~> ]yWHa܌倵?M[V8UENbW 0 T੭ \Y"l M185pV+[akp`sD4 ?y~ty :P;錼p %2jIV7ʒ ߧu>,hdf"ˬga\-CJ4?舌9{x)c@%`1yt4wy[ vceZ=uȿ +P8 I熁 kϿSEDci\X%E0Z$sJ6_?,|Dv S [G2r|nّe,!)XqdG|kèzb:6̃h2J&<-Vga'Du(lXuZ)̜?!vנI+K)>9:HN ո _`@wQl68 ҉ D6;!'Jĝ"(af 7pRU245&ѸPZs&Dµy]v7GqZSkk,w9f MC2*񑸘LK5Q9F.w6uѸ(f嵃p:,c5TbURKPWy%%IZlG DQ('85(櫫bG3s\`XLn*x[Aao¿exF:@[gp* X6 _VK'2Hqf7Zk:euqD[M5\T"s"\[e+LV!9c>/֮I%b=*QKg6jJ"&P}sMoR9#)O*.l`@*Oo+g%,SUASI9MJ]K6u _?o~ۋJ1nWIENDB`PK!uAp]p]Ilibraries/freemius/assets/js/pricing/f18006f6535a1a6e9c6bfbffafe6f18a.svgnu[ PK!H p]p]Ilibraries/freemius/assets/js/pricing/14fb1bd5b7c41648488b06147f50a0dc.svgnu[ PK!:0$$Ilibraries/freemius/assets/js/pricing/4375c4a3ddc6f637c2ab9a2d7220f91e.pngnu[PNG  IHDR?1tEXtSoftwareAdobe ImageReadyqe<$FIDATx}_[G !$ F`/=qǓޟ"$XH$E,=.ܐ ' P#KKK+˰~Hv#kYYmmkZZ[RJDbQ_lɢN.$HRs ͢Ș;Z.$Ojfff"p|e.bH.ϟ3rP@^;99 zAd̒EÅѱ1q4hoo/++(BӇޖN/?`#% JKCCź(}Ncc$޿_:;Ν;n$13888;3#mJzzznܼiZ%SSCCC{{{jQQQqֶ6IH$~{&JKe?xPYY) FGGFFR.2XVd]]]Hz*K>4z(06&''?~ ?/l6ȡVI]]]V]rI_|Nn:0ٜ1~-ꫯxV`<L1߱ z?ֲ*t***v{xʉPZuV*|Lo;cl[[[dr+\Y,}eKK$+ ]'Nv=h4#sq* C_~Y&]Xyc1|uuoRh$5 8 (3)AX,/ گMM0}@+Sq O SI pZZ[}2_&8~ A( 3o߾74tuu!362酅0zXxZNK 06::88ˁuftLFf"1555;;e%L}nKY̌OLlHܹsW ''V;0}|SJrqqnEʹ7o߸! ޾E}>ۥ39piiihppSN`_$UDZ1=ϭfS`~~4sVB$44422[ QRcp`b||tlLxw޽~$J|A@ _|!Nx~aaA{_}!Q0;;k~qvPٗUp@_} esBf}ri6KY\''&X^__/ `1MMM͗t -01P`ٞ>}}SgӍ`0xb:b o߾e]yrߺQ#p=܏fg߽{wr+ 74֭[>d|pp .7q }}iu:;;\5ĥ7o?mA?}T{>}'#}򄼭Qȇ3kkkY^^c<~L~渾>;;g>lwwǏYVV#}vvv3{vL&Bt:̠njj twWh_ͬkE;ymllk!`7Cnrr7I@h4:>>GC <|h` ɏ6:`0~|laanrs։gT %/"d2/~uiq3yf 333XxWw|^9e+Hy|5< >|!򞻏$0$:i 97R RVMOM]Y+JM;PXC!k||ښJ>oQYA?r=V,obEYڕ ZS彔HP"ӀR󛛉c0mbmmH1!?퀷wzVC$;`x).PM։㕸}m#dK$eee_>z$6aB÷V?|o_~I=Ngz?' 7YE<\ |(LPs~\7D+~ NBV:B;w!UQOOqZևۮ~fE2?>!w!fb|< Muj {iϑHW=iqB~?ի!$%&8JRVUUHdqxf"۷oMOMSa3eyu,gD{䱈3HC,u{zLFƅj>&9OMnoV{$(_%ZN<[IOqG*lKT3׉>3FXˠy tz; _<ZJP6469Y_`lOyDH̆?}BNUQQ y8FPSXٌ7q:_gss>_+++(nyT` e?;Jy3 {^@"SP*3:px4*+ &@֡>2[~_ȖApRb2/.p%eH`(deOdCa岵LMMOKwH^g+:]c*4D PgV_?ȱ*F,i)@9!x*:YM}}=$,:X8e:  W'8EFa:5Px{ m|,Tg>~7=MH `hҤs@ 5B AA+0x7݄&zJ_ggb9tBeeefH#]yTU HkN2p8!tTg6|ܻLfˏ55>B rdnR>j߻Nr!G/ L:US A^V/fZl3ongD9Nh@2_RP .0AY,J\~0g)+e|6\b::!;Urk_%ZHs=PEȝ^3i&'D5˛rhhH1rਨL tNLhr}pgr}nGAPP-j @ T|$7Xs/;s#U"Q Pj"D=٬Kh̒ !gt,kB7,? !}kk뤛[ S(X"|ׁI.p G_S:'Nz&ͬv` \FB ˂3EpfyXј~J>L Y%@>,(}΃ #߰L+㙊%)Bt0 __B$#j#I"IRH!qcS܎lgP=_b/HL{8A =X#Ka EB@x^2˲j8(Hv &z]]iB4 ]  f]$C9ZI$L !I) ' 4HBޢ(:8 @$,LL*LB!u5V> DSj 8KRjŽ$ȶ56%$NfuD{Q _!', PAlkDʪ*ؼP_sD  ʛ%jAB V%b{yMk_[K 7T !PA 0 ʁjBaBf1)nGdlwt2|>n,sS$1p*hFaʢd Pq>Ld:d3tm}k׮c݌oj-)`BWn] AYYYgWWi C)<^8j)w4v`cr !d$U@. ҬzzzJ,m&!KWVVH^K֒tT-/-` ΒrߟBDPW8,V+ #  I2{C]b:Q%tk&)Y^^&{zJMyl|BKKK$5pw,.,b~ x 8B 4 3YMՑ";7kk? !ƢWG 'z;VBSSS$e`5[oYALfAuvu[,#PZU)%bB?*>j4ZkJbϓ؊bDȨN {z:D` s =Ѱ(q{:@*%EWWW^UqxxHRAȍfZ\.7X0kv+|>!xbĄ=b @kű~,Q 7ɱ=͆@O:TJC1XBc "! b7ncollP]+VGtKDQq Gf+Enr:n-_Z\z)!#Q=# A4VT񯗄uV cJQP֓T08I}al~d^>O'n S[{;UW!0pA hݻb{F"[_l0"#2aCЧO"\}"s޽wY{j<>%j#xϜ2PvwwS,Ix޽{+].W"B!;婊XD=DaFa*A:pG˼ 6`k[LP|>;IGL>|(N***~kkk$Q &NCcQ7B3Kb  ۠w tvv r4VyD*ï!CL lc 4"L Ͳ`0Hj{'N6P~X[Z[}E__^ i.\ 791A ”pf<dEVk^lhp{mmm{qq0œgKOGGFH&hIgx<{I˛z-.dr^AyZ!_ ոWS.ޱnn '|*FLl+O$?~'[h ~-fgfrQ dZi#Zc"?߯f6 ׂh4Jxfptۛ7B4z/*yow]~:;;iu˗/E˂ bccc 䫫czCl^ZDX3eŝ痢 nliss3ct/GR«ֶ61l  \X&DZODf‚gv};? +>W"wS\ntI]]}swwPb#GI_t:=22Bimc" p1 O4rSoe,-hJt Z![A3vv}:;;YT*ϟL@ ěn_D#zHn6W?_)3ѻJZ?I8%/ l90LZ#*dtUOO΅i9kMMf(`abmH3& Rshpѵ-!֕BNF ݙL919ʤ5 RsǑ8G.]p8UTS{(.eZ]f$ y]^-28>R/^0U9r12!"PS AЋdo~Lj*@;>6byq)N%X LQ{h^xn;hLͺ*|y~2;[*bTVjxt+뽼Fww7#p4i`#t&gŻ/@SWmhnnF.{{{޿CxiolYIEx]@F_]a}8`|k-J4^ H盶wt\z`ʎWHR'\gRx޾&{ooo_q6RB^]0(h'dEJf̌Q_uUWvttlQ+e#5W" E0}. Gd",:\_'*fso0(" ̨ڛn.JɁ,޽} U(q)溺ׁf"D6R_߸g) `ʶ!>c۷o^8Uag뇜mnjŸ4^yx EXQQog#V9|۟''_&,/-)I!q{A)S"st HY(x^.g^i<mJ6񮛛]WFu;! BeA`rbBKSೝNgUU]fWT`TH9 ND8 D뽽Lu +ފr:00 ZUIɢ_\sW̚˻-E <@wqWA;ܿS޹{Wb{^~n 턌~!)ެqCY^얕A- ]]Es/@'Ƚ%zhtfzWWqR?ȩo ,..pd1ۖәGStuu5ߓt:=7;Btz `ʎj: DbvfFDC~Н$<$ U1&8*++۳[ TS@^{g"iڌ_Rva4S[[RbϪqBDbiy9^DWW, {& ! E?Vkk EsU9BEAM:Uι\% PAH+B#5N AA",E R:EEL&C X4M|x<LWLY.O$E=WVj+Xa1箮mbxPȐJ *UUKv\nGuoww;FX^Bn4xná̛06$)3 O} PK!Aw++Ilibraries/freemius/assets/js/pricing/fde48e4609a6ddc11d639fc2421f2afd.pngnu[PNG  IHDR?1tEXtSoftwareAdobe ImageReadyqe<+IDATx]_YB';vt׺~߽(("@ {$\I93s[wՅ!3>o;o2 2"#!@ 2d!C& 2dȐ CL2dȐ!@ 2d!C& 2dȐ CL2dȐ!@ 2dX#qtxx~\v_z ΋DIII %?[lr Yݵ{?Bε!%%ENHMM)!@lY[[[;^@C8 /j(y, wX_ܴZ ~BIVLt͆Gd6vwwC蓃y $ h_]Y1L!}/A\D˕ kkGGGavk :$33S~2˵7VKJK@ G:B" TVUeddD/v@!//O&@DtiiivfTTHNN}F ΢0m0_K IIIkzzLpsx SS_G+J,n߹# T;<}6p o6e  Qd2}/bcc/9n+*UI mmmڜtttta~gRjuWҞq2AO+?p8F}VuuuSssFJĻ_z{wvvxi  ζ6mww*Ά)HJJ 666u{ YY& 4"4)ommI8֖P`fff|lL*go:777///'78Lb4pB#VVVP!]YYG#l-,,iZ]XՕ5IoܼB!Av#υ/+/r( ٕe$ܿ?TΌC?}s0R,-tΪT*E(nq1]#uJǏC",@?RSSkjku:]8 AS43=-?y\`ggGqFQ/F`I<~$--M&O=Ay  -aX&E8E{aVvLGhOIIilj*((PDVWV&zr111YYY2j Z2Gg"6ONh=~fK @[P_VV/FptdQxd5sp]]]Ej,><4d#*&f]] /S]] ''|񱱹9T*w" 3Vv^tƆ6Dmnl 8N!. 3xD2G7oJ燆4M}@688*š>z9#+\XX~Yx;n*.)ֲu|+M1$oa! ޻'oăy%:>>j/_҂L&'𩧇zbNNN5{{o޼RRRZZ[?~&:\GKISJx,T䎌}0Ov1>zz'P<~)VĚ,LzX`QJKIt4BOKoR`|lljY*Uk_9;3@yqaaUՈ\-18>p# t#SU,.**z/tOa*#zp~Jsttt495=uP1C^^W*Dkxb_~e<~sVϔRWz=Nal)÷o"p?~Lȍ䫎U)`\ GF|eyyyFFJKK{ykOz-\pOBl6̦3??!rANEcc#o{ w[ ӹbJ%||P /rͶg/ Yxr~~z ^x-dB0@Q&8Jd M?;3e5ϢAzo$)5o#9\X!hcskao7$)@`@z{{)0tRF6^RZ.8`7nd4~jl]#`X91,?鶗qqqqM,)))?_I v7^.&uG?Ve y0--$]^\A3PI><ų@ɎՕ./KEErxx/تH^uu5|{{k?$RFew,GlZ SLP~?aoc_R.'t8!œ~&Klr+~sxsXk}>$FK E@ ?0z)A?wwsX}BGG\Tdfe\ [Ha॓`hBO.Fu< (twumfccc@?;5J.q.FJKs<O'KK1QЧR?etpOwvvl)H!opih.51>sPҎB'[_$a8tb2'--nWk?8p8`FCK_III~e OąuywJJ} vᡁ[ǣ?M&@i>UF6:(1,@:l+.( s\B 5! tY"vUKG UG%G.I[+6tU16F_g0g u o Pቹ\n N;%`cA2 e|䠘ľSlPTI6 ڳ)Q"2<;hPkQx]]߿~%؜Ĥ^ŏ$ςo?W:1{UBxз޸yMq$0:89&rC aq E3))b0˥ +Y6 x ǏF( y^qN'w rɔt:!Q՞#v~z D2 2-va-6!roX68\IÏH!@jgk4oݾ-\ZrIA8U0@/]BD³⩬ $ϘJ]]KM!!,)QDs8__NNN>յx&--iojǥ ߾}8%+++E> ~Pud|X+Bx 4&_| jׇwGGF[}w%fggONNjHl 9.IUX)TDK'f ްZoޔWT444h ܜIM>Aa HÜJAPC"  b~ny*g/UUU/ _@.$$n "Vh!X!![7os&_DQq$;m#!zfZ#--{9;⁏? QO0EeDLX/Z7zKklvFp]bi, f a+!" $$PxKKں:lhd aZ=VAyLდ6002ګՇRy޽~] `<$ M4Ȫ wk(0]]_0Y~pݻRW%9IA9N@I6|Z^ի5-66ŋc AQѣ_> i{C*%')ɣ#2:~YSqN˃)hni!|Chri{5@cb+I HB,Pznvmuu52))-e0p{Af ؠ~[o9t/l`, Nuڶ얺CT}˗/N O=k{*٦p~KT Y9uQzWǏW^M[ӵxŔggaXEG H ro sv!u 3-O9"l&X,E۷o](`!#": luEȸ!@͔fcq|6[WW׷_7AKRv-.TJRt:2[8'#2LMQw 'О ްȋ hz1MBN8'&&^z{'gmR- *""ST8Qy$(a(N˗ MWfs/hqaASVV&|948EA?O/??_m?vЅX,⏊,0!,`+h0kbq7EqE4E'%^-)/##oX8F슋j%Ly/--I +S$O==?}_W\oԤFeJ=ē'q$F!'DyPjB0o޼A2䯧V0nF&`ۧ i_3ҮTq ?{GGGS~>eegK] D4?9=tq''W.`"l6qxmȤ`GZ#kbi}}__/W/g ՞ku+(֦_UV^ 0R QF?&SuuFxI\J> 3G~X aczTjUҪ$.8==ﯕTaoW 7 @oNHn1yl6"j5;Ys G Q-D~q\.W}!RRxTj0. 1|O}}WPtd2aGT`1G² WJ(g"o 111E>aTlbDv=YYY icr,/-w(Jt"g?nu5u KeTEeVKPDp{4 Beee>1bij!) v(&e>LRRS⮬c9⯳qNNP4 {~߳Oqwd}`@{h\*cn/INOccb32iF*JI^F_:wxr_>>>q_^u V5]ڂ;0T p7MM!!B`fz(O' b-%7nue|jpQ5"Ꚛ(jŁd5d-CPxi |N#VTiX!LE, ƩC9A )//B|\8?TKh" 3+VƯKK`{s9x\wqhu❞ .uVoX'&~T!EZ;ϓj%l@TRj\engti/:†1?ZzM=ccc{|LZ~t:k:. y40޽rDbbb?;;;!j^uٗޟc\L1B(]HPBX[[KqOl6ߌ;%0)ْKSC{]t?;'@$YIbNޣE@RR.Hd &&&I̝o0VRUKw|.YEII %II* og?lmmMQ|fΔTH64vW@`L&tSSSun/1,>YH}7\ʪ*ič!ONN SSGv&H$^PPPptHRvpЫrҲT\Vw8FBX8|Ņ!Zr\τB(+*:>>7XE)D_בb&G099ڧTU+7vnK2 <隚%hh$H X!@LL T) PuB wltd4 9P_Z֛U")))1TJq z,]ffxHR_^ hlj :f(°Y.2)x>; \$Btfx& 8ݏWev D}A4̲gKdϦsp>EχYW0W !7OĄh{bA'JƜYχ]()Qh&_.|&WP jakOjHM(sONajJw`Zx"7NwhTSSuVqNw> º$@βk{vvG}}>?:##2&D p/ \EEER&t߲Z9!t8.MU*|}7$4~`j 6`.oLFgl63ᎎ,Hw4m0H*:(xBxI* NAL@¾2W =0?q1* ^ʋ$!|Z@r3w2 %ޮ$7mXhRx\Aw\\q.L!Ĉ_DAAA=#n(aj͓+'D*HM!q/p{SC!@@ Uo$Y^^BBn^C/fdXҲ2]ep"kn QNG)+}ӱQe2|e @W IK* ECcg|__LȨ°B#$;]Txa6^732JJJx}Th%O # [A0oEkS8H eeeEKv~.^'FkYeggn-"5`0JSPP9/;,~kHMME ܤR4L0j6~r|k!iֶ-$oBJێ ' Sd:tO!V( TM"Q&6.HOO ZDE%xrON`( xrrz7~6蘘X0q>/xL"@JJ38>¤ؓ_%{8߱'CF$@qaǞ HŅ{2dD"x .'MUNʈ\J u=2dP\,# S2"%]rJTF@!DeD8䔨&[%i.eprJTF@᝞"De\`=ܛ IENDB`PK!XPPIlibraries/freemius/assets/js/pricing/b4f3b958f4a019862d81b15f3f8eee3a.svgnu[ PK!{Ilibraries/freemius/assets/js/pricing/178afa6030e76635dbe835e111d2c507.pngnu[PNG  IHDRddtEXtSoftwareAdobe ImageReadyqe<&IDATx]t\ՙ4]fMrwlcÞM ,%l 6'в ,$@,I8,I`,޻4TMmFFrCww^.lK pbx?f0W9L@:Ƭ^hgwVMV)Xs^{;^UZu`EccE*?yk0Ny_E`ق Phժּ~J4F %>,2 G.U ӓ abB;C@Hl#NWp826[f .~``p|>o =H 1hx:OJĸcN'nGV :>_VdA^ ƣhIø3(fN(岋 n˵ +aJbY کtw˨ݹGa^S9]nP}HI& h"h,όNzK3jZ$f')e:>oBr(k;jvp4֭Q,X>4:ܻr("޲`zSD2V3FvR*Њ}JSl:L[7pRg.ˠX{{ŋ.WלW}`x@NW+HJaor~^ˋ Ng0'|aGg8Ǡ-@MpJLz g W Rgw3-{T2tk ,6cCcjSi!FO{NsNI6'کWl]Jm,ޠh)<_]~7iݱM^.\ |jdyc ߨ]^WBg=6GZtoTXKZbs*U+vwu1O>\U*Vr9,z1rzzt"6KجCR* _ؙ߲Z5Ʋkў @nߐA?bi~{aU2=46'Q9M p,vª T)ht\n%. ;6Y)eeMbIGmbGGl5_$^.- f[0H i9M 2> vbWS47lJi #؀?S3 9qW(״R0%/5]YNpL4ݯU<.'fptE!4+.tIV JWf LR'YRULmi#E rYѾxWW_' :.OH"hǕv\pթb s{ѾW\A?ɐ9iydG;$Ӑ4ZL9_Yw?Ԗ=}MIB@|1PcCmk?V.2 /1I9nP(s  򂮳lQK%Q$[T~o(%U_\KNƈ%ҴO(Ӷ+.Ym2[zc]H H$MGW؝eEgP^|en$-Di_#~&jyO*3+ۃyYmQuTt¤7$UjBuZm_%-u x:lþcc+(F yԴx`v 6ypE+1$W7>_pܓnORO\1̷rϭ^sUDtL6µ"Hd랞?1000PmUU==X**Xy8]nOZ鄜K*?Q)9w*䇌{_ AmCH0_:d$ Ar+kXJbo+UױьEKS3At2sK:bTk;о!*Nџ̛p9/kШu|^,=І$pԳ<^1楬SW5fu4i̅wE*b]9'7X^1"Fʈ*`½Xc DL=" FpW[).p<"."7?8d䆰oDvc293B-|F>sV^Hx%URyv&RU*~SȈ*[2 Կa>]SEZL}cT n7R't ujJB7DM>6[/إT(/3P7t :jHz̬ru|P2Qo=jeva x.WOg0N{<` %`@s8Ɯ0&!-t,Uq t15`Td JU=3:΂)d "pnJ,Xķ+9R1p;/ڽ2w?f}=8YjD4?Hn nJK)[ob:a2).gɪ'Xeuc"[_ %?=oĵF">B Yq DKW.r2t @5ǛG@pyaL3y0%"ٹ[74zcC'o!֯ȝ 5ŸkW/?5<-oמ[M WHج{sfrݩ'=t^HS&-.DK2  $EһWD3k.He\a}!;,璚*CMLp&*qk[7ɤX&ݗsTKv$t "p*7=k^2u'P]*o*Y#򃏇m%M} ٽL<}FCZ["kD+xؙ|N*nEBܡpj3?6f~i6reC N21ⱡџ/'߶qDŽLFXV,@kLF?s2cHڡ J>sUC- C4,,+{~|inf? 9prR[L=.7e L鿮a#Ioi#G ©F[ROCPBj7$T xw'/:xj  zFd=R^lc<fn oJKUBzjx|^D :{rlJq牑F&Ҷ3'7 e-P7 ~qѩ! ;\7||qp׵A{SL0ALJF/i.8^['jK[ P4=œb}ijfJk7c\^=3Sh  )=8o8B\ŗXfހn|ѳh߯/H#oww<[ZR=w9ٳ+kzYvƵv .;O2l8l<!pA-DG՞8Pj٥aOD5Ud D"ǭƧ@ݼ)ް]&cfxh&s,`EjxTwcBh]]GwAS~r '-:J-:*kGF.ʣ@0|!(\TWӖN{z1JZSc3}BG,+Ui4|&*ϩ$#g;IiʕlSQKQXcsY};bɍ_lҮE1!VSQ.XD T,dƦ;_:mgyLj,XeR.J1h|\ %BV].r=o@Ҹ7ol 1,=2o,6jRY\ƒ)s`lI/eYb=r~NAc"zp`=L|I+adړlO{3OQC&?^ FȎY NӣUr>3}(YOzЪ7?`S 0Rg41G [3Bf%+\>W+g=,%++r͚L.p6gPF.BYnSíuY\U LR o _C"b V`E"l`AYB«H"\@jsto")ͤr 'LC& t-45sJb- Yg]h,B.ނIJQuRqP)+d2ي Ђ7mqg:I٬;)_M,O)ƩP"].vԕgz"X%덄KVY"sfM+Yf9oVmJ07Y UvZLς.'0)`2V$r$: y#U1L?^&3::m`pqGX%wR ![ U}~f8EJvnɟX/<{WT"^({l`aB8A`PM|\5ԏ;&p3O(X**UO<谎bDkoTj+唾f!-Q9Xɲ &/蒆1גHf̊׏6q 8L}W/&㌬Xk@`BmX(8w7k)dzS2 k-MJ\'dKW}bDR) Ӭ9\t yԋl҃]rYA1c.I#7| ,tR[ 4<�T ʂ凾U˛G,2_xnְH%.p4yt>d{qIENDB`PK!8p]p]Ilibraries/freemius/assets/js/pricing/4529cac82a2d1f300d3c4702b7b5e8f3.svgnu[ PK!T\^^Ilibraries/freemius/assets/js/pricing/5480ed23b199531a8cbc05924f26952b.pngnu[PNG  IHDR<<MtEXtSoftwareAdobe ImageReadyqe<"iTXtXML:com.adobe.xmp s IDATxbx/  0!I⟌L8d?DYXMAb @㖞`S&>vCxH## j@coteb(3ßOV7 ؙ0 su T$S`Ć׹@>#H *ًALl@%,@s2} S\P8Ljp +`vgcR7}H@0 <<`w$ q$ÿ, ?]s-Cx@ @- 3qX$ЫLg-ÿ@.0g#q4CzUfo!py;*M|AJO L377+REPZݬibMfL˅&$瞹|x-5-r}ovk1zHPǩtUCpߗ`H#N~hK3P;uonܣ7^ eeڇ yjy|7Gu1H|wI\3z4(7Gǻz +G &,YX۱Tle+ g dhI^J%)z3Αr:G{ ڟHp]:4h)go;!me',ǿo-QUgwߛ$$@!ːqV_ TUhaeԊ*%0*T|:.L!oy7#̜f?_z =8>7)veS\N1nTDN4^YxY^3 tb(X|\}+ )f>\-;d"MQ :VpZ N$=̃N?t?;,XY QrL yn$7){wٜ4nyоěZ+`0I % eD9Vw@w(cG[3mtß!>Kh6lb[LE41w.s!.";pelըS !DSz ǀz̮]QF w4G/]K` @ 11_$ݐug@'0a@AjԘ5 & Ӧ(X&lOųTK]2tJ:Q 9V5|P<ǣA'W1x#D8ChJ֊sl`"?Uk~ԃ.Rg=+ddU۔jM 6܆190[LV~:EwGBP]Ťn_|`ךh;E$V8=HO򅿂`&vS7VɃBA w .ub\Owa5/7GO1GX$ݯB"kOXæq-!%/(Fm/ފd(.ޅ#m \r3vcǧQǃxٟ 's(n܉*uބO~<)[,ރY6`rdXcEjd|l~?1^E8QV&8VV%O"'U/|Pt};NpU+#pحX8'kpr ͽMwiGš6Cz\ ;?O'`0*T O V Mز; f{S 񻷏/xϽ&SOOc-׶ >s1Iؽ0xy{g"0SӼ90ew]f$(/^oNZ^F]Cc0덕[lS.?*N\&f?USh"뀑DSǸ,=ofq9 |dܥNP_;o[W-P.)|bjxkeS"!.1{SnR<"bP#LK^#0Ҿn! P="viK?,[xexd =6!9y$#+G~VߞH҅N_x N +^/@$XkPRr/,dzܕ(}6/ Z 0a,Zz6#'k`M {QtDjWe쨘B %촎Gs\y} &o 6r=݅;&߆QPhőiEx\84" ~#+gIDATxblhh`x5Cvv6dÇ빹TeY20X[[3J\.jdH ? ={7Ew8YX82|sACПM L ?}aXy# h۷o}3Gg|?>Py?##W!f1>|aҤI QQQ .DX,,,/ 2H0201gz{GYg&ϟ?گ nay'fe AQDڵkՑsw?޽{7 l,3.zʄ;gyɰITT&cC1DIa"%~WU Z; 1T3"&Cggyg_x  XFSAHT0aǰdfٱ ?~`CNNCyy9×/_z#2pCm-8ņA ~fb! L$%MhQOl!@BnRHp \Q B 5 KAvqM!`čHP\4PRH 1!J26iƌi~)]>fͻo;46@(n6Kr9A`N= UUMSW083NY'2EqU*< [shm( lT_^3Dk3h}WJ;u W2(Dn.jl~~s};b_ZD2YUt~iX"gw2${ :j <`Aw?:@*oiڰ6{UDw,f'?׶0{nЍ3NJYu+i]*,o@jC.<[i‰U]nbb'w"vG6}GV, mKH(u+$.ZcWXyA "-M\L% ĘjK%BM!*BPs,}=Iܹs;3s8D7Htxc}q:ڿSx!2y, l6A60Џ+y[Yˎ0Z 3NM, h:qJTԼBRw/D!g-b`@RR[m֨pN Ϫ^/Bg&'3Bڈ|ÉOa` c/0w1eoB`a3ÛǑ_/VdxS-A.p[]1,r6`"2v1h"bhŻϏpTp*E`u8L̒Ch~9s( Q6 ,Pyr ǷoT: 2p OFޘ|Aaqg)ֲݧUB3x7^-eZ|mS.Cm &"Z `J5~yy9oH1n7oPVV&g殒h !m }_º>f[V8ɱ13q\HHH@ L.UOgIDO֢/zO۽Ⱥ+g0:x±8mN, JJJ!//fx<%p2 ~bu)vƈ.Pr/ gR}z̑Nw~oN/+Z(+8o.ځx Mq5d;`^gMV #Er:9,EU6S Wo/Mh4&BpWP* xeԞiA]S5 \ Y#BTxI_ayX1HW9Em& _޳a'XQ H|Je~DM$UA᷊ڍP.2l _ҌG1Ye^JR&pj|"rL9BPRIdsW XλXz¤~4}t&['x{oz OΒAsb栲N꼉Xe,U ($,܈W,"+9ЌDr"8R+0#HT0e3"&͘!⣪I+U ]u (El6mii9{J6czES\ZXtEF\!g7:τH+G֪2O IJ;O)atT_W_ݳ;zQun֐jV%ˡ9 9Gl佔RL }\ХuG' ;(0'ã>gaʕFNNL#33ӆtzVD ( YqjGQQߍ`&xCnn5:Qz1ЉkkkOSG42 P5֏=[tf'(4:T< PE$''3Ze&z}5k֠ c[ztg"Mv&FdddK <#xAŲB}Џ`0E@O"tޢI5e͂ȜnT!*h7'kk1gc[{e99MKKòe˰w^,z̐ qhd塴?X,h4Y^^xtHJJD:&-8Cv׺bQ&("xwPGUp!a$DdT'SďehDaËlTZy4( Gy/#[`Nֺۃػ̛iҠ-X콴-_QFX(zļbXPRޅ3ͦ?]yd{>d2;hү>N l drJ%&jcxxbDܑ<-u}؅%#`P CPl[V,3C$4:QѱXΠ֠12NcJQy,}PL'ޙ~{Ͻ}߹ð4-^Y, 1 Z$T }U__a!6{x85$dFۮ|N}(edXaEdR`HACQUU%V( Hm3+inBrh"qP%_~ T˸um2XLfUzM_/r5jR1%D){V@`%K jWG//Ćo p=C}ʿbz ْ]BF'22 //}esC"3l4 A&zVܩ+|L'\i*<[= =F( )3zȨ eZbqI @[Oډ! "`@i^ CKsߪ7EmC2dB.8WF㬃$Aĩ2 :=ˑF<ڟ@lT׃/2>ô\#~@ߢ>kVۮɊ,, GcMn!K|{=1foF -Hw) EMU ~;,~`Ƹw07~~']#ǩ A}FE5wijJ(#Je}V.eF4Z]0pj1ZƔ~K5袘֫\]~Ăh1!0;L5ؗ֬ƹOE1kxkX,bK5Tv^CNj7NL\l؏~/`=[&L+ jށZ7&bGKCEǿ+ Ih "'L:d RƤ/0e4i:XnS/%n"r/󤴵鈭`˂?J'H۶l0SJtVضv _w ?FVF$.h&o7/MPZF '9wVWVVhe{'r' YaX7c|$B aL{qϐ$X11YpMLXq4jqUGm7bZ)H. ˁjM-*}Lrp1mX<ᘈSzS: te()**JɼSpY&YCxC[fu\d})'Dª<߿ɆxXQZ,Ei8!\\JM}<ڌ^c>$6:jīG>4Y 0|@HRZ ,BbWS?+=So9vĈBLЊZ)zĀWyb˼K ΔⲷB?߀^tَPCGn-=qq嶄e(^" 7 ՐH:;Z+(>b}"fy/s$<i35S2s-b29MC7E^O1,۔(,RbSDHt>\⋉Ç%:LABNDXlP?fggcgńEgǏLnmm=#եZ4.( mЖvI<\\%kZJU.eo* LtEfeՎ~L;׭['$4am61ph:o1ai' vq׮]عs~!ϋ@syTTmAO?zp 䞿q ѡ(Q7>QkoXo޼YK5L)yKi%oq& PFe|s=BXƾ)P.+ j"m3. ]M-_OJi&v|T!R4+ 0>& A,9s111#%vx=={yjkk~}?>b7{v}Y@555())nq{޽"^zz0n[l[B|`?I`EܼySa'ȥ+YhvJƴn|:\iA^ hfŦX2\SXQ_Ň7Rva. BYoZQ =Ckji@ 4GZ=O3$dVuvJPWiEu,U (^'!#Ayԏ89x1ٗ^BH*2d@c(mȹ=>>~1F85p WKޣ$Ohʣ﫪z_AY% BG9 Q`\(zFdbh2HMHG8#$b#LlZKwU]sGuT}{{oP"~Tn/MG@KZ$J1>jj\2q6 >cǎ#XҀ2.sY=$Ts7i^}.-}IFuWv^a\{7cNdH n|'D,\!<2G"B`((BJs8*P^^E+fR'f0K,[ke2qDeE@m@sRai* =^YjaQh IA< Gኩތ4^/Zܹs1Dȫ/ڇZP}]% |e\x[ne`Rya)͌lj%#(σ+k&D %uS°WP7ڽ47T-!_Ht \XK.Dd?^k}&W^.4*pL~o(l_Dun6lU}AG­!z1(T28&{ I Ox_Jg̰찘ؐdA.@KG:_#XA,YrZ&Q܍- $"JEzhرTڎ/šhTQkjp{5-4%&F +32TA"ՒAdO_>vmzHvKTB'ON4'~3ܲfA3\޲Uug9 ޤqL(ւ ȶHn.}"h6dKkRE cmaʃI"oQ9e p[.QۧRdT(?x<'#|J TS4S8TL-?= Wsy,QSO>^\lJyV /.CL$*12L#A5)|F#=ٲ߽a>֬F1֛pS9L(|N|;ze,Y>/4/Zv:5َ >^i5a;&~hW ,>8GJ^cw*D8+lD}òg{lLtϏ6ڲ`DLj\+ensZWm{(FUh3+>x-A, L1jS{e/p]z8KLXLhESP@^(*t&0䆁G?aЗP{:uy׋ r Xyb-V am!_!HؕQW?Ui s^1]D_U,"bM)X"&ώڮh բ19KEH]pFQ^Lq7'!ezV :T<ԧ`7 VqT5$H伛}.Z"<[/ 2 WJ6S8kmH2lq/dlwQ ʑ8cJ{{Mq\rEe$]IJ%ܐL%{L_ۿ=&3vfⳲ53 ē5yKFv ϟ`̃iEw)mtf3% NDE^Ljk} S"@.4(tڗAZT|ϬļK3;Ͼ$T&1D#6Jp/ b7":ٓ nŴsMnC͵m,[ GfʢQ$f,$@5>'5p>Ѓ&jN{{oۧ>vO^hI*pH|1X K2=v*YD؅P#'V`%hhoﴠcxfbԨQ*{h7K,=aM&"}O(t%mЖzn8Bo L[z?gkI|A ӗlIh0iBf<L^S88͏닖cޘGm'?NjO潇EW_T8P$snS{Xu!q:ESC31]@s˟W%CPCv] m_؟ryIVpXY zcb˓zIpf;<{1[PY鉑Zݡ>Ent.g??bI|U2˰,bKut*Ì72SZZ|_HS}*C ʺ6ӭ0 B2=ѣG}}}>Sf_$;u8V)S ^5^PB!XL[TdF ui{`3;j0%u#ch | OcEQIqut5b;?DQd(YciDH eejNwº8nqBY[w|IŲ ۂy5k(?D\UUu|(VC\ЇDIRh WKX4,s&9ȳ!F8R&btBƟ|}xjVoo:.`1̺.cTB6FU"ƿ]nk 5jCEjo(4H928rRGBŻGJw-XV{8Fޔ:e̴Pn-H)$ y\V %WˍFAŠyʅHdN3%b׎9ǺۺE~  'oRԔgm7m7N17H>W7;"IlHaT39 !#C<-!Π*]K[׶\9.@u2t&k!C=m= keb4w)`+11vy ,(dC'H" !q-obE r"PFJ(NEW (# בRw9obeL2JR x[Զ'Mz^ciu;SآUz+^ xw ী,E; !tz+s* yNSx'V$ ;u89L]_:!|b ʻS0htB  špFL1XT:o(b `1p0I d ɃX_pdd <}@Eǒw&&& kc#cx.hN w-vYI'x+djvopdM H2Rf 9Ep-˸'@|4L_}2~/ dm^Cq݅};XrjLhW$u5v~ד*q߶hZN"96z%0./e=,<,ʺ=l܍CapgJ9?C 8>1β\:^ 3?Ē"Z ]|J'pf+aш/2|Uŏd-@D2XA }s$4'=B*!O\;Yg[9 kPk3Ў%4r0/'Hcc!?R sCjiBYT:!5 %uy"OMBLhJbCNZ묋Ys!oQDl, x*R3;A@j>$1'_2aK&l] 8[^v ~BW,z ,ex`A!Yeǯ_uqYaVԄ 7=0ډ2Eo~lݏ6؛JB 8mAfF8 o%a#]onC 'Het⬴uVޏ(oHv5*w`y`"u[)%RAwsXgWpQj˸}76V|]N׿y`n;@^Ik.Te.ب SgCG%`gi6~j6gBm5g'o o@/Xe}*^)Uʄ|k|φw?w9kvBr/VO=$nbt|x`mP@"-Z6K (%Z,- ےaO{V'WĘr,AH"H*(xbW]!(XmU-\# 'I|oΪP L)K. nY}r-' Vd \)sw%m\52sU\h%M8~1xt˹݅܄0zZǗ􆑬zn]M~v h6laFFt~nbbbdioW0*𓭕-1e^ڴܤsU5|#Ò$+aV"i-pOx[0 {sz2TiɌc_2/8MZ)H8eFګۡ&x!ŒN wmƲF6ŭ@tPf ntV!J=Oݗ<;1;r%9㸉AhA" :ckA8 MaE$vT33$A6 q[*Ip=jڨMBH9lI8`LLCq.9TL 2Z!J!s 7^ ʅ ,)W4^7ΔmG^z;.;'S꨷BJ *&R-ce,,xbͷ؉l2&!vi.o&kthOOK_zDN*N矇qx6Zygcǡ 3 t:x_~hk(ꬥGYG74Rq,E1`¨-d%iN]u&`%EfY=V|NUkA5e/eVhms#zǿ`V,[i 1=8|UᎯRR=1oӂܵs"qlׁai>jH"֙ܛRu._1A4!xoq `AFbێw]-]+Yjm=v'= ,Sml~Z5vBh9My)4.)qwM}Dʥl+|B95Otl K_?}߹W=RB['|ʆ(H",갱:`= ?ýWٵN\kԂIDQbZn4fgg01'ɛAph軽 Kʎ_in*ex~VUEǃqDŧ'f=`ISLUmmKeXRn9R'$=+l} 2l{ g]mq`Hp][~wۘd-=) b1J܍N!lnh@[]t#Lޮp {)g^۶?-| `{}>DG3ς?oq &DR LԞGNu_ %hB@ >S ?? %w24r̿oT2Aro p(0PjT/?"% ؎" E;^jX GEM#mW42O>om8`ч d1oLR 1a)dXӞ*6Զ_͹[r^o S4 qAd1y.2ŏV fJ5-2 ,h|H#u~zҝS<ڌ3cRG![cFu$> /߄[:R&'^V,I"Н8y֪lO*RΔt;!rC  DI\nďX<ʲ;U1M8?@SV ^MIVbN2[FE a =ۤ.db!͖SV'oƑ5%+K~:x򱦰TӫnX3 bc`0vx <*9(jJa.-\帖*6=.I^3qYر'"4K؃E;6R2N&i#әF8}iP/WJ`v0cIENDB`PK!}IPeeIlibraries/freemius/assets/js/pricing/27b5a722a5553d9de0170325267fccec.pngnu[PNG  IHDRddtEXtSoftwareAdobe ImageReadyqe<IDATx] XSWچ=a}Qvĭ:h;LkՙjijNjպP }IXC7\^KrBgx${{sEsӺ?j<( z%ْk{5'U,Of1aꚪ ԏ=$%hI}C>[;k꫄Cf-V\sKSWGo\ctP0 p=}雙p77@}t X>~˹8}:Z45YVZ{hŵpҳSVmC%$G-p9#ɰtuz tw eg>ٖ_Y04WruL޾ZK k~K5lj+(UWx;)XNv.#<62F`xpP_B:pRq`vT.W2H!l  016o8p^R^XUW>`FDI)-m뀠_֎im4Xd+h j/*##"Remm[ӍDQN~C"t& |a5~E1Xl6X?uKs]%ҧ;THKKA7 * 5Ʀ8Ru弞.$ac_Y[?8~Y' /;/CQn~WxpayMO=}E7 t]g/I`حL]䎑=`U]A#'9 ߟvќ<%Fu}Žɬ9ai#p-F F ^<]>GS^]r ; 7jh/eҘK"].^'\ 9 nFHۨ('D[KGB=`bl/]=Yr`\񤻒|O)?%#VCt;ioo0a#:YTWwsf\;,_lbdRL# ש+/IUݮ@CC}Bb1HýAg-XuQZmm 7'1X~I GZnG z^ `lh tsRpH>F@tD iY`LÂGy--D%q+}/SX[';g7fAG!R#If0Sarqs# @$u 1! JQ#?] +wTϋXx I4WUK]zԬІm ,./G`{\Ir": nz:)f3 _dHg ot0&sn0:seG/nl+*/LT8`%1"0< onjaan!S=Z]>.eِ^8>K;׳zCPo~LƍJ%>^,.^MZ△45 AytSb{WTH:@ cl/`O%37If-Ӳ/xV}􀲞pL%(Be#}} <44(;NâacC7׿[PScbo@5@}SDŽd?ƍϋ\}9y%E8NPȈdJǹF+}ϭؐ1%{\XPG77E7--Wq=2X@&J} ~A)$wɐXʁy )*r3%gO?g>T ,BO\}e<$ c|@u{/>ZF.{@_>R$^fvPdna6`H *0cEgg6?p²3Cx'V> 橚dU7Th`\ۻphBW`1"%QG,AtN W@]c ?"[Ո9pf:b?.S&f3sUL|6kNؼ˙dEe | H!!FAiSsw❓1Tl{>&td^~\E#O4̞hj }/O9x M_MV9VvL}F'??@/xp(vTU֕M5obslapٸ HͰW¿$4|V pI "GkSESGPf:pXx7> e1ږWB)V౯y? ',s }/r Ne{Y߯B.X)D^R: 4} aRRԑ.}gHS7_絴7"ֹOHd]P,aA=DjEl235s,0zJ@`sU 譬U. vw164L 4q18ȈHGlTX_+c.\%A3g1%WKN?TjM8;G]f#Hk{svAF v!~w*Rc1~J"z ŞPRG;gzdz?LfoMX<~aLBBJW^A~iO%EeyIYJ&zbURGE,Ka eIR%s"j_^z5/יo0WΪ`6/^%8['",Sšv4ɯ5kJ77׾k' /Nt0'k?+rD nV_qbŏݛNd)djXïF:#7ܜ!Yחveo7aņmzױ7ɩfXɽou7tjfV)౶VTdciKvRZ[[7}=&n/悹a5e˙ɄЇ_~'-܉eyX pϧ `{xkH7oCbwKe`D>j`tVܪ* C@ ʎ$=ֽM;:fΛ:[m,gLFY ,pK 9NjRh@ '־ƒn^t9&Q\gy74I~dB"ʻԈSxӴɉ(_g߶r62y~ׇ[_}0i&`[y*I8iC=њ/zr`2>hLM؋޺ISP.? A 5?B[5F$ϑq.,Pj>eRFq1,1^!HOu/$5_{mdWxUmG?D[$f[gKRiR$qrj;xgN7^xS)dD}|;w1(t5 J.nr16 C3sӺ{Hshkw(/OA\]`IPӷ |a X?}}(7o>YOY|@j+ 4C;n YMdSRZ=X()ĒRhdz$d啖ڿ}ltTA=GpN|]J7IyXy8{I fD$fkmu1A–f]f+ o^O׎϶/r;,̬4~a9aCц7D< {,I݂UjVCYiڲ5^DA>m]wU[ 'hZ+Yi AYu cma _/iG,G=xㄧ*RhwZ o$^oWu}e-2^^]ig]ĮR+4"ʼ~2b* Z|!hWL@Y : SS?}Edֆm`j0GN$׏/^`ViioJ;SEՙ٭֎Su7yhj2U/ʝp&f$Fs%饿fK{S~FL9yD8# @fvs1i]s͍k(VPt;w|uO|;Ȝ 螮3s uS-03>|Lb{}ۨ+D&ËHcU7T"Q=\M-jF'rC¡>~wowcK}m\QA @+k~ [f:1ޣ#W_>{ص9^EEty#rW`V-Y=TՀozflpR>^]c xSie!r%\S^N.~I0{쒚G Z:?z'տ˟RQ,╼w˕E?>vųS#h'v>܄#/P(Р`P ll0!5p8`$Lf&_^:AK_Y GfT(R\X`R^/\&˧-_,,w?d;Sx%OϷ\=DU|Sx{YYBa!MUu5:vHhlaKP'X+DatfI/~s ^xM{g+BҩMX[3Efi"ް0Y>\_^͗52Ap8ӍOso ==-Vjg:LvU zֆmQr|#k [ 0g0,7'f0H%7lwPdYe|˟vDhMcQ,rL F\z {nm _AJ ES::%VUIr1,N!U>[NI 1-t$˗?P,#:ŖG=LCπ`8XG.'B*[j+J EE-*n6vsW `zlk> n>rIKC28vk,/2֕GFݒʟΚPjuKc]8ь<`tu޺Z yg٬"a֨U` rx=ZQJ+kud^Tc< Xyײ%ClمRO4WZjt`ֶ|o_]Ҁs+K?=48ݿLGg,ϮOH=T<ʪ+-V  YfjW/TzV66s,XcA`}IDʼDf%-YCG`w<3hCD2Hen63 ]a6d}2zD:?HiSU9,Y I )HCd;9㯍=b~@_ooijui}QIm{B+JSO@N|7Z z{$uà ^4/7b`W/߽iXYc#`P+jˊ!}g oxU_ Np!2z 54WMUe{ύL\pjV0 Wa~EcTiXͷ](R2k!3X2IW7^fc`l 驥Y藶i-yjړnł*/%\B3U{_k?{u t>Z,(@7,Vm+ä7x,%֞mf9kϟnoɃt]Z"Y'Qd-ڮlQꂜqav $0ity}"(buH4[K/2ƎXQ=y+긚H!7I0m> ;8V3wa3`C*˪s|?z 8G"< OxN5ʾ?&h<V^.+&,cG7.By`|}Ŭ`"7K= ܵ7l2KC͝7@%-]݊ y`j%aYYY+_Ck1Ȏzh& ~"H'jLZ D]Rr5ܙ:Ȏ,ԖoHKM0s;?RJ 5Kl'g )9H?KFokr7H<>G&؋`0VgS=qCyn~|^p)x+k[p u'-saRY,g`/vnF2.)0Oyղ^D<f!Ե_;JP(s}zżm=Zjf⌻D:<@_̂%d^>g1ڌK,X-]27f, 3L&.8qFz|LQҬi,--y8cezW_ށ^T"l40vZ+-D膹1?XSfꈊI1#ȝϰl[e6US36 :bZak@twpe=ν7T,Ҥ6hٶ=I3 *(.ψךo%&B+GߏYvC &iJem|5gx(m|o0,a4'X8#O^7"ŞhtxrM#wV[]͕,XhS،@gc=NùT?~T]cRe3}&jMu膑噠88<נ؄E:Ϣո̻iSծi ުMTc`SQqA'Wśw täC n(G%Jq4jzųO6ί /)1S_({GK []k(/~9vgpfC ࢫ:K饣΅CgWbȝG?H? H?#㋬꽋g SD/_v[|Gr^~ƅlѮp=k44Sʶ:A7 nHm96zyhhC34ZCKI E|@†~'ĮZ&B.&q(p^>5yDmxP`pd[v^ b>$Yi,}p(Ni=x瀘x(5Džñ6T R2J$c ,Meˤl>A[WI8-M 򊜬Ǐ`G\F5o໋FK]Z5sW}ħD[ܡ1UTt@DQ ehYD/_+ /-"iQ2,. r-7ߒ̴gɤu57eY@WA|p G@Y6LDO|Bp1Bc^nuGcAqFMv#8j@ cY[/V­Į\7qiR;!u%4!wKf <A xs6s4x=Ќ^> q p7E7 ZRM\(،04"lѲЄaIc2n,+YL#߀#S1,i?N't4af bpL3iW`2ʲ_?% a+m:B uϛixQPCB%.!LOK +$6i2.aODsqݢwuaQ_9:HG.#wX!4Y2t3:19l_7=q%m5"FPWS O[ Gnvw$VDʡE/7, :>0fOkZ6!#̈́m6!+="CQ@@$Ý%WЉK'F^*)t\ Bpxu#q^%hn֎GzE}PIɆN)(ݳ?^džl==D8XdtG9[_?{_{JDw>vK@ ]tS;纒»OId ({?|g 5_#зٔE6^'6k'@/n٭C'4 ^_w(JEg/$e{TQR)&8iF#\s0x^u^Aa]'Ix=eW7B Fe1فhLb08 Dx\21w^/-ʇ0$C'i~llBBc^2ݿ.w Zg^ 94G`hdcWH>7>tI1 ,*X6yj I 8wp͟[a2y>i1{$h)z ^sˡ7L=4oL,W=<[&sLDP/6 bKL蘌\1U Ku^1q4mk Mʅc/10%}? `ЄS…&u сjL34 M%RTH&RA|ל)EP{uW}YDkC?4vodl,FM TM湤Ny)zcwŚ= #S}9F?ҕqy M ATS/JimMK@5[ÃV6FR-ߌJ]2ܠKC@L 0-J}IIENDB`PK!ܠ\\8libraries/freemius/assets/js/pricing/freemius-pricing.jsnu[/*! For license information please see freemius-pricing.js.LICENSE.txt */ !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Freemius=t():e.Freemius=t()}(self,(function(){return(()=>{var e={487:e=>{var t={utf8:{stringToBytes:function(e){return t.bin.stringToBytes(unescape(encodeURIComponent(e)))},bytesToString:function(e){return decodeURIComponent(escape(t.bin.bytesToString(e)))}},bin:{stringToBytes:function(e){for(var t=[],n=0;n{var t,n;t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",n={rotl:function(e,t){return e<>>32-t},rotr:function(e,t){return e<<32-t|e>>>t},endian:function(e){if(e.constructor==Number)return 16711935&n.rotl(e,8)|4278255360&n.rotl(e,24);for(var t=0;t0;e--)t.push(Math.floor(256*Math.random()));return t},bytesToWords:function(e){for(var t=[],n=0,a=0;n>>5]|=e[n]<<24-a%32;return t},wordsToBytes:function(e){for(var t=[],n=0;n<32*e.length;n+=8)t.push(e[n>>>5]>>>24-n%32&255);return t},bytesToHex:function(e){for(var t=[],n=0;n>>4).toString(16)),t.push((15&e[n]).toString(16));return t.join("")},hexToBytes:function(e){for(var t=[],n=0;n>>6*(3-i)&63)):n.push("=");return n.join("")},base64ToBytes:function(e){e=e.replace(/[^A-Z0-9+\/]/gi,"");for(var n=[],a=0,r=0;a>>6-2*r);return n}},e.exports=n},477:(e,t,n)=>{"use strict";n.d(t,{Z:()=>s});var a=n(81),r=n.n(a),i=n(645),o=n.n(i)()(r());o.push([e.id,':root{--fs-ds-blue-10: #f0f6fc;--fs-ds-blue-50: #c5d9ed;--fs-ds-blue-100: #9ec2e6;--fs-ds-blue-200: #72aee6;--fs-ds-blue-300: #4f94d4;--fs-ds-blue-400: #3582c4;--fs-ds-blue-500: #2271b1;--fs-ds-blue-600: #135e96;--fs-ds-blue-700: #0a4b78;--fs-ds-blue-800: #043959;--fs-ds-blue-900: #01263a;--fs-ds-neutral-10: #f0f0f1;--fs-ds-neutral-50: #dcdcde;--fs-ds-neutral-100: #c3c4c7;--fs-ds-neutral-200: #a7aaad;--fs-ds-neutral-300: #8c8f94;--fs-ds-neutral-400: #787c82;--fs-ds-neutral-500: #646970;--fs-ds-neutral-600: #50575e;--fs-ds-neutral-700: #3c434a;--fs-ds-neutral-800: #2c3338;--fs-ds-neutral-900: #1d2327;--fs-ds-neutral-900-fade-60: rgba(29, 35, 39, .6);--fs-ds-neutral-900-fade-92: rgba(29, 35, 39, .08);--fs-ds-green-10: #b8e6bf;--fs-ds-green-100: #68de7c;--fs-ds-green-200: #1ed14b;--fs-ds-green-300: #00ba37;--fs-ds-green-400: #00a32a;--fs-ds-green-500: #008a20;--fs-ds-green-600: #007017;--fs-ds-green-700: #005c12;--fs-ds-green-800: #00450c;--fs-ds-green-900: #003008;--fs-ds-red-10: #facfd2;--fs-ds-red-100: #ffabaf;--fs-ds-red-200: #ff8085;--fs-ds-red-300: #f86368;--fs-ds-red-400: #e65054;--fs-ds-red-500: #d63638;--fs-ds-red-600: #b32d2e;--fs-ds-red-700: #8a2424;--fs-ds-red-800: #691c1c;--fs-ds-red-900: #451313;--fs-ds-yellow-10: #fcf9e8;--fs-ds-yellow-100: #f2d675;--fs-ds-yellow-200: #f0c33c;--fs-ds-yellow-300: #dba617;--fs-ds-yellow-400: #bd8600;--fs-ds-yellow-500: #996800;--fs-ds-yellow-600: #755100;--fs-ds-yellow-700: #614200;--fs-ds-yellow-800: #4a3200;--fs-ds-yellow-900: #362400;--fs-ds-white-10: #ffffff}#fs_pricing_app,#fs_pricing_wrapper{--fs-ds-theme-primary-accent-color: var(--fs-ds-blue-500);--fs-ds-theme-primary-accent-color-hover: var(--fs-ds-blue-600);--fs-ds-theme-primary-green-color: var(--fs-ds-green-500);--fs-ds-theme-primary-red-color: var(--fs-ds-red-500);--fs-ds-theme-primary-yellow-color: var(--fs-ds-yellow-500);--fs-ds-theme-error-color: var(--fs-ds-theme-primary-red-color);--fs-ds-theme-success-color: var(--fs-ds-theme-primary-green-color);--fs-ds-theme-warn-color: var(--fs-ds-theme-primary-yellow-color);--fs-ds-theme-background-color: var(--fs-ds-white-10);--fs-ds-theme-background-shade: var(--fs-ds-neutral-10);--fs-ds-theme-background-accented: var(--fs-ds-neutral-50);--fs-ds-theme-background-hover: var(--fs-ds-neutral-200);--fs-ds-theme-background-overlay: var(--fs-ds-neutral-900-fade-60);--fs-ds-theme-background-dark: var(--fs-ds-neutral-800);--fs-ds-theme-background-darkest: var(--fs-ds-neutral-900);--fs-ds-theme-text-color: var(--fs-ds-neutral-900);--fs-ds-theme-heading-text-color: var(--fs-ds-neutral-800);--fs-ds-theme-muted-text-color: var(--fs-ds-neutral-600);--fs-ds-theme-dark-background-text-color: var(--fs-ds-white-10);--fs-ds-theme-dark-background-muted-text-color: var(--fs-ds-neutral-300);--fs-ds-theme-divider-color: var(--fs-ds-theme-background-accented);--fs-ds-theme-border-color: var(--fs-ds-neutral-100);--fs-ds-theme-button-background-color: var(--fs-ds-neutral-50);--fs-ds-theme-button-background-hover-color: var(--fs-ds-neutral-200);--fs-ds-theme-button-text-color: var(--fs-ds-theme-heading-text-color);--fs-ds-theme-button-border-color: var(--fs-ds-neutral-300);--fs-ds-theme-button-border-hover-color: var(--fs-ds-neutral-600);--fs-ds-theme-button-border-focus-color: var(--fs-ds-blue-400);--fs-ds-theme-button-primary-background-color: var(--fs-ds-theme-primary-accent-color);--fs-ds-theme-button-primary-background-hover-color: var(--fs-ds-theme-primary-accent-color-hover);--fs-ds-theme-button-primary-text-color: var(--fs-ds-white-10);--fs-ds-theme-button-primary-border-color: var(--fs-ds-blue-800);--fs-ds-theme-button-primary-border-hover-color: var(--fs-ds-blue-900);--fs-ds-theme-button-primary-border-focus-color: var(--fs-ds-blue-100);--fs-ds-theme-button-disabled-border-color: var(--fs-ds-neutral-100);--fs-ds-theme-button-disabled-background-color: var(--fs-ds-neutral-50);--fs-ds-theme-button-disabled-text-color: var(--fs-ds-neutral-300);--fs-ds-theme-notice-warn-background: var(--fs-ds-yellow-10);--fs-ds-theme-notice-warn-color: var(--fs-ds-yellow-900);--fs-ds-theme-notice-warn-border: var(--fs-ds-theme-warn-color);--fs-ds-theme-notice-info-background: var(--fs-ds-theme-background-shade);--fs-ds-theme-notice-info-color: var(--fs-ds-theme-primary-accent-color-hover);--fs-ds-theme-notice-info-border: var(--fs-ds-theme-primary-accent-color);--fs-ds-theme-package-popular-background: var(--fs-ds-blue-200);--fs-ds-theme-testimonial-star-color: var(--fs-ds-yellow-300)}#fs_pricing.fs-full-size-wrapper{margin-top:0}#root,#fs_pricing_app{background:var(--fs-ds-theme-background-shade);color:var(--fs-ds-theme-text-color);height:auto;line-height:normal;font-size:13px;margin:0}#root h1,#root h2,#root h3,#root h4,#root ul,#root blockquote,#fs_pricing_app h1,#fs_pricing_app h2,#fs_pricing_app h3,#fs_pricing_app h4,#fs_pricing_app ul,#fs_pricing_app blockquote{margin:0;padding:0;text-align:center;color:var(--fs-ds-theme-heading-text-color)}#root h1,#fs_pricing_app h1{font-size:2.5em}#root h2,#fs_pricing_app h2{font-size:1.5em}#root h3,#fs_pricing_app h3{font-size:1.2em}#root ul,#fs_pricing_app ul{list-style-type:none}#root p,#fs_pricing_app p{font-size:.9em}#root p,#root blockquote,#fs_pricing_app p,#fs_pricing_app blockquote{color:var(--fs-ds-theme-text-color)}#root strong,#fs_pricing_app strong{font-weight:700}#root li,#root dd,#fs_pricing_app li,#fs_pricing_app dd{margin:0}#root .fs-app-header .fs-page-title,#fs_pricing_app .fs-app-header .fs-page-title{margin:0 0 15px;text-align:left;display:flex;flex-flow:row wrap;gap:10px;align-items:center;padding:20px 15px 10px}#root .fs-app-header .fs-page-title h1,#fs_pricing_app .fs-app-header .fs-page-title h1{font-size:18px;margin:0}#root .fs-app-header .fs-page-title h3,#fs_pricing_app .fs-app-header .fs-page-title h3{margin:0;font-size:14px;padding:4px 8px;font-weight:400;border-radius:4px;background-color:var(--fs-ds-theme-background-accented);color:var(--fs-ds-theme-muted-text-color)}#root .fs-app-header .fs-plugin-title-and-logo,#fs_pricing_app .fs-app-header .fs-plugin-title-and-logo{margin:0 15px;background:var(--fs-ds-theme-background-color);padding:12px 0;border:1px solid var(--fs-ds-theme-divider-color);border-radius:4px;text-align:center}#root .fs-app-header .fs-plugin-title-and-logo .fs-plugin-logo,#root .fs-app-header .fs-plugin-title-and-logo h1,#fs_pricing_app .fs-app-header .fs-plugin-title-and-logo .fs-plugin-logo,#fs_pricing_app .fs-app-header .fs-plugin-title-and-logo h1{display:inline-block;vertical-align:middle;margin:0 10px}#root .fs-app-header .fs-plugin-title-and-logo .fs-plugin-logo,#fs_pricing_app .fs-app-header .fs-plugin-title-and-logo .fs-plugin-logo{width:48px;height:48px;border-radius:4px}@media screen and (min-width: 601px){#root .fs-app-header .fs-plugin-title-and-logo .fs-plugin-logo,#fs_pricing_app .fs-app-header .fs-plugin-title-and-logo .fs-plugin-logo{width:64px;height:64px}}#root .fs-trial-message,#fs_pricing_app .fs-trial-message{padding:20px;background:var(--fs-ds-theme-notice-warn-background);color:var(--fs-ds-theme-notice-warn-color);font-weight:700;text-align:center;border-top:1px solid var(--fs-ds-theme-notice-warn-border);border-bottom:1px solid var(--fs-ds-theme-notice-warn-border);font-size:1.2em;box-sizing:border-box;margin:0 0 5px}#root .fs-app-main,#fs_pricing_app .fs-app-main{text-align:center}#root .fs-app-main .fs-section,#fs_pricing_app .fs-app-main .fs-section{margin:auto;display:block}#root .fs-app-main .fs-section .fs-section-header,#fs_pricing_app .fs-app-main .fs-section .fs-section-header{font-weight:700}#root .fs-app-main>.fs-section,#fs_pricing_app .fs-app-main>.fs-section{padding:20px;margin:4em auto 0}#root .fs-app-main>.fs-section:nth-child(even),#fs_pricing_app .fs-app-main>.fs-section:nth-child(even){background:var(--fs-ds-theme-background-color)}#root .fs-app-main>.fs-section>header,#fs_pricing_app .fs-app-main>.fs-section>header{margin:0 0 3em}#root .fs-app-main>.fs-section>header h2,#fs_pricing_app .fs-app-main>.fs-section>header h2{margin:0;font-size:2.5em}#root .fs-app-main .fs-section--plans-and-pricing,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing{padding:20px;margin-top:0}#root .fs-app-main .fs-section--plans-and-pricing>.fs-section,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing>.fs-section{margin:1.5em auto 0}#root .fs-app-main .fs-section--plans-and-pricing>.fs-section:first-child,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing>.fs-section:first-child{margin-top:0}#root .fs-app-main .fs-section--plans-and-pricing .fs-annual-discount,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-annual-discount{font-weight:700;font-size:small}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--trial-header,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--trial-header{text-align:center;background:var(--fs-ds-theme-background-color);padding:20px;border-radius:5px;box-sizing:border-box;max-width:945px}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--trial-header h2,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--trial-header h2{margin-bottom:10px}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--trial-header h4,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--trial-header h4{font-weight:400}#root .fs-app-main .fs-section--plans-and-pricing .fs-currencies,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-currencies{border-color:var(--fs-ds-theme-button-border-color)}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles{display:inline-block;vertical-align:middle;padding:0 10px;width:auto}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles{overflow:hidden}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles li,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles li{border:1px solid var(--fs-ds-theme-border-color);border-right-width:0;display:inline-block;font-weight:700;margin:0;padding:10px;cursor:pointer}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles li:first-child,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles li:first-child{border-radius:20px 0 0 20px}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles li:last-child,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles li:last-child{border-radius:0 20px 20px 0;border-right-width:1px}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles li.fs-selected-billing-cycle,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles li.fs-selected-billing-cycle{background:var(--fs-ds-theme-background-color);color:var(--fs-ds-theme-primary-accent-color-hover)}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--custom-implementation,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--custom-implementation{padding:15px;background:var(--fs-ds-theme-background-color);border:1px solid var(--fs-ds-theme-divider-color);border-radius:4px;box-sizing:border-box;max-width:945px;margin:0 auto}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--custom-implementation h2,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--custom-implementation h2{margin-bottom:10px;font-weight:700}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--custom-implementation p,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--custom-implementation p{font-size:small;margin:0}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--money-back-guarantee,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--money-back-guarantee{max-width:857px;margin:30px auto;position:relative}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--money-back-guarantee .fs-money-back-guarantee-title,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--money-back-guarantee .fs-money-back-guarantee-title{color:var(--fs-ds-theme-heading-text-color);font-weight:700;margin-bottom:15px}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--money-back-guarantee .fs-money-back-guarantee-message,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--money-back-guarantee .fs-money-back-guarantee-message{font-size:small;line-height:20px;margin-bottom:15px;padding:0 15px}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--money-back-guarantee img,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--money-back-guarantee img{position:absolute;width:90px;top:50%;right:0;margin-top:-45px}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--badges .fs-badge,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--badges .fs-badge{display:inline-block;vertical-align:middle;position:relative;box-shadow:none;background:transparent}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--badges .fs-badge+.fs-badge,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--badges .fs-badge+.fs-badge{margin-left:20px;margin-top:13px}#root .fs-app-main .fs-section--testimonials,#fs_pricing_app .fs-app-main .fs-section--testimonials{border-top:1px solid var(--fs-ds-theme-border-color);border-bottom:1px solid var(--fs-ds-theme-border-color);padding:3em 4em 4em}#root .fs-app-main .fs-section--testimonials .fs-section-header,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-section-header{margin-left:-30px;margin-right:-30px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav{margin:auto;display:block;width:auto;position:relative}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-prev,#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-next,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-prev,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-next{top:50%;border:1px solid var(--fs-ds-theme-border-color);border-radius:14px;cursor:pointer;margin-top:11px;position:absolute}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-prev .fs-icon,#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-next .fs-icon,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-prev .fs-icon,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-next .fs-icon{display:inline-block;height:1em;width:1em;line-height:1em;color:var(--fs-ds-theme-muted-text-color);padding:5px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-prev,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-prev{margin-left:-30px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-next,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-next{right:-30px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials-track,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials-track{margin:auto;overflow:hidden;position:relative;display:block;padding-top:45px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials{width:10000px;display:block;position:relative;transition:left .5s ease,right .5s ease}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial{float:left;font-size:small;position:relative;width:340px;box-sizing:border-box;margin:0}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial>section,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial>section{box-sizing:border-box}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-rating,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-rating{color:var(--fs-ds-theme-testimonial-star-color)}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header,#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial>section,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial>section{background:var(--fs-ds-theme-background-color);padding:10px;margin:0 2em;border:1px solid var(--fs-ds-theme-divider-color)}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial>section,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial>section{border-radius:0 0 8px 8px;border-top:0 none}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header{border-bottom:0 none;border-radius:8px 8px 0 0}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header .fs-testimonial-logo,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header .fs-testimonial-logo{border:1px solid var(--fs-ds-theme-divider-color);border-radius:44px;padding:5px;background:var(--fs-ds-theme-background-color);width:76px;height:76px;position:relative;margin-top:-54px;left:50%;margin-left:-44px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header .fs-testimonial-logo object,#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header .fs-testimonial-logo img,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header .fs-testimonial-logo object,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header .fs-testimonial-logo img{max-width:100%;border-radius:40px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header h4,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header h4{margin:15px 0 6px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-icon-quote,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-icon-quote{color:var(--fs-ds-theme-muted-text-color)}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-message,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-message{line-height:18px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-author,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-author{margin-top:30px;margin-bottom:10px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-author .fs-testimonial-author-name,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-author .fs-testimonial-author-name{font-weight:700;margin-bottom:2px;color:var(--fs-ds-theme-text-color)}#root .fs-app-main .fs-section--testimonials .fs-nav-pagination,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-nav-pagination{margin:4em 0 0;position:relative}#root .fs-app-main .fs-section--testimonials .fs-nav-pagination li,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-nav-pagination li{position:relative;display:inline-block;margin:0 8px}#root .fs-app-main .fs-section--testimonials .fs-nav-pagination li button.fs-round-button,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-nav-pagination li button.fs-round-button{cursor:pointer;border:1px solid var(--fs-ds-theme-border-color);vertical-align:middle;display:inline-block;line-height:0;width:8px;height:8px;padding:0;color:transparent;outline:none;border-radius:4px;overflow:hidden}#root .fs-app-main .fs-section--testimonials .fs-nav-pagination li button.fs-round-button span,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-nav-pagination li button.fs-round-button span{display:inline-block;width:100%;height:100%;background:var(--fs-ds-theme-background-shade)}#root .fs-app-main .fs-section--testimonials .fs-nav-pagination li.selected button,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-nav-pagination li.selected button{border:0 none}#root .fs-app-main .fs-section--testimonials .fs-nav-pagination li.selected button.fs-round-button span,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-nav-pagination li.selected button.fs-round-button span{background:var(--fs-ds-theme-background-accented)}#root .fs-app-main .fs-section--faq,#fs_pricing_app .fs-app-main .fs-section--faq{background:var(--fs-ds-theme-background-shade)}#root .fs-app-main .fs-section--faq .fs-section--faq-items,#fs_pricing_app .fs-app-main .fs-section--faq .fs-section--faq-items{max-width:945px;margin:0 auto;box-sizing:border-box;text-align:left;columns:2;column-gap:20px}@media only screen and (max-width: 600px){#root .fs-app-main .fs-section--faq .fs-section--faq-items,#fs_pricing_app .fs-app-main .fs-section--faq .fs-section--faq-items{columns:1}}#root .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item,#fs_pricing_app .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item{width:100%;display:inline-block;vertical-align:top;margin:0 0 20px;overflow:hidden}#root .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item h3,#root .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item p,#fs_pricing_app .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item h3,#fs_pricing_app .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item p{margin:0;text-align:left}#root .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item h3,#fs_pricing_app .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item h3{background:var(--fs-ds-theme-background-dark);color:var(--fs-ds-theme-dark-background-text-color);padding:15px;font-weight:700;border:1px solid var(--fs-ds-theme-background-darkest);border-bottom:0 none;border-radius:4px 4px 0 0}#root .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item p,#fs_pricing_app .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item p{background:var(--fs-ds-theme-background-color);font-size:small;padding:15px;line-height:20px;border:1px solid var(--fs-ds-theme-border-color);border-top:0 none;border-radius:0 0 4px 4px}#root .fs-button,#fs_pricing_app .fs-button{background:var(--fs-ds-theme-button-background-color);color:var(--fs-ds-theme-button-text-color);padding:12px 10px;display:inline-block;text-transform:uppercase;font-weight:700;font-size:18px;width:100%;border-radius:4px;border:0 none;cursor:pointer;transition:background .2s ease-out,border-bottom-color .2s ease-out}#root .fs-button:focus:not(:disabled),#fs_pricing_app .fs-button:focus:not(:disabled){box-shadow:0 0 0 1px var(--fs-ds-theme-button-border-focus-color)}#root .fs-button:hover:not(:disabled),#root .fs-button:focus:not(:disabled),#root .fs-button:active:not(:disabled),#fs_pricing_app .fs-button:hover:not(:disabled),#fs_pricing_app .fs-button:focus:not(:disabled),#fs_pricing_app .fs-button:active:not(:disabled){will-change:background,border;background:var(--fs-ds-theme-button-background-hover-color)}#root .fs-button.fs-button--outline,#fs_pricing_app .fs-button.fs-button--outline{padding-top:11px;padding-bottom:11px;background:var(--fs-ds-theme-background-color);border:1px solid var(--fs-ds-theme-button-border-color)}#root .fs-button.fs-button--outline:focus:not(:disabled),#fs_pricing_app .fs-button.fs-button--outline:focus:not(:disabled){background:var(--fs-ds-theme-background-shade);border-color:var(--fs-ds-theme-button-border-focus-color)}#root .fs-button.fs-button--outline:hover:not(:disabled),#root .fs-button.fs-button--outline:active:not(:disabled),#fs_pricing_app .fs-button.fs-button--outline:hover:not(:disabled),#fs_pricing_app .fs-button.fs-button--outline:active:not(:disabled){background:var(--fs-ds-theme-background-shade);border-color:var(--fs-ds-theme-button-border-hover-color)}#root .fs-button.fs-button--type-primary,#fs_pricing_app .fs-button.fs-button--type-primary{background-color:var(--fs-ds-theme-button-primary-background-color);color:var(--fs-ds-theme-button-primary-text-color);border-color:var(--fs-ds-theme-button-primary-border-color)}#root .fs-button.fs-button--type-primary:focus:not(:disabled),#root .fs-button.fs-button--type-primary:hover:not(:disabled),#root .fs-button.fs-button--type-primary:active:not(:disabled),#fs_pricing_app .fs-button.fs-button--type-primary:focus:not(:disabled),#fs_pricing_app .fs-button.fs-button--type-primary:hover:not(:disabled),#fs_pricing_app .fs-button.fs-button--type-primary:active:not(:disabled){background-color:var(--fs-ds-theme-button-primary-background-hover-color);border-color:var(--fs-ds-theme-button-primary-border-hover-color)}#root .fs-button.fs-button--type-primary.fs-button--outline,#fs_pricing_app .fs-button.fs-button--type-primary.fs-button--outline{background-color:var(--fs-ds-theme-background-color);color:var(--fs-ds-theme-primary-accent-color);border:1px solid var(--fs-ds-theme-button-primary-border-color)}#root .fs-button.fs-button--type-primary.fs-button--outline:focus:not(:disabled),#root .fs-button.fs-button--type-primary.fs-button--outline:hover:not(:disabled),#root .fs-button.fs-button--type-primary.fs-button--outline:active:not(:disabled),#fs_pricing_app .fs-button.fs-button--type-primary.fs-button--outline:focus:not(:disabled),#fs_pricing_app .fs-button.fs-button--type-primary.fs-button--outline:hover:not(:disabled),#fs_pricing_app .fs-button.fs-button--type-primary.fs-button--outline:active:not(:disabled){background-color:var(--fs-ds-theme-background-shade);color:var(--fs-ds-theme-button-primary-background-hover-color);border-color:var(--fs-ds-theme-primary-accent-color-hover)}#root .fs-button:disabled,#fs_pricing_app .fs-button:disabled{cursor:not-allowed;background-color:var(--fs-ds-theme-button-disabled-background-color);color:var(--fs-ds-theme-button-disabled-text-color);border-color:var(--fs-ds-theme-button-disabled-border-color)}#root .fs-button.fs-button--size-small,#fs_pricing_app .fs-button.fs-button--size-small{font-size:14px;width:auto}#root .fs-placeholder:before,#fs_pricing_app .fs-placeholder:before{content:"";display:inline-block}@media only screen and (max-width: 768px){#root .fs-app-main .fs-section--testimonials .fs-nav-pagination,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-nav-pagination{display:none!important}#root .fs-app-main .fs-section>header h2,#fs_pricing_app .fs-app-main .fs-section>header h2{font-size:1.5em}}@media only screen and (max-width: 455px){#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial{width:auto}#root .fs-app-main .fs-section--billing-cycles .fs-billing-cycles li.fs-period--annual span,#fs_pricing_app .fs-app-main .fs-section--billing-cycles .fs-billing-cycles li.fs-period--annual span{display:none}}@media only screen and (max-width: 375px){#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial{width:auto}}\n',""]);const s=o},333:(e,t,n)=>{"use strict";n.d(t,{Z:()=>s});var a=n(81),r=n.n(a),i=n(645),o=n.n(i)()(r());o.push([e.id,"#fs_pricing_app .fs-modal,#fs_pricing_wrapper .fs-modal,#fs_pricing_wrapper #fs_pricing_app .fs-modal{position:fixed;inset:0;z-index:1000;zoom:1;text-align:left;display:block!important}#fs_pricing_app .fs-modal .fs-modal-content-container,#fs_pricing_wrapper .fs-modal .fs-modal-content-container,#fs_pricing_wrapper #fs_pricing_app .fs-modal .fs-modal-content-container{display:block;position:absolute;left:50%;background:var(--fs-ds-theme-background-color);box-shadow:0 0 8px 2px #0000004d}#fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-header,#fs_pricing_wrapper .fs-modal .fs-modal-content-container .fs-modal-header,#fs_pricing_wrapper #fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-header{background:var(--fs-ds-theme-primary-accent-color);padding:15px}#fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-header h3,#fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-header .fs-modal-close,#fs_pricing_wrapper .fs-modal .fs-modal-content-container .fs-modal-header h3,#fs_pricing_wrapper .fs-modal .fs-modal-content-container .fs-modal-header .fs-modal-close,#fs_pricing_wrapper #fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-header h3,#fs_pricing_wrapper #fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-header .fs-modal-close{color:var(--fs-ds-theme-background-color)}#fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-content,#fs_pricing_wrapper .fs-modal .fs-modal-content-container .fs-modal-content,#fs_pricing_wrapper #fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-content{font-size:1.2em}#fs_pricing_app .fs-modal--loading,#fs_pricing_wrapper .fs-modal--loading,#fs_pricing_wrapper #fs_pricing_app .fs-modal--loading{background-color:#0000004d}#fs_pricing_app .fs-modal--loading .fs-modal-content-container,#fs_pricing_wrapper .fs-modal--loading .fs-modal-content-container,#fs_pricing_wrapper #fs_pricing_app .fs-modal--loading .fs-modal-content-container{width:220px;margin-left:-126px;padding:15px;border:1px solid var(--fs-ds-theme-divider-color);text-align:center;top:50%}#fs_pricing_app .fs-modal--loading .fs-modal-content-container span,#fs_pricing_wrapper .fs-modal--loading .fs-modal-content-container span,#fs_pricing_wrapper #fs_pricing_app .fs-modal--loading .fs-modal-content-container span{display:block;font-weight:700;font-size:16px;text-align:center;color:var(--fs-ds-theme-primary-accent-color);margin-bottom:10px}#fs_pricing_app .fs-modal--loading .fs-modal-content-container .fs-ajax-loader,#fs_pricing_wrapper .fs-modal--loading .fs-modal-content-container .fs-ajax-loader,#fs_pricing_wrapper #fs_pricing_app .fs-modal--loading .fs-modal-content-container .fs-ajax-loader{width:160px}#fs_pricing_app .fs-modal--loading .fs-modal-content-container i,#fs_pricing_wrapper .fs-modal--loading .fs-modal-content-container i,#fs_pricing_wrapper #fs_pricing_app .fs-modal--loading .fs-modal-content-container i{display:block;width:128px;margin:0 auto;height:15px;background:url(//img.freemius.com/blue-loader.gif)}#fs_pricing_app .fs-modal--refund-policy,#fs_pricing_app .fs-modal--trial-confirmation,#fs_pricing_wrapper .fs-modal--refund-policy,#fs_pricing_wrapper .fs-modal--trial-confirmation,#fs_pricing_wrapper #fs_pricing_app .fs-modal--refund-policy,#fs_pricing_wrapper #fs_pricing_app .fs-modal--trial-confirmation{background:rgba(0,0,0,.7)}#fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container,#fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container,#fs_pricing_wrapper .fs-modal--refund-policy .fs-modal-content-container,#fs_pricing_wrapper .fs-modal--trial-confirmation .fs-modal-content-container,#fs_pricing_wrapper #fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container,#fs_pricing_wrapper #fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container{width:510px;margin-left:-255px;top:20%}#fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-header .fs-modal-close,#fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-header .fs-modal-close,#fs_pricing_wrapper .fs-modal--refund-policy .fs-modal-content-container .fs-modal-header .fs-modal-close,#fs_pricing_wrapper .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-header .fs-modal-close,#fs_pricing_wrapper #fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-header .fs-modal-close,#fs_pricing_wrapper #fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-header .fs-modal-close{line-height:24px;font-size:24px;position:absolute;top:-12px;right:-12px;cursor:pointer}#fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-content,#fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-content,#fs_pricing_wrapper .fs-modal--refund-policy .fs-modal-content-container .fs-modal-content,#fs_pricing_wrapper .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-content,#fs_pricing_wrapper #fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-content,#fs_pricing_wrapper #fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-content{height:100%;padding:1px 15px}#fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-footer,#fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-footer,#fs_pricing_wrapper .fs-modal--refund-policy .fs-modal-content-container .fs-modal-footer,#fs_pricing_wrapper .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-footer,#fs_pricing_wrapper #fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-footer,#fs_pricing_wrapper #fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-footer{padding:10px;text-align:right;border-top:1px solid var(--fs-ds-theme-border-color);background:var(--fs-ds-theme-background-shade)}#fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-footer .fs-button--approve-trial,#fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-footer .fs-button--approve-trial,#fs_pricing_wrapper .fs-modal--refund-policy .fs-modal-content-container .fs-modal-footer .fs-button--approve-trial,#fs_pricing_wrapper .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-footer .fs-button--approve-trial,#fs_pricing_wrapper #fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-footer .fs-button--approve-trial,#fs_pricing_wrapper #fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-footer .fs-button--approve-trial{margin:0 7px}#fs_pricing_app .fs-modal--trial-confirmation .fs-button,#fs_pricing_wrapper .fs-modal--trial-confirmation .fs-button,#fs_pricing_wrapper #fs_pricing_app .fs-modal--trial-confirmation .fs-button{width:auto;font-size:13px}\n",""]);const s=o},267:(e,t,n)=>{"use strict";n.d(t,{Z:()=>s});var a=n(81),r=n.n(a),i=n(645),o=n.n(i)()(r());o.push([e.id,'#root .fs-package,#fs_pricing_app .fs-package{display:inline-block;vertical-align:top;background:var(--fs-ds-theme-dark-background-text-color);border-bottom:3px solid var(--fs-ds-theme-border-color);width:315px;box-sizing:border-box}#root .fs-package:first-child,#root .fs-package+.fs-package,#fs_pricing_app .fs-package:first-child,#fs_pricing_app .fs-package+.fs-package{border-left:1px solid var(--fs-ds-theme-divider-color)}#root .fs-package:last-child,#fs_pricing_app .fs-package:last-child{border-right:1px solid var(--fs-ds-theme-divider-color)}#root .fs-package:not(.fs-featured-plan):first-child,#fs_pricing_app .fs-package:not(.fs-featured-plan):first-child{border-top-left-radius:10px}#root .fs-package:not(.fs-featured-plan):first-child .fs-plan-title,#fs_pricing_app .fs-package:not(.fs-featured-plan):first-child .fs-plan-title{border-top-left-radius:9px}#root .fs-package:not(.fs-featured-plan):last-child,#fs_pricing_app .fs-package:not(.fs-featured-plan):last-child{border-top-right-radius:10px}#root .fs-package:not(.fs-featured-plan):last-child .fs-plan-title,#fs_pricing_app .fs-package:not(.fs-featured-plan):last-child .fs-plan-title{border-top-right-radius:9px}#root .fs-package .fs-package-content,#fs_pricing_app .fs-package .fs-package-content{vertical-align:middle;padding-bottom:30px}#root .fs-package .fs-plan-title,#fs_pricing_app .fs-package .fs-plan-title{padding:10px 0;background:var(--fs-ds-theme-background-shade);text-transform:uppercase;border-bottom:1px solid var(--fs-ds-theme-divider-color);border-top:1px solid var(--fs-ds-theme-divider-color);width:100%;text-align:center}#root .fs-package .fs-plan-title:last-child,#fs_pricing_app .fs-package .fs-plan-title:last-child{border-right:none}#root .fs-package .fs-plan-description,#root .fs-package .fs-undiscounted-price,#root .fs-package .fs-licenses,#root .fs-package .fs-upgrade-button,#root .fs-package .fs-plan-features,#fs_pricing_app .fs-package .fs-plan-description,#fs_pricing_app .fs-package .fs-undiscounted-price,#fs_pricing_app .fs-package .fs-licenses,#fs_pricing_app .fs-package .fs-upgrade-button,#fs_pricing_app .fs-package .fs-plan-features{margin-top:10px}#root .fs-package .fs-plan-description,#fs_pricing_app .fs-package .fs-plan-description{text-transform:uppercase}#root .fs-package .fs-undiscounted-price,#fs_pricing_app .fs-package .fs-undiscounted-price{margin:auto;position:relative;display:inline-block;color:var(--fs-ds-theme-muted-text-color);top:6px}#root .fs-package .fs-undiscounted-price:after,#fs_pricing_app .fs-package .fs-undiscounted-price:after{display:block;content:"";position:absolute;height:1px;background-color:var(--fs-ds-theme-error-color);left:-4px;right:-4px;top:50%;transform:translateY(-50%) skewY(1deg)}#root .fs-package .fs-selected-pricing-amount,#fs_pricing_app .fs-package .fs-selected-pricing-amount{margin:5px 0}#root .fs-package .fs-selected-pricing-amount .fs-currency-symbol,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-currency-symbol{font-size:39px}#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer{font-size:58px;margin:0 5px}#root .fs-package .fs-selected-pricing-amount .fs-currency-symbol,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-currency-symbol,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container{display:inline-block;vertical-align:middle}#root .fs-package .fs-selected-pricing-amount .fs-currency-symbol:not(.fs-selected-pricing-amount-integer),#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer:not(.fs-selected-pricing-amount-integer),#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container:not(.fs-selected-pricing-amount-integer),#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-currency-symbol:not(.fs-selected-pricing-amount-integer),#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer:not(.fs-selected-pricing-amount-integer),#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container:not(.fs-selected-pricing-amount-integer){line-height:18px}#root .fs-package .fs-selected-pricing-amount .fs-currency-symbol .fs-selected-pricing-amount-fraction,#root .fs-package .fs-selected-pricing-amount .fs-currency-symbol .fs-selected-pricing-amount-cycle,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer .fs-selected-pricing-amount-fraction,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer .fs-selected-pricing-amount-cycle,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container .fs-selected-pricing-amount-fraction,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container .fs-selected-pricing-amount-cycle,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-currency-symbol .fs-selected-pricing-amount-fraction,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-currency-symbol .fs-selected-pricing-amount-cycle,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer .fs-selected-pricing-amount-fraction,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer .fs-selected-pricing-amount-cycle,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container .fs-selected-pricing-amount-fraction,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container .fs-selected-pricing-amount-cycle{display:block;font-size:12px}#root .fs-package .fs-selected-pricing-amount .fs-currency-symbol .fs-selected-pricing-amount-fraction,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer .fs-selected-pricing-amount-fraction,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container .fs-selected-pricing-amount-fraction,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-currency-symbol .fs-selected-pricing-amount-fraction,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer .fs-selected-pricing-amount-fraction,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container .fs-selected-pricing-amount-fraction{vertical-align:top}#root .fs-package .fs-selected-pricing-amount .fs-currency-symbol .fs-selected-pricing-amount-cycle,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer .fs-selected-pricing-amount-cycle,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container .fs-selected-pricing-amount-cycle,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-currency-symbol .fs-selected-pricing-amount-cycle,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer .fs-selected-pricing-amount-cycle,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container .fs-selected-pricing-amount-cycle{vertical-align:bottom}#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container{color:var(--fs-ds-theme-muted-text-color)}#root .fs-package .fs-selected-pricing-amount-free,#fs_pricing_app .fs-package .fs-selected-pricing-amount-free{font-size:48px}#root .fs-package .fs-selected-pricing-cycle,#fs_pricing_app .fs-package .fs-selected-pricing-cycle{margin-bottom:5px;text-transform:uppercase;color:var(--fs-ds-theme-muted-text-color)}#root .fs-package .fs-selected-pricing-license-quantity,#fs_pricing_app .fs-package .fs-selected-pricing-license-quantity{color:var(--fs-ds-theme-muted-text-color)}#root .fs-package .fs-selected-pricing-license-quantity .fs-tooltip,#fs_pricing_app .fs-package .fs-selected-pricing-license-quantity .fs-tooltip{margin-left:5px}#root .fs-package .fs-upgrade-button-container,#fs_pricing_app .fs-package .fs-upgrade-button-container{padding:0 13px;display:block}#root .fs-package .fs-upgrade-button-container .fs-upgrade-button,#fs_pricing_app .fs-package .fs-upgrade-button-container .fs-upgrade-button{margin-top:20px;margin-bottom:5px}#root .fs-package .fs-plan-features,#fs_pricing_app .fs-package .fs-plan-features{text-align:left;margin-left:13px}#root .fs-package .fs-plan-features li,#fs_pricing_app .fs-package .fs-plan-features li{font-size:16px;display:flex;margin-bottom:8px}#root .fs-package .fs-plan-features li:not(:first-child),#fs_pricing_app .fs-package .fs-plan-features li:not(:first-child){margin-top:8px}#root .fs-package .fs-plan-features li>span,#root .fs-package .fs-plan-features li .fs-tooltip,#fs_pricing_app .fs-package .fs-plan-features li>span,#fs_pricing_app .fs-package .fs-plan-features li .fs-tooltip{font-size:small;vertical-align:middle;display:inline-block}#root .fs-package .fs-plan-features li .fs-feature-title,#fs_pricing_app .fs-package .fs-plan-features li .fs-feature-title{margin:0 5px;color:var(--fs-ds-theme-muted-text-color);max-width:260px;overflow-wrap:break-word}#root .fs-package .fs-support-and-main-features,#fs_pricing_app .fs-package .fs-support-and-main-features{margin-top:12px;padding-top:18px;padding-bottom:18px;color:var(--fs-ds-theme-muted-text-color)}#root .fs-package .fs-support-and-main-features .fs-plan-support,#fs_pricing_app .fs-package .fs-support-and-main-features .fs-plan-support{margin-bottom:15px}#root .fs-package .fs-support-and-main-features .fs-plan-features-with-value li,#fs_pricing_app .fs-package .fs-support-and-main-features .fs-plan-features-with-value li{font-size:small}#root .fs-package .fs-support-and-main-features .fs-plan-features-with-value li .fs-feature-title,#fs_pricing_app .fs-package .fs-support-and-main-features .fs-plan-features-with-value li .fs-feature-title{margin:0 2px}#root .fs-package .fs-support-and-main-features .fs-plan-features-with-value li:not(:first-child),#fs_pricing_app .fs-package .fs-support-and-main-features .fs-plan-features-with-value li:not(:first-child){margin-top:5px}#root .fs-package .fs-plan-features-with-value,#fs_pricing_app .fs-package .fs-plan-features-with-value{color:var(--fs-ds-theme-muted-text-color)}#root .fs-package .fs-license-quantities,#fs_pricing_app .fs-package .fs-license-quantities{border-collapse:collapse;position:relative;width:100%}#root .fs-package .fs-license-quantities,#root .fs-package .fs-license-quantities input,#fs_pricing_app .fs-package .fs-license-quantities,#fs_pricing_app .fs-package .fs-license-quantities input{cursor:pointer}#root .fs-package .fs-license-quantities .fs-license-quantity-discount span,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-discount span{background-color:var(--fs-ds-theme-background-color);border:1px solid var(--fs-ds-theme-primary-accent-color);color:var(--fs-ds-theme-primary-accent-color);display:inline;padding:4px 8px;border-radius:4px;font-weight:700;margin:0 5px;white-space:nowrap}#root .fs-package .fs-license-quantities .fs-license-quantity-discount span.fs-license-quantity-no-discount,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-discount span.fs-license-quantity-no-discount{visibility:hidden}#root .fs-package .fs-license-quantities .fs-license-quantity-container,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-container{line-height:30px;border-top:1px solid var(--fs-ds-theme-background-shade);font-size:small;color:var(--fs-ds-theme-muted-text-color)}#root .fs-package .fs-license-quantities .fs-license-quantity-container:last-child,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-container:last-child{border-bottom:1px solid var(--fs-ds-theme-background-shade)}#root .fs-package .fs-license-quantities .fs-license-quantity-container:last-child.fs-license-quantity-selected,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-container:last-child.fs-license-quantity-selected{border-bottom-color:var(--fs-ds-theme-divider-color)}#root .fs-package .fs-license-quantities .fs-license-quantity-container.fs-license-quantity-selected,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-container.fs-license-quantity-selected{background:var(--fs-ds-theme-background-shade);border-color:var(--fs-ds-theme-divider-color);color:var(--fs-ds-theme-text-color)}#root .fs-package .fs-license-quantities .fs-license-quantity-container.fs-license-quantity-selected+.fs-license-quantity-container,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-container.fs-license-quantity-selected+.fs-license-quantity-container{border-top-color:var(--fs-ds-theme-divider-color)}#root .fs-package .fs-license-quantities .fs-license-quantity-container>td:not(.fs-license-quantity-discount):not(.fs-license-quantity-price),#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-container>td:not(.fs-license-quantity-discount):not(.fs-license-quantity-price){text-align:left}#root .fs-package .fs-license-quantities .fs-license-quantity,#root .fs-package .fs-license-quantities .fs-license-quantity-discount,#root .fs-package .fs-license-quantities .fs-license-quantity-price,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-discount,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-price{vertical-align:middle}#root .fs-package .fs-license-quantities .fs-license-quantity,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity{position:relative;white-space:nowrap}#root .fs-package .fs-license-quantities .fs-license-quantity input,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity input{position:relative;margin-top:-1px;margin-left:7px;margin-right:7px}#root .fs-package .fs-license-quantities .fs-license-quantity-price,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-price{position:relative;margin-right:auto;padding-right:7px;white-space:nowrap;font-variant-numeric:tabular-nums;text-align:right}#root .fs-package.fs-free-plan .fs-license-quantity-container:not(:last-child),#fs_pricing_app .fs-package.fs-free-plan .fs-license-quantity-container:not(:last-child){border-color:transparent}#root .fs-package .fs-most-popular,#fs_pricing_app .fs-package .fs-most-popular{display:none}#root .fs-package.fs-featured-plan .fs-most-popular,#fs_pricing_app .fs-package.fs-featured-plan .fs-most-popular{display:block;line-height:2.8em;margin-top:-2.8em;border-radius:10px 10px 0 0;color:var(--fs-ds-theme-text-color);background:var(--fs-ds-theme-package-popular-background);text-transform:uppercase;font-size:14px}#root .fs-package.fs-featured-plan .fs-plan-title,#fs_pricing_app .fs-package.fs-featured-plan .fs-plan-title{color:var(--fs-ds-theme-dark-background-text-color);background:var(--fs-ds-theme-primary-accent-color);border-top-color:var(--fs-ds-theme-primary-accent-color);border-bottom-color:var(--fs-ds-theme-primary-accent-color)}#root .fs-package.fs-featured-plan .fs-selected-pricing-license-quantity,#fs_pricing_app .fs-package.fs-featured-plan .fs-selected-pricing-license-quantity{color:var(--fs-ds-theme-primary-accent-color)}#root .fs-package.fs-featured-plan .fs-license-quantity-discount span,#fs_pricing_app .fs-package.fs-featured-plan .fs-license-quantity-discount span{background:var(--fs-ds-theme-primary-accent-color);color:var(--fs-ds-theme-dark-background-text-color)}#root .fs-package.fs-featured-plan .fs-license-quantities .fs-license-quantity-selected,#fs_pricing_app .fs-package.fs-featured-plan .fs-license-quantities .fs-license-quantity-selected{background:var(--fs-ds-theme-primary-accent-color);border-color:var(--fs-ds-theme-primary-accent-color);color:var(--fs-ds-theme-dark-background-text-color)}#root .fs-package.fs-featured-plan .fs-license-quantities .fs-license-quantity-selected+.fs-license-quantity-container,#fs_pricing_app .fs-package.fs-featured-plan .fs-license-quantities .fs-license-quantity-selected+.fs-license-quantity-container{border-top-color:var(--fs-ds-theme-primary-accent-color)}#root .fs-package.fs-featured-plan .fs-license-quantities .fs-license-quantity-selected:last-child,#fs_pricing_app .fs-package.fs-featured-plan .fs-license-quantities .fs-license-quantity-selected:last-child{border-bottom-color:var(--fs-ds-theme-primary-accent-color)}#root .fs-package.fs-featured-plan .fs-license-quantities .fs-license-quantity-selected .fs-license-quantity-discount span,#fs_pricing_app .fs-package.fs-featured-plan .fs-license-quantities .fs-license-quantity-selected .fs-license-quantity-discount span{background:var(--fs-ds-theme-background-color);color:var(--fs-ds-theme-primary-accent-color-hover)}\n',""]);const s=o},700:(e,t,n)=>{"use strict";n.d(t,{Z:()=>s});var a=n(81),r=n.n(a),i=n(645),o=n.n(i)()(r());o.push([e.id,'#root .fs-section--packages,#fs_pricing_app .fs-section--packages{display:inline-block;width:100%;position:relative}#root .fs-section--packages .fs-packages-menu,#fs_pricing_app .fs-section--packages .fs-packages-menu{display:none;flex-wrap:wrap;justify-content:center}#root .fs-section--packages .fs-packages-tab,#fs_pricing_app .fs-section--packages .fs-packages-tab{display:none}#root .fs-section--packages .fs-package-tab,#fs_pricing_app .fs-section--packages .fs-package-tab{display:inline-block;flex:1}#root .fs-section--packages .fs-package-tab a,#fs_pricing_app .fs-section--packages .fs-package-tab a{display:block;padding:4px 10px 7px;border-bottom:2px solid transparent;color:#000;text-align:center;text-decoration:none}#root .fs-section--packages .fs-package-tab.fs-package-tab--selected a,#fs_pricing_app .fs-section--packages .fs-package-tab.fs-package-tab--selected a{border-color:#0085ba}#root .fs-section--packages .fs-packages-nav,#fs_pricing_app .fs-section--packages .fs-packages-nav{position:relative;overflow:hidden;margin:auto}#root .fs-section--packages .fs-packages-nav:before,#root .fs-section--packages .fs-packages-nav:after,#fs_pricing_app .fs-section--packages .fs-packages-nav:before,#fs_pricing_app .fs-section--packages .fs-packages-nav:after{position:absolute;top:0;bottom:0;width:60px;margin-bottom:32px}#root .fs-section--packages .fs-packages-nav:before,#fs_pricing_app .fs-section--packages .fs-packages-nav:before{z-index:1}#root .fs-section--packages .fs-packages-nav.fs-has-previous-plan:before,#fs_pricing_app .fs-section--packages .fs-packages-nav.fs-has-previous-plan:before{content:"";left:0;background:linear-gradient(to right,#cccccc96,transparent)}#root .fs-section--packages .fs-packages-nav.fs-has-next-plan:after,#fs_pricing_app .fs-section--packages .fs-packages-nav.fs-has-next-plan:after{content:"";right:0;background:linear-gradient(to left,#cccccc96,transparent)}#root .fs-section--packages .fs-packages-nav.fs-has-featured-plan:before,#root .fs-section--packages .fs-packages-nav.fs-has-featured-plan:after,#fs_pricing_app .fs-section--packages .fs-packages-nav.fs-has-featured-plan:before,#fs_pricing_app .fs-section--packages .fs-packages-nav.fs-has-featured-plan:after{top:2.8em}#root .fs-section--packages .fs-prev-package,#root .fs-section--packages .fs-next-package,#fs_pricing_app .fs-section--packages .fs-prev-package,#fs_pricing_app .fs-section--packages .fs-next-package{position:absolute;top:50%;margin-top:-11px;cursor:pointer;font-size:48px;z-index:1}#root .fs-section--packages .fs-prev-package,#fs_pricing_app .fs-section--packages .fs-prev-package{visibility:hidden;z-index:2}#root .fs-section--packages .fs-has-featured-plan .fs-packages,#fs_pricing_app .fs-section--packages .fs-has-featured-plan .fs-packages{margin-top:2.8em}#root .fs-section--packages .fs-packages,#fs_pricing_app .fs-section--packages .fs-packages{width:auto;display:flex;flex-direction:row;margin-left:auto;margin-right:auto;margin-bottom:30px;border-top-right-radius:10px;position:relative;transition:left .5s ease,right .5s ease;padding-top:5px}#root .fs-section--packages .fs-packages:before,#fs_pricing_app .fs-section--packages .fs-packages:before{content:"";position:absolute;top:0;right:0;bottom:0;width:100px;height:100px}@media only screen and (max-width: 768px){#root .fs-section--plans-and-pricing .fs-section--packages .fs-next-package,#root .fs-section--plans-and-pricing .fs-section--packages .fs-prev-package,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-next-package,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-prev-package{display:none}#root .fs-section--plans-and-pricing .fs-section--packages .fs-packages-menu,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-packages-menu{display:block;font-size:24px;margin:0 auto 10px}#root .fs-section--plans-and-pricing .fs-section--packages .fs-packages-tab,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-packages-tab{display:flex;font-size:18px;margin:0 auto 10px}#root .fs-section--plans-and-pricing .fs-section--packages .fs-packages .fs-most-popular,#root .fs-section--plans-and-pricing .fs-section--packages .fs-package .fs-most-popular,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-packages .fs-most-popular,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-package .fs-most-popular{display:none}#root .fs-section--plans-and-pricing .fs-section--packages .fs-has-featured-plan .fs-packages,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-has-featured-plan .fs-packages{margin-top:0}}@media only screen and (max-width: 455px){#root .fs-section--plans-and-pricing .fs-section--packages .fs-packages .fs-package,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-packages .fs-package{width:100%}#root .fs-section--plans-and-pricing,#fs_pricing_app .fs-section--plans-and-pricing{padding:10px}}@media only screen and (max-width: 375px){#root .fs-section--plans-and-pricing .fs-section--packages .fs-packages .fs-package,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-packages .fs-package{width:100%}}\n',""]);const s=o},302:(e,t,n)=>{"use strict";n.d(t,{Z:()=>s});var a=n(81),r=n.n(a),i=n(645),o=n.n(i)()(r());o.push([e.id,'#root .fs-tooltip,#fs_pricing_app .fs-tooltip{cursor:help;position:relative;color:inherit}#root .fs-tooltip .fs-tooltip-message,#fs_pricing_app .fs-tooltip .fs-tooltip-message{position:absolute;width:200px;background:var(--fs-ds-theme-background-darkest);z-index:1;display:none;border-radius:4px;color:var(--fs-ds-theme-dark-background-text-color);padding:8px;text-align:left;line-height:18px}#root .fs-tooltip .fs-tooltip-message:before,#fs_pricing_app .fs-tooltip .fs-tooltip-message:before{content:"";position:absolute;z-index:1}#root .fs-tooltip .fs-tooltip-message:not(.fs-tooltip-message--position-none),#fs_pricing_app .fs-tooltip .fs-tooltip-message:not(.fs-tooltip-message--position-none){display:block}#root .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-right,#fs_pricing_app .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-right{transform:translateY(-50%);left:30px;top:8px}#root .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-right:before,#fs_pricing_app .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-right:before{left:-8px;top:50%;margin-top:-6px;border-top:6px solid transparent;border-bottom:6px solid transparent;border-right:8px solid var(--fs-ds-theme-background-darkest)}#root .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-top,#fs_pricing_app .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-top{left:50%;bottom:30px;transform:translate(-50%)}#root .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-top:before,#fs_pricing_app .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-top:before{left:50%;bottom:-8px;margin-left:-6px;border-right:6px solid transparent;border-left:6px solid transparent;border-top:8px solid var(--fs-ds-theme-background-darkest)}#root .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-top-right,#fs_pricing_app .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-top-right{right:-10px;bottom:30px}#root .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-top-right:before,#fs_pricing_app .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-top-right:before{right:10px;bottom:-8px;margin-left:-6px;border-right:6px solid transparent;border-left:6px solid transparent;border-top:8px solid var(--fs-ds-theme-background-darkest)}\n',""]);const s=o},645:e=>{"use strict";e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var n="",a=void 0!==t[5];return t[4]&&(n+="@supports (".concat(t[4],") {")),t[2]&&(n+="@media ".concat(t[2]," {")),a&&(n+="@layer".concat(t[5].length>0?" ".concat(t[5]):""," {")),n+=e(t),a&&(n+="}"),t[2]&&(n+="}"),t[4]&&(n+="}"),n})).join("")},t.i=function(e,n,a,r,i){"string"==typeof e&&(e=[[null,e,void 0]]);var o={};if(a)for(var s=0;s0?" ".concat(u[5]):""," {").concat(u[1],"}")),u[5]=i),n&&(u[2]?(u[1]="@media ".concat(u[2]," {").concat(u[1],"}"),u[2]=n):u[2]=n),r&&(u[4]?(u[1]="@supports (".concat(u[4],") {").concat(u[1],"}"),u[4]=r):u[4]="".concat(r)),t.push(u))}},t}},81:e=>{"use strict";e.exports=function(e){return e[1]}},867:(e,t,n)=>{let a=document.getElementById("fs_pricing_wrapper");a&&a.dataset&&a.dataset.publicUrl&&(n.p=a.dataset.publicUrl)},738:e=>{function t(e){return!!e.constructor&&"function"==typeof e.constructor.isBuffer&&e.constructor.isBuffer(e)}e.exports=function(e){return null!=e&&(t(e)||function(e){return"function"==typeof e.readFloatLE&&"function"==typeof e.slice&&t(e.slice(0,0))}(e)||!!e._isBuffer)}},568:(e,t,n)=>{var a,r,i,o,s;a=n(12),r=n(487).utf8,i=n(738),o=n(487).bin,(s=function(e,t){e.constructor==String?e=t&&"binary"===t.encoding?o.stringToBytes(e):r.stringToBytes(e):i(e)?e=Array.prototype.slice.call(e,0):Array.isArray(e)||e.constructor===Uint8Array||(e=e.toString());for(var n=a.bytesToWords(e),l=8*e.length,c=1732584193,u=-271733879,f=-1732584194,p=271733878,d=0;d>>24)|4278255360&(n[d]<<24|n[d]>>>8);n[l>>>5]|=128<>>9<<4)]=l;var m=s._ff,g=s._gg,h=s._hh,b=s._ii;for(d=0;d>>0,u=u+v>>>0,f=f+k>>>0,p=p+_>>>0}return a.endian([c,u,f,p])})._ff=function(e,t,n,a,r,i,o){var s=e+(t&n|~t&a)+(r>>>0)+o;return(s<>>32-i)+t},s._gg=function(e,t,n,a,r,i,o){var s=e+(t&a|n&~a)+(r>>>0)+o;return(s<>>32-i)+t},s._hh=function(e,t,n,a,r,i,o){var s=e+(t^n^a)+(r>>>0)+o;return(s<>>32-i)+t},s._ii=function(e,t,n,a,r,i,o){var s=e+(n^(t|~a))+(r>>>0)+o;return(s<>>32-i)+t},s._blocksize=16,s._digestsize=16,e.exports=function(e,t){if(null==e)throw new Error("Illegal argument "+e);var n=a.wordsToBytes(s(e,t));return t&&t.asBytes?n:t&&t.asString?o.bytesToString(n):a.bytesToHex(n)}},418:e=>{"use strict";var t=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,a=Object.prototype.propertyIsEnumerable;function r(e){if(null==e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t["_"+String.fromCharCode(n)]=n;if("0123456789"!==Object.getOwnPropertyNames(t).map((function(e){return t[e]})).join(""))return!1;var a={};return"abcdefghijklmnopqrst".split("").forEach((function(e){a[e]=e})),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},a)).join("")}catch(e){return!1}}()?Object.assign:function(e,i){for(var o,s,l=r(e),c=1;c{"use strict";var a=n(414);function r(){}function i(){}i.resetWarningCache=r,e.exports=function(){function e(e,t,n,r,i,o){if(o!==a){var s=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw s.name="Invariant Violation",s}}function t(){return e}e.isRequired=e;var n={array:e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:i,resetWarningCache:r};return n.PropTypes=n,n}},697:(e,t,n)=>{e.exports=n(703)()},414:e=>{"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},448:(e,t,n)=>{"use strict";var a=n(294),r=n(418),i=n(840);function o(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n
'; return true; } #endregion /** * Add in-page JavaScript to inject the Freemius tabs into * the module's setting tabs section. * * @author Vova Feldman (@svovaf) * @since 1.2.2.7 */ function _add_freemius_tabs() { $this->_logger->entrance(); if ( ! $this->should_page_include_tabs() ) { return; } $params = array( 'id' => $this->_module_id ); fs_require_once_template( 'tabs.php', $params ); } #endregion #-------------------------------------------------------------------------------- #region Customizer Integration for Themes #-------------------------------------------------------------------------------- /** * @author Vova Feldman (@svovaf) * @since 1.2.2.7 * * @param WP_Customize_Manager $customizer */ function _customizer_register( $customizer ) { $this->_logger->entrance(); if ( $this->is_pricing_page_visible() ) { require_once WP_FS__DIR_INCLUDES . '/customizer/class-fs-customizer-upsell-control.php'; $customizer->add_section( 'freemius_upsell', array( 'title' => '★ ' . $this->get_text_inline( 'View paid features', 'view-paid-features' ), 'priority' => 1, ) ); $customizer->add_setting( 'freemius_upsell', array( 'sanitize_callback' => 'esc_html', ) ); $customizer->add_control( new FS_Customizer_Upsell_Control( $customizer, 'freemius_upsell', array( 'fs' => $this, 'section' => 'freemius_upsell', 'priority' => 100, ) ) ); } if ( $this->is_page_visible( 'contact' ) || $this->is_page_visible( 'support' ) ) { require_once WP_FS__DIR_INCLUDES . '/customizer/class-fs-customizer-support-section.php'; // Main Documentation Link In Customizer Root. $customizer->add_section( new FS_Customizer_Support_Section( $customizer, 'freemius_support', array( 'fs' => $this, 'priority' => 1000, ) ) ); } } #endregion /** * If the theme has a paid version, add some custom * styling to the theme's premium version (if exists) * to highlight that it's the premium version of the * same theme, making it easier for identification * after the user upgrades and upload it to the site. * * @author Vova Feldman (@svovaf) * @since 1.2.2.7 */ function _style_premium_theme() { $this->_logger->entrance(); if ( ! self::is_themes_page() ) { // Only include in the themes page. return; } if ( ! $this->has_paid_plan() ) { // Only include if has any paid plans. return; } $params = null; fs_require_once_template( '/js/jquery.content-change.php', $params ); $params = array( 'slug' => $this->_slug, 'id' => $this->_module_id, ); fs_require_template( '/js/style-premium-theme.php', $params ); } /** * This method will return the absolute URL of the module's local icon. * * When you are running your plugin or theme on a **localhost** environment, if the icon * is not found in the local assets folder, try to fetch the icon URL from Freemius. If not set and * it's a plugin hosted on WordPress.org, try fetching the icon URL from wordpress.org. * If an icon is found, this method will automatically attempt to download the icon and store it * in /freemius/assets/img/{slug}.{png|jpg|gif|svg}. * * It's important to mention that this method is NOT phoning home since the developer will deploy * the product with the local icon in the assets folder. The download process just simplifies * the process for the developer. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return string */ function get_local_icon_url() { global $fs_active_plugins; /** * @since 1.1.7.5 */ $local_path = $this->apply_filters( 'plugin_icon', false ); if ( is_string( $local_path ) ) { $icons = array( $local_path ); } else { $img_dir = WP_FS__DIR_IMG; // Locate the main assets folder. if ( ! empty( $fs_active_plugins->plugins ) ) { $plugin_or_theme_img_dir = ( $this->is_plugin() ? WP_PLUGIN_DIR : get_theme_root( get_stylesheet() ) ); foreach ( $fs_active_plugins->plugins as $sdk_path => &$data ) { if ( $data->plugin_path == $this->get_plugin_basename() ) { $img_dir = $plugin_or_theme_img_dir . '/' /** * The basename will be `themes` or the basename of a custom themes directory. * * @author Leo Fajardo (@leorw) * @since 2.2.3 */ . str_replace( '../' . basename( $plugin_or_theme_img_dir ) . '/', '', $sdk_path ) . '/assets/img'; break; } } } // Try to locate the icon in the assets folder. $icons = glob( fs_normalize_path( $img_dir . "/{$this->_slug}.*" ) ); if ( ! is_array( $icons ) || 0 === count( $icons ) ) { if ( ! WP_FS__IS_LOCALHOST && $this->is_theme() ) { $icons = array( fs_normalize_path( $img_dir . '/theme-icon.png' ) ); } else { $icon_found = false; $local_path = fs_normalize_path( "{$img_dir}/{$this->_slug}.png" ); if ( ! function_exists( 'get_filesystem_method' ) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; } $have_write_permissions = ( 'direct' === get_filesystem_method( array(), fs_normalize_path( $img_dir ) ) ); /** * IMPORTANT: THIS CODE WILL NEVER RUN AFTER THE PLUGIN IS IN THE REPO. * * This code will only be executed once during the testing * of the plugin in a local environment. The plugin icon file WILL * already exist in the assets folder when the plugin is deployed to * the repository. */ if ( WP_FS__IS_LOCALHOST && $have_write_permissions ) { // Fetch icon from Freemius. $icon = $this->fetch_remote_icon_url(); // Fetch icon from WordPress.org. if ( empty( $icon ) && $this->is_plugin() && $this->is_org_repo_compliant() ) { if ( ! function_exists( 'plugins_api' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; } $plugin_information = plugins_api( 'plugin_information', array( 'slug' => $this->_slug, 'fields' => array( 'sections' => false, 'tags' => false, 'icons' => true ) ) ); if ( ! is_wp_error( $plugin_information ) && isset( $plugin_information->icons ) && ! empty( $plugin_information->icons ) ) { /** * Get the smallest icon. * * @author Leo Fajardo (@leorw) * @since 1.2.2 */ $icon = end( $plugin_information->icons ); } } if ( ! empty( $icon ) ) { if ( 0 !== strpos( $icon, 'http' ) ) { $icon = 'http:' . $icon; } /** * Get a clean file extension, e.g.: "jpg" and not "jpg?rev=1305765". * * @author Leo Fajardo (@leorw) * @since 1.2.2 */ $ext = pathinfo( strtok( $icon, '?' ), PATHINFO_EXTENSION ); $local_path = fs_normalize_path( "{$img_dir}/{$this->_slug}.{$ext}" ); // Try to download the icon. $icon_found = fs_download_image( $icon, $local_path ); } } if ( ! $icon_found ) { // No icons found, fallback to default icon. if ( $have_write_permissions ) { // If have write permissions, copy default icon. copy( fs_normalize_path( $img_dir . "/{$this->_module_type}-icon.png" ), $local_path ); } else { // If doesn't have write permissions, use default icon path. $local_path = fs_normalize_path( $img_dir . "/{$this->_module_type}-icon.png" ); } } $icons = array( $local_path ); } } } $icon_dir = dirname( $icons[0] ); return fs_img_url( substr( $icons[0], strlen( $icon_dir ) ), $icon_dir ); } /** * Fetch module's extended info. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return object|mixed */ private function fetch_module_info() { return $this->get_api_plugin_scope()->get( 'info.json', false, WP_FS__TIME_WEEK_IN_SEC ); } /** * Fetch module's remote icon URL. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return string */ function fetch_remote_icon_url() { $info = $this->fetch_module_info(); return ( $this->is_api_result_object( $info, 'icon' ) && is_string( $info->icon ) ) ? $info->icon : ''; } #-------------------------------------------------------------------------------- #region GDPR #-------------------------------------------------------------------------------- /** * @author Leo Fajardo (@leorw) * @since 2.1.0 * * @param array $user_plugins * * @return string */ private function get_gdpr_admin_notice_string( $user_plugins ) { $this->_logger->entrance(); $addons = self::get_all_addons(); foreach ( $user_plugins as $user_plugin ) { $has_addons = isset( $addons[ $user_plugin->id ] ); if ( WP_FS__MODULE_TYPE_PLUGIN === $user_plugin->type && ! $has_addons ) { if ( $this->_module_id == $user_plugin->id ) { $addons = $this->get_addons(); $has_addons = ( ! empty( $addons ) ); } else { $plugin_api = FS_Api::instance( $user_plugin->id, 'plugin', $user_plugin->id, $user_plugin->public_key, ! $user_plugin->is_live, false, $this->get_sdk_version() ); $addons_result = $plugin_api->get( '/addons.json?enriched=true', true ); if ( $this->is_api_result_object( $addons_result, 'plugins' ) && is_array( $addons_result->plugins ) && ! empty( $addons_result->plugins ) ) { $has_addons = true; } } } $user_plugin->has_addons = $has_addons; } $is_single_parent_product = ( 1 === count( $user_plugins ) ); $multiple_products_text = ''; if ( $is_single_parent_product ) { $single_parent_product = reset( $user_plugins ); $thank_you = sprintf( "%s", $single_parent_product->id, sprintf( $single_parent_product->has_addons ? $this->get_text_inline( 'Thank you so much for using %s and its add-ons!', 'thank-you-for-using-product-and-its-addons' ) : $this->get_text_inline( 'Thank you so much for using %s!', 'thank-you-for-using-product' ), sprintf('%s', $single_parent_product->title) ) ); $already_opted_in = sprintf( $this->get_text_inline( "You've already opted-in to our usage-tracking, which helps us keep improving the %s.", 'already-opted-in-to-product-usage-tracking' ), ( WP_FS__MODULE_TYPE_THEME === $single_parent_product->type ) ? WP_FS__MODULE_TYPE_THEME : WP_FS__MODULE_TYPE_PLUGIN ); } else { $thank_you = $this->get_text_inline( 'Thank you so much for using our products!', 'thank-you-for-using-products' ); $already_opted_in = $this->get_text_inline( "You've already opted-in to our usage-tracking, which helps us keep improving them.", 'already-opted-in-to-products-usage-tracking' ); $products_and_add_ons = ''; foreach ( $user_plugins as $user_plugin ) { if ( ! empty( $products_and_add_ons ) ) { $products_and_add_ons .= ', '; } if ( ! $user_plugin->has_addons ) { $products_and_add_ons .= sprintf( "%s", $user_plugin->id, $user_plugin->title ); } else { $products_and_add_ons .= sprintf( "%s", $user_plugin->id, sprintf( $this->get_text_inline( '%s and its add-ons', 'product-and-its-addons' ), $user_plugin->title ) ); } } $multiple_products_text = sprintf( "%s: %s", $this->get_text_inline( 'Products', 'products' ), $products_and_add_ons ); } $actions = sprintf( '
  • %s - %s
  • %s - %s
', sprintf('', $this->get_text_inline( 'Yes', 'yes' ) ), $this->get_text_inline( 'send me security & feature updates, educational content and offers.', 'send-updates' ), sprintf('', $this->get_text_inline( 'No', 'no' ) ), sprintf( $this->get_text_inline( 'do %sNOT%s send me security & feature updates, educational content and offers.', 'do-not-send-updates' ), '', '' ) ); return sprintf( '%s %s %s', $thank_you, $already_opted_in, sprintf( $this->get_text_inline( 'Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)', 'due-to-gdpr-compliance-requirements' ), '', '' ) . '

' . '' . $this->get_text_inline( "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:", 'contact-for-updates' ) . '' . $actions . ( $is_single_parent_product ? '' : $multiple_products_text ) ); } /** * This method is called for opted-in users to fetch the is_marketing_allowed flag of the user for all the * plugins and themes they've opted in to. * * @author Leo Fajardo (@leorw) * @since 2.1.0 * * @param string $user_email * @param string $license_key * @param array $plugin_ids * @param string|null $license_key * * @return array|false */ private function fetch_user_marketing_flag_status_by_plugins( $user_email, $license_key, $plugin_ids ) { $request = array( 'method' => 'POST', 'body' => array(), 'timeout' => WP_FS__DEBUG_SDK ? 60 : 30, ); if ( is_string( $user_email ) ) { $request['body']['email'] = $user_email; } else { $request['body']['license_key'] = $license_key; } $result = array(); $url = WP_FS__ADDRESS . '/action/service/user_plugin/'; $total_plugin_ids = count( $plugin_ids ); $plugin_ids_count_per_request = 10; for ( $i = 1; $i <= $total_plugin_ids; $i += $plugin_ids_count_per_request ) { $plugin_ids_set = array_slice( $plugin_ids, $i - 1, $plugin_ids_count_per_request ); $request['body']['plugin_ids'] = $plugin_ids_set; $response = self::safe_remote_post( $url, $request, WP_FS__TIME_24_HOURS_IN_SEC, WP_FS__TIME_12_HOURS_IN_SEC ); if ( ! is_wp_error( $response ) ) { $decoded = is_string( $response['body'] ) ? json_decode( $response['body'] ) : null; if ( !is_object($decoded) || !isset($decoded->success) || true !== $decoded->success || !isset( $decoded->data ) || !is_array( $decoded->data ) ) { return false; } $result = array_merge( $result, $decoded->data ); } } return $result; } /** * @author Leo Fajardo (@leorw) * @since 2.1.0 */ function _maybe_show_gdpr_admin_notice() { if ( ! $this->is_user_in_admin() ) { return; } if ( ! $this->should_handle_gdpr_admin_notice() ) { return; } if ( ! $this->is_user_admin() ) { return; } require_once WP_FS__DIR_INCLUDES . '/class-fs-user-lock.php'; $lock = FS_User_Lock::instance(); /** * Try to acquire a 60-sec lock based on the WP user and thread/process ID. */ if ( ! $lock->try_lock( 60 ) ) { return; } /** * @var $current_wp_user WP_User */ $current_wp_user = self::_get_current_wp_user(); /** * @var FS_User $current_fs_user */ $current_fs_user = Freemius::_get_user_by_email( $current_wp_user->user_email ); $ten_years_in_sec = 10 * 365 * WP_FS__TIME_24_HOURS_IN_SEC; if ( ! is_object( $current_fs_user ) ) { // 10-year lock. $lock->lock( $ten_years_in_sec ); return; } $gdpr = FS_GDPR_Manager::instance(); if ( $gdpr->is_opt_in_notice_shown() ) { // 30-day lock. $lock->lock( 30 * WP_FS__TIME_24_HOURS_IN_SEC ); return; } if ( ! $gdpr->should_show_opt_in_notice() ) { // 10-year lock. $lock->lock( $ten_years_in_sec ); return; } $last_time_notice_shown = $gdpr->last_time_notice_was_shown(); $was_notice_shown_before = ( false !== $last_time_notice_shown ); if ( $was_notice_shown_before && 30 * WP_FS__TIME_24_HOURS_IN_SEC > time() - $last_time_notice_shown ) { // If the notice was shown before, show it again after 30 days from the last time it was shown. return; } /** * Find all plugin IDs that were installed by the current admin. */ $plugin_ids_map = self::get_user_opted_in_module_ids_map( $current_fs_user->id ); if ( empty( $plugin_ids_map )) { $lock->lock( $ten_years_in_sec ); return; } $user_plugins = $this->fetch_user_marketing_flag_status_by_plugins( $current_fs_user->email, null, array_keys( $plugin_ids_map ) ); if ( empty( $user_plugins ) ) { $lock->lock( is_array($user_plugins) ? $ten_years_in_sec : // Lock for 24-hours on errors. WP_FS__TIME_24_HOURS_IN_SEC ); return; } $has_unset_marketing_optin = false; foreach ( $user_plugins as $user_plugin ) { if ( true == $user_plugin->is_marketing_allowed ) { unset( $plugin_ids_map[ $user_plugin->plugin_id ] ); } if ( ! $has_unset_marketing_optin && is_null( $user_plugin->is_marketing_allowed ) ) { $has_unset_marketing_optin = true; } } if ( empty( $plugin_ids_map ) || ( $was_notice_shown_before && ! $has_unset_marketing_optin ) ) { $lock->lock( $ten_years_in_sec ); return; } $modules = array_merge( array_values( self::maybe_get_entities_account_option( 'plugins', array() ) ), array_values( self::maybe_get_entities_account_option( 'themes', array() ) ) ); foreach ( $modules as $module ) { if ( ! FS_Plugin::is_valid_id( $module->parent_plugin_id ) && isset( $plugin_ids_map[ $module->id ] ) ) { $plugin_ids_map[ $module->id ] = $module; } } $plugin_title = null; if ( 1 === count( $plugin_ids_map ) ) { $module = reset( $plugin_ids_map ); $plugin_title = $module->title; } $gdpr->add_opt_in_sticky_notice( $this->get_gdpr_admin_notice_string( $plugin_ids_map ), $plugin_title ); $this->add_gdpr_optin_ajax_handler_and_style(); $gdpr->notice_was_just_shown(); // 30-day lock. $lock->lock( 30 * WP_FS__TIME_24_HOURS_IN_SEC ); } /** * Prevents the GDPR opt-in admin notice from being added if the user has already chosen to allow or not allow * marketing. * * @author Leo Fajardo (@leorw) * @since 2.1.0 */ private function disable_opt_in_notice_and_lock_user() { FS_GDPR_Manager::instance()->disable_opt_in_notice(); require_once WP_FS__DIR_INCLUDES . '/class-fs-user-lock.php'; // 10-year lock. FS_User_Lock::instance()->lock( 10 * 365 * WP_FS__TIME_24_HOURS_IN_SEC ); } /** * @author Leo Fajardo (@leorw) * @since 2.5.4 */ static function _add_api_connectivity_notice_handler_js() { fs_require_once_template( 'api-connectivity-message-js.php' ); } /** * @author Leo Fajardo (@leorw) * @since 2.1.0 */ function _add_gdpr_optin_js() { $vars = array( 'id' => $this->_module_id ); fs_require_once_template( 'gdpr-optin-js.php', $vars ); } /** * @author Leo Fajardo (@leorw) * @since 2.1.0 */ function enqueue_gdpr_optin_notice_style() { fs_enqueue_local_style( 'fs_gdpr_optin_notice', '/admin/gdpr-optin-notice.css' ); } /** * @author Leo Fajardo (@leorw) * @since 2.1.0 */ function _maybe_add_gdpr_optin_ajax_handler() { $this->add_ajax_action( 'fetch_is_marketing_required_flag_value', array( &$this, '_fetch_is_marketing_required_flag_value_ajax_action' ) ); if ( FS_GDPR_Manager::instance()->is_opt_in_notice_shown() ) { $this->add_gdpr_optin_ajax_handler_and_style(); } } /** * @author Leo Fajardo (@leorw) * @since 2.1.0 */ function _fetch_is_marketing_required_flag_value_ajax_action() { $this->_logger->entrance(); $this->check_ajax_referer( 'fetch_is_marketing_required_flag_value' ); $license_key = fs_request_get_raw( 'license_key' ); if ( empty($license_key) ) { self::shoot_ajax_failure( $this->get_text_inline( 'License key is empty.', 'empty-license-key' ) ); } $user_plugins = $this->fetch_user_marketing_flag_status_by_plugins( null, $license_key, array( $this->_module_id ) ); if ( ! is_array( $user_plugins ) || empty($user_plugins) || !isset($user_plugins[0]->plugin_id) || $user_plugins[0]->plugin_id != $this->_module_id ) { /** * If faced an error or if the module ID do not match to the current module, ask for GDPR opt-in. * * @author Vova Feldman (@svovaf) */ self::shoot_ajax_success( array( 'is_marketing_allowed' => null, 'license_owner_id' => null ) ); } self::shoot_ajax_success( array( 'is_marketing_allowed' => $user_plugins[0]->is_marketing_allowed, 'license_owner_id' => ( isset( $user_plugins[0]->license_owner_id ) ? $user_plugins[0]->license_owner_id : null ) ) ); } /** * @author Leo Fajardo (@leorw) * @since 2.3.2 * * @param number[] $install_ids * * @return array { * An array of objects containing the installs' licenses owners data. * * @property number $id User ID. * @property string $email User email (can be masked email). * } */ private function fetch_installs_licenses_owners_data( $install_ids ) { $this->_logger->entrance(); $response = $this->get_api_user_scope()->get( '/licenses_owners.json?install_ids=' . implode( ',', $install_ids ) ); $license_owners = array(); if ( $this->is_api_result_object( $response, 'owners' ) ) { $license_owners = $response->owners; } return $license_owners; } /** * @author Leo Fajardo (@leorw) * @since 2.1.0 */ private function add_gdpr_optin_ajax_handler_and_style() { // Add GDPR action AJAX callback. $this->add_ajax_action( 'gdpr_optin_action', array( &$this, '_gdpr_optin_ajax_action' ) ); add_action( 'admin_footer', array( &$this, '_add_gdpr_optin_js' ) ); add_action( 'admin_enqueue_scripts', array( &$this, 'enqueue_gdpr_optin_notice_style' ) ); } /** * @author Leo Fajardo (@leorw) * @since 2.1.0 */ function _gdpr_optin_ajax_action() { $this->_logger->entrance(); $this->check_ajax_referer( 'gdpr_optin_action' ); if ( ! fs_request_has( 'is_marketing_allowed' ) || ! fs_request_has( 'plugin_ids' ) ) { self::shoot_ajax_failure(); } $current_wp_user = self::_get_current_wp_user(); $plugin_ids = fs_request_get( 'plugin_ids', array() ); if ( ! is_array( $plugin_ids ) || empty( $plugin_ids ) ) { self::shoot_ajax_failure(); } $modules = array_merge( array_values( self::maybe_get_entities_account_option( 'plugins', array() ) ), array_values( self::maybe_get_entities_account_option( 'themes', array() ) ) ); foreach ( $modules as $key => $module ) { if ( ! in_array( $module->id, $plugin_ids ) ) { unset( $modules[ $key ] ); } } if ( empty( $modules ) ) { self::shoot_ajax_failure(); } $user_api = $this->get_api_user_scope_by_user( Freemius::_get_user_by_email( $current_wp_user->user_email ) ); foreach ( $modules as $module ) { $user_api->call( "?plugin_id={$module->id}", 'put', array( 'is_marketing_allowed' => ( true == fs_request_get_bool( 'is_marketing_allowed' ) ) ) ); } FS_GDPR_Manager::instance()->remove_opt_in_notice(); require_once WP_FS__DIR_INCLUDES . '/class-fs-user-lock.php'; // 10-year lock. FS_User_Lock::instance()->lock( 10 * 365 * WP_FS__TIME_24_HOURS_IN_SEC ); self::shoot_ajax_success(); } /** * Checks if the GDPR admin notice should be handled. By default, this logic is off, unless the integrator adds the special 'handle_gdpr_admin_notice' filter. * * @author Vova Feldman (@svovaf) * @since 2.1.0 * * @return bool */ private function should_handle_gdpr_admin_notice() { return $this->apply_filters( 'handle_gdpr_admin_notice', // Default to false. false ); } #endregion #---------------------------------------------------------------------------------- #region Marketing #---------------------------------------------------------------------------------- /** * Check if current user purchased any other plugins before. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool */ function has_purchased_before() { // TODO: Implement has_purchased_before() method. throw new Exception( 'not implemented' ); } /** * Check if current user classified as an agency. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool */ function is_agency() { // TODO: Implement is_agency() method. throw new Exception( 'not implemented' ); } /** * Check if current user classified as a developer. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool */ function is_developer() { // TODO: Implement is_developer() method. throw new Exception( 'not implemented' ); } /** * Check if current user classified as a business. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool */ function is_business() { // TODO: Implement is_business() method. throw new Exception( 'not implemented' ); } #endregion #---------------------------------------------------------------------------------- #region Helper #---------------------------------------------------------------------------------- /** * If running with a secret key, assume it's the developer and show pending plans as well. * * @author Vova Feldman (@svovaf) * @since 2.1.2 * * @param string $path * * @return string */ function add_show_pending( $path ) { if ( ! $this->has_secret_key() ) { return $path; } return $path . ( false !== strpos( $path, '?' ) ? '&' : '?' ) . 'show_pending=true'; } #endregion } PK!0WW2libraries/freemius/includes/class-fs-user-lock.phpnu[_lock = new FS_Lock( "locked_{$current_user_id}" ); } /** * Try to acquire lock. If the lock is already set or is being acquired by another locker, don't do anything. * * @author Vova Feldman (@svovaf) * @since 2.1.0 * * @param int $expiration * * @return bool TRUE if successfully acquired lock. */ function try_lock( $expiration = 0 ) { return $this->_lock->try_lock( $expiration ); } /** * Acquire lock regardless if it's already acquired by another locker or not. * * @author Vova Feldman (@svovaf) * @since 2.1.0 * * @param int $expiration */ function lock( $expiration = 0 ) { $this->_lock->lock( $expiration ); } /** * Unlock the lock. * * @author Vova Feldman (@svovaf) * @since 2.1.0 */ function unlock() { $this->_lock->unlock(); } }PK!|eCeC/libraries/freemius/includes/class-fs-logger.phpnu[_id = $id; $caller = $bt[2]; if ( false !== strpos( $caller['file'], 'plugins' ) ) { $this->_file_start = strpos( $caller['file'], 'plugins' ) + strlen( 'plugins/' ); } else { $this->_file_start = strpos( $caller['file'], 'themes' ) + strlen( 'themes/' ); } if ( $on ) { $this->on(); } if ( $echo ) { $this->echo_on(); } } /** * @param string $id * @param bool $on * @param bool $echo * * @return FS_Logger */ public static function get_logger( $id, $on = false, $echo = false ) { $id = strtolower( $id ); if ( ! isset( self::$_processID ) ) { self::init(); } if ( ! isset( self::$LOGGERS[ $id ] ) ) { self::$LOGGERS[ $id ] = new FS_Logger( $id, $on, $echo ); } return self::$LOGGERS[ $id ]; } /** * Initialize logging global info. * * @author Vova Feldman (@svovaf) * @since 1.2.1.6 */ private static function init() { self::$_ownerName = function_exists( 'get_current_user' ) ? get_current_user() : 'unknown'; self::$_isStorageLoggingOn = ( 1 == get_option( 'fs_storage_logger', 0 ) ); self::$_abspathLength = strlen( ABSPATH ); self::$_processID = mt_rand( 0, 32000 ); // Process ID may be `false` on errors. if ( ! is_numeric( self::$_processID ) ) { self::$_processID = 0; } } private static function hook_footer() { if ( self::$_HOOKED_FOOTER ) { return; } if ( is_admin() ) { add_action( 'admin_footer', 'FS_Logger::dump', 100 ); } else { add_action( 'wp_footer', 'FS_Logger::dump', 100 ); } } function is_on() { return $this->_on; } function on() { $this->_on = true; if ( ! function_exists( 'dbDelta' ) ) { require_once ABSPATH . 'wp-admin/includes/upgrade.php'; } self::hook_footer(); } function echo_on() { $this->on(); $this->_echo = true; } function is_echo_on() { return $this->_echo; } function get_id() { return $this->_id; } function get_file() { return $this->_file_start; } private function _log( &$message, $type, $wrapper = false ) { if ( ! $this->is_on() ) { return; } $bt = debug_backtrace(); $depth = $wrapper ? 3 : 2; while ( $depth < count( $bt ) - 1 && 'eval' === $bt[ $depth ]['function'] ) { $depth ++; } $caller = $bt[ $depth ]; /** * Retrieve the correct call file & line number from backtrace * when logging from a wrapper method. * * @author Vova Feldman * @since 1.2.1.6 */ if ( empty( $caller['line'] ) ) { $depth --; while ( $depth >= 0 ) { if ( ! empty( $bt[ $depth ]['line'] ) ) { $caller['line'] = $bt[ $depth ]['line']; $caller['file'] = $bt[ $depth ]['file']; break; } } } $log = array_merge( $caller, array( 'cnt' => self::$CNT ++, 'logger' => $this, 'timestamp' => microtime( true ), 'log_type' => $type, 'msg' => $message, ) ); if ( self::$_isStorageLoggingOn ) { $this->db_log( $type, $message, self::$CNT, $caller ); } self::$LOG[] = $log; if ( $this->is_echo_on() && ! Freemius::is_ajax() ) { echo self::format_html( $log ) . "\n"; } } function log( $message, $wrapper = false ) { $this->_log( $message, 'log', $wrapper ); } function info( $message, $wrapper = false ) { $this->_log( $message, 'info', $wrapper ); } function warn( $message, $wrapper = false ) { $this->_log( $message, 'warn', $wrapper ); } function error( $message, $wrapper = false ) { $this->_log( $message, 'error', $wrapper ); } /** * Log API error. * * @author Vova Feldman (@svovaf) * @since 1.2.1.5 * * @param mixed $api_result * @param bool $wrapper */ function api_error( $api_result, $wrapper = false ) { $message = ''; if ( is_object( $api_result ) && ! empty( $api_result->error ) && ! empty( $api_result->error->message ) ) { $message = $api_result->error->message; } else if ( is_object( $api_result ) ) { $message = var_export( $api_result, true ); } else if ( is_string( $api_result ) ) { $message = $api_result; } else if ( empty( $api_result ) ) { $message = 'Empty API result.'; } $message = 'API Error: ' . $message; $this->_log( $message, 'error', $wrapper ); } function entrance( $message = '', $wrapper = false ) { $msg = 'Entrance' . ( empty( $message ) ? '' : ' > ' ) . $message; $this->_log( $msg, 'log', $wrapper ); } function departure( $message = '', $wrapper = false ) { $msg = 'Departure' . ( empty( $message ) ? '' : ' > ' ) . $message; $this->_log( $msg, 'log', $wrapper ); } #-------------------------------------------------------------------------------- #region Log Formatting #-------------------------------------------------------------------------------- private static function format( $log, $show_type = true ) { return '[' . str_pad( $log['cnt'], strlen( self::$CNT ), '0', STR_PAD_LEFT ) . '] [' . $log['logger']->_id . '] ' . ( $show_type ? '[' . $log['log_type'] . ']' : '' ) . ( ! empty( $log['class'] ) ? $log['class'] . $log['type'] : '' ) . $log['function'] . ' >> ' . $log['msg'] . ( isset( $log['file'] ) ? ' (' . substr( $log['file'], $log['logger']->_file_start ) . ' ' . $log['line'] . ') ' : '' ) . ' [' . $log['timestamp'] . ']'; } private static function format_html( $log ) { return '
[' . $log['cnt'] . '] [' . $log['logger']->_id . '] [' . $log['log_type'] . '] ' . ( ! empty( $log['class'] ) ? $log['class'] . $log['type'] : '' ) . $log['function'] . ' >> ' . esc_html( $log['msg'] ) . '' . ( isset( $log['file'] ) ? ' (' . substr( $log['file'], $log['logger']->_file_start ) . ' ' . $log['line'] . ')' : '' ) . ' [' . $log['timestamp'] . ']
'; } #endregion static function dump() { ?> prefix}fs_logger"; /** * Drop logging table in any case. */ $result = $wpdb->query( "DROP TABLE IF EXISTS $table;" ); if ( $is_on ) { /** * Create logging table. * * NOTE: * dbDelta must use KEY and not INDEX for indexes. * * @link https://core.trac.wordpress.org/ticket/2695 */ $result = $wpdb->query( "CREATE TABLE IF NOT EXISTS {$table} ( `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, `process_id` INT UNSIGNED NOT NULL, `user_name` VARCHAR(64) NOT NULL, `logger` VARCHAR(128) NOT NULL, `log_order` INT UNSIGNED NOT NULL, `type` ENUM('log','info','warn','error') NOT NULL DEFAULT 'log', `message` TEXT NOT NULL, `file` VARCHAR(256) NOT NULL, `line` INT UNSIGNED NOT NULL, `function` VARCHAR(256) NOT NULL, `request_type` ENUM('call','ajax','cron') NOT NULL DEFAULT 'call', `request_url` VARCHAR(1024) NOT NULL, `created` DECIMAL(16, 6) NOT NULL, PRIMARY KEY (`id`), KEY `process_id` (`process_id` ASC), KEY `process_logger` (`process_id` ASC, `logger` ASC), KEY `function` (`function` ASC), KEY `type` (`type` ASC))" ); } if ( false !== $result ) { update_option( 'fs_storage_logger', ( $is_on ? 1 : 0 ) ); self::$_isStorageLoggingOn = $is_on; } return ( false !== $result ); } /** * @author Vova Feldman (@svovaf) * @since 1.2.1.6 * * @param string $type * @param string $message * @param int $log_order * @param array $caller * * @return false|int */ private function db_log( &$type, &$message, &$log_order, &$caller ) { global $wpdb; $request_type = 'call'; if ( defined( 'DOING_CRON' ) && DOING_CRON ) { $request_type = 'cron'; } else if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { $request_type = 'ajax'; } $request_url = WP_FS__IS_HTTP_REQUEST ? $_SERVER['REQUEST_URI'] : ''; return $wpdb->insert( "{$wpdb->prefix}fs_logger", array( 'process_id' => self::$_processID, 'user_name' => self::$_ownerName, 'logger' => $this->_id, 'log_order' => $log_order, 'type' => $type, 'request_type' => $request_type, 'request_url' => $request_url, 'message' => $message, 'file' => isset( $caller['file'] ) ? substr( $caller['file'], self::$_abspathLength ) : '', 'line' => $caller['line'], 'function' => ( ! empty( $caller['class'] ) ? $caller['class'] . $caller['type'] : '' ) . $caller['function'], 'created' => microtime( true ), ) ); } /** * Persistent DB logger columns. * * @var array */ private static $_log_columns = array( 'id', 'process_id', 'user_name', 'logger', 'log_order', 'type', 'message', 'file', 'line', 'function', 'request_type', 'request_url', 'created', ); /** * Create DB logs query. * * @author Vova Feldman (@svovaf) * @since 1.2.1.6 * * @param bool $filters * @param int $limit * @param int $offset * @param bool $order * @param bool $escape_eol * * @return string */ private static function build_db_logs_query( $filters = false, $limit = 200, $offset = 0, $order = false, $escape_eol = false ) { global $wpdb; $select = '*'; if ( $escape_eol ) { $select = ''; for ( $i = 0, $len = count( self::$_log_columns ); $i < $len; $i ++ ) { if ( $i > 0 ) { $select .= ', '; } if ( 'message' !== self::$_log_columns[ $i ] ) { $select .= self::$_log_columns[ $i ]; } else { $select .= 'REPLACE(message , \'\n\', \' \') AS message'; } } } $query = "SELECT {$select} FROM {$wpdb->prefix}fs_logger"; if ( is_array( $filters ) ) { $criteria = array(); if ( ! empty( $filters['type'] ) && 'all' !== $filters['type'] ) { $filters['type'] = strtolower( $filters['type'] ); switch ( $filters['type'] ) { case 'warn_error': $criteria[] = array( 'col' => 'type', 'val' => array( 'warn', 'error' ) ); break; case 'error': case 'warn': $criteria[] = array( 'col' => 'type', 'val' => $filters['type'] ); break; case 'info': default: $criteria[] = array( 'col' => 'type', 'val' => array( 'info', 'log' ) ); break; } } if ( ! empty( $filters['request_type'] ) ) { $filters['request_type'] = strtolower( $filters['request_type'] ); if ( in_array( $filters['request_type'], array( 'call', 'ajax', 'cron' ) ) ) { $criteria[] = array( 'col' => 'request_type', 'val' => $filters['request_type'] ); } } if ( ! empty( $filters['file'] ) ) { $criteria[] = array( 'col' => 'file', 'op' => 'LIKE', 'val' => '%' . esc_sql( $filters['file'] ), ); } if ( ! empty( $filters['function'] ) ) { $criteria[] = array( 'col' => 'function', 'op' => 'LIKE', 'val' => '%' . esc_sql( $filters['function'] ), ); } if ( ! empty( $filters['process_id'] ) && is_numeric( $filters['process_id'] ) ) { $criteria[] = array( 'col' => 'process_id', 'val' => $filters['process_id'] ); } if ( ! empty( $filters['logger'] ) ) { $criteria[] = array( 'col' => 'logger', 'op' => 'LIKE', 'val' => '%' . esc_sql( $filters['logger'] ) . '%', ); } if ( ! empty( $filters['message'] ) ) { $criteria[] = array( 'col' => 'message', 'op' => 'LIKE', 'val' => '%' . esc_sql( $filters['message'] ) . '%', ); } if ( 0 < count( $criteria ) ) { $query .= "\nWHERE\n"; $first = true; foreach ( $criteria as $c ) { if ( ! $first ) { $query .= "AND\n"; } if ( is_array( $c['val'] ) ) { $operator = 'IN'; for ( $i = 0, $len = count( $c['val'] ); $i < $len; $i ++ ) { $c['val'][ $i ] = "'" . esc_sql( $c['val'][ $i ] ) . "'"; } $val = '(' . implode( ',', $c['val'] ) . ')'; } else { $operator = ! empty( $c['op'] ) ? $c['op'] : '='; $val = "'" . esc_sql( $c['val'] ) . "'"; } $query .= "`{$c['col']}` {$operator} {$val}\n"; $first = false; } } } if ( ! is_array( $order ) ) { $order = array( 'col' => 'id', 'order' => 'desc' ); } $query .= " ORDER BY {$order['col']} {$order['order']} LIMIT {$offset},{$limit}"; return $query; } /** * Load logs from DB. * * @author Vova Feldman (@svovaf) * @since 1.2.1.6 * * @param bool $filters * @param int $limit * @param int $offset * @param bool $order * * @return object[]|null */ public static function load_db_logs( $filters = false, $limit = 200, $offset = 0, $order = false ) { global $wpdb; $query = self::build_db_logs_query( $filters, $limit, $offset, $order ); return $wpdb->get_results( $query ); } /** * Load logs from DB. * * @author Vova Feldman (@svovaf) * @since 1.2.1.6 * * @param bool $filters * @param string $filename * @param int $limit * @param int $offset * @param bool $order * * @return false|string File download URL or false on failure. */ public static function download_db_logs( $filters = false, $filename = '', $limit = 10000, $offset = 0, $order = false ) { global $wpdb; $query = self::build_db_logs_query( $filters, $limit, $offset, $order, true ); $upload_dir = wp_upload_dir(); if ( empty( $filename ) ) { $filename = 'fs-logs-' . date( 'Y-m-d_H-i-s', WP_FS__SCRIPT_START_TIME ) . '.csv'; } $filepath = rtrim( $upload_dir['path'], '/' ) . "/{$filename}"; $query .= " INTO OUTFILE '{$filepath}' FIELDS TERMINATED BY '\t' ESCAPED BY '\\\\' OPTIONALLY ENCLOSED BY '\"' LINES TERMINATED BY '\\n'"; $columns = ''; for ( $i = 0, $len = count( self::$_log_columns ); $i < $len; $i ++ ) { if ( $i > 0 ) { $columns .= ', '; } $columns .= "'" . self::$_log_columns[ $i ] . "'"; } $query = "SELECT {$columns} UNION ALL " . $query; $result = $wpdb->query( $query ); if ( false === $result ) { return false; } return rtrim( $upload_dir['url'], '/' ) . '/' . $filename; } /** * @author Vova Feldman (@svovaf) * @since 1.2.1.6 * * @param string $filename * * @return string */ public static function get_logs_download_url( $filename = '' ) { $upload_dir = wp_upload_dir(); if ( empty( $filename ) ) { $filename = 'fs-logs-' . date( 'Y-m-d_H-i-s', WP_FS__SCRIPT_START_TIME ) . '.csv'; } return rtrim( $upload_dir['url'], '/' ) . $filename; } #endregion } PK!p2WW+libraries/freemius/includes/debug/index.phpnu[libraries/freemius/includes/debug/class-fs-debug-bar-panel.phpnu[title( 'Freemius' ); // @phpstan-ignore-line } public static function requests_count() { if ( class_exists( 'Freemius_Api_WordPress' ) ) { $logger = Freemius_Api_WordPress::GetLogger(); } else { $logger = array(); } return number_format( count( $logger ) ); } public static function total_time() { if ( class_exists( 'Freemius_Api_WordPress' ) ) { $logger = Freemius_Api_WordPress::GetLogger(); } else { $logger = array(); } $total_time = .0; foreach ( $logger as $l ) { $total_time += $l['total']; } return number_format( 100 * $total_time, 2 ) . ' ' . fs_text_x_inline( 'ms', 'milliseconds' ); } public function render() { ?>



_id = $pID; $this->_public = $pPublic; $this->_secret = $pSecret; $this->_scope = $pScope; $this->_isSandbox = $pIsSandbox; } public function IsSandbox() { return $this->_isSandbox; } function CanonizePath( $pPath ) { $pPath = trim( $pPath, '/' ); $query_pos = strpos( $pPath, '?' ); $query = ''; if ( false !== $query_pos ) { $query = substr( $pPath, $query_pos ); $pPath = substr( $pPath, 0, $query_pos ); } // Trim '.json' suffix. $format_length = strlen( '.' . self::FORMAT ); $start = $format_length * ( - 1 ); //negative if ( substr( strtolower( $pPath ), $start ) === ( '.' . self::FORMAT ) ) { $pPath = substr( $pPath, 0, strlen( $pPath ) - $format_length ); } switch ( $this->_scope ) { case 'app': $base = '/apps/' . $this->_id; break; case 'developer': $base = '/developers/' . $this->_id; break; case 'user': $base = '/users/' . $this->_id; break; case 'plugin': $base = '/plugins/' . $this->_id; break; case 'install': $base = '/installs/' . $this->_id; break; default: throw new Freemius_Exception( 'Scope not implemented.' ); } return '/v' . FS_API__VERSION . $base . ( ! empty( $pPath ) ? '/' : '' ) . $pPath . ( ( false === strpos( $pPath, '.' ) ) ? '.' . self::FORMAT : '' ) . $query; } abstract function MakeRequest( $pCanonizedPath, $pMethod = 'GET', $pParams = array() ); /** * @param string $pPath * @param string $pMethod * @param array $pParams * * @return object[]|object|null */ private function _Api( $pPath, $pMethod = 'GET', $pParams = array() ) { $pMethod = strtoupper( $pMethod ); try { $result = $this->MakeRequest( $pPath, $pMethod, $pParams ); } catch ( Freemius_Exception $e ) { // Map to error object. $result = (object) $e->getResult(); } catch ( Exception $e ) { // Map to error object. $result = (object) array( 'error' => (object) array( 'type' => 'Unknown', 'message' => $e->getMessage() . ' (' . $e->getFile() . ': ' . $e->getLine() . ')', 'code' => 'unknown', 'http' => 402 ) ); } return $result; } public function Api( $pPath, $pMethod = 'GET', $pParams = array() ) { return $this->_Api( $this->CanonizePath( $pPath ), $pMethod, $pParams ); } /** * Base64 decoding that does not need to be urldecode()-ed. * * Exactly the same as PHP base64 encode except it uses * `-` instead of `+` * `_` instead of `/` * No padded = * * @param string $input Base64UrlEncoded() string * * @return string */ protected static function Base64UrlDecode( $input ) { /** * IMPORTANT NOTE: * This is a hack suggested by @otto42 and @greenshady from * the theme's review team. The usage of base64 for API * signature encoding was approved in a Slack meeting * held on Tue (10/25 2016). * * @todo Remove this hack once the base64 error is removed from the Theme Check. * * @since 1.2.2 * @author Vova Feldman (@svovaf) */ $fn = 'base64' . '_decode'; return $fn( strtr( $input, '-_', '+/' ) ); } /** * Base64 encoding that does not need to be urlencode()ed. * * Exactly the same as base64 encode except it uses * `-` instead of `+ * `_` instead of `/` * * @param string $input string * * @return string Base64 encoded string */ protected static function Base64UrlEncode( $input ) { /** * IMPORTANT NOTE: * This is a hack suggested by @otto42 and @greenshady from * the theme's review team. The usage of base64 for API * signature encoding was approved in a Slack meeting * held on Tue (10/25 2016). * * @todo Remove this hack once the base64 error is removed from the Theme Check. * * @since 1.2.2 * @author Vova Feldman (@svovaf) */ $fn = 'base64' . '_encode'; $str = strtr( $fn( $input ), '+/', '-_' ); $str = str_replace( '=', '', $str ); return $str; } } }PK!UQQ5libraries/freemius/includes/sdk/FreemiusWordPress.phpnu[ '7.37' ); if ( ! defined( 'FS_API__PROTOCOL' ) ) { define( 'FS_API__PROTOCOL', version_compare( $curl_version['version'], '7.37', '>=' ) ? 'https' : 'http' ); } if ( ! defined( 'FS_API__LOGGER_ON' ) ) { define( 'FS_API__LOGGER_ON', false ); } if ( ! defined( 'FS_API__ADDRESS' ) ) { define( 'FS_API__ADDRESS', '://api.freemius.com' ); } if ( ! defined( 'FS_API__SANDBOX_ADDRESS' ) ) { define( 'FS_API__SANDBOX_ADDRESS', '://sandbox-api.freemius.com' ); } if ( ! class_exists( 'Freemius_Api_WordPress' ) ) { class Freemius_Api_WordPress extends Freemius_Api_Base { private static $_logger = array(); /** * @param string $pScope 'app', 'developer', 'user' or 'install'. * @param number $pID Element's id. * @param string $pPublic Public key. * @param string|bool $pSecret Element's secret key. * @param bool $pSandbox Whether or not to run API in sandbox mode. */ public function __construct( $pScope, $pID, $pPublic, $pSecret = false, $pSandbox = false ) { // If secret key not provided, use public key encryption. if ( is_bool( $pSecret ) ) { $pSecret = $pPublic; } parent::Init( $pScope, $pID, $pPublic, $pSecret, $pSandbox ); } public static function GetUrl( $pCanonizedPath = '', $pIsSandbox = false ) { $address = ( $pIsSandbox ? FS_API__SANDBOX_ADDRESS : FS_API__ADDRESS ); if ( ':' === $address[0] ) { $address = self::$_protocol . $address; } return $address . $pCanonizedPath; } #---------------------------------------------------------------------------------- #region Servers Clock Diff #---------------------------------------------------------------------------------- /** * @var int Clock diff in seconds between current server to API server. */ private static $_clock_diff = 0; /** * Set clock diff for all API calls. * * @since 1.0.3 * * @param $pSeconds */ public static function SetClockDiff( $pSeconds ) { self::$_clock_diff = $pSeconds; } /** * Find clock diff between current server to API server. * * @since 1.0.2 * @return int Clock diff in seconds. */ public static function FindClockDiff() { $time = time(); $pong = self::Ping(); return ( $time - strtotime( $pong->timestamp ) ); } #endregion /** * @var string http or https */ private static $_protocol = FS_API__PROTOCOL; /** * Set API connection protocol. * * @since 1.0.4 */ public static function SetHttp() { self::$_protocol = 'http'; } /** * Sets API connection protocol to HTTPS. * * @since 2.5.4 */ public static function SetHttps() { self::$_protocol = 'https'; } /** * @since 1.0.4 * * @return bool */ public static function IsHttps() { return ( 'https' === self::$_protocol ); } /** * Sign request with the following HTTP headers: * Content-MD5: MD5(HTTP Request body) * Date: Current date (i.e Sat, 14 Feb 2016 20:24:46 +0000) * Authorization: FS {scope_entity_id}:{scope_entity_public_key}:base64encode(sha256(string_to_sign, * {scope_entity_secret_key})) * * @param string $pResourceUrl * @param array $pWPRemoteArgs * * @return array */ function SignRequest( $pResourceUrl, $pWPRemoteArgs ) { $auth = $this->GenerateAuthorizationParams( $pResourceUrl, $pWPRemoteArgs['method'], ! empty( $pWPRemoteArgs['body'] ) ? $pWPRemoteArgs['body'] : '' ); $pWPRemoteArgs['headers']['Date'] = $auth['date']; $pWPRemoteArgs['headers']['Authorization'] = $auth['authorization']; if ( ! empty( $auth['content_md5'] ) ) { $pWPRemoteArgs['headers']['Content-MD5'] = $auth['content_md5']; } return $pWPRemoteArgs; } /** * Generate Authorization request headers: * * Content-MD5: MD5(HTTP Request body) * Date: Current date (i.e Sat, 14 Feb 2016 20:24:46 +0000) * Authorization: FS {scope_entity_id}:{scope_entity_public_key}:base64encode(sha256(string_to_sign, * {scope_entity_secret_key})) * * @author Vova Feldman * * @param string $pResourceUrl * @param string $pMethod * @param string $pPostParams * * @return array * @throws Freemius_Exception */ function GenerateAuthorizationParams( $pResourceUrl, $pMethod = 'GET', $pPostParams = '' ) { $pMethod = strtoupper( $pMethod ); $eol = "\n"; $content_md5 = ''; $content_type = ''; $now = ( time() - self::$_clock_diff ); $date = date( 'r', $now ); if ( in_array( $pMethod, array( 'POST', 'PUT' ) ) ) { $content_type = 'application/json'; if ( ! empty( $pPostParams ) ) { $content_md5 = md5( $pPostParams ); } } $string_to_sign = implode( $eol, array( $pMethod, $content_md5, $content_type, $date, $pResourceUrl ) ); // If secret and public keys are identical, it means that // the signature uses public key hash encoding. $auth_type = ( $this->_secret !== $this->_public ) ? 'FS' : 'FSP'; $auth = array( 'date' => $date, 'authorization' => $auth_type . ' ' . $this->_id . ':' . $this->_public . ':' . self::Base64UrlEncode( hash_hmac( 'sha256', $string_to_sign, $this->_secret ) ) ); if ( ! empty( $content_md5 ) ) { $auth['content_md5'] = $content_md5; } return $auth; } /** * Get API request URL signed via query string. * * @since 1.2.3 Stopped using http_build_query(). Instead, use urlencode(). In some environments the encoding of http_build_query() can generate a URL that once used with a redirect, the `&` querystring separator is escaped to `&` which breaks the URL (Added by @svovaf). * * @param string $pPath * * @throws Freemius_Exception * * @return string */ function GetSignedUrl( $pPath ) { $resource = explode( '?', $this->CanonizePath( $pPath ) ); $pResourceUrl = $resource[0]; $auth = $this->GenerateAuthorizationParams( $pResourceUrl ); return Freemius_Api_WordPress::GetUrl( $pResourceUrl . '?' . ( 1 < count( $resource ) && ! empty( $resource[1] ) ? $resource[1] . '&' : '' ) . 'authorization=' . urlencode( $auth['authorization'] ) . '&auth_date=' . urlencode( $auth['date'] ) , $this->_isSandbox ); } /** * @author Vova Feldman * * @param string $pUrl * @param array $pWPRemoteArgs * * @return mixed */ private static function ExecuteRequest( $pUrl, &$pWPRemoteArgs ) { $bt = debug_backtrace(); $start = microtime( true ); $response = self::RemoteRequest( $pUrl, $pWPRemoteArgs ); if ( FS_API__LOGGER_ON ) { $end = microtime( true ); $has_body = ( isset( $pWPRemoteArgs['body'] ) && ! empty( $pWPRemoteArgs['body'] ) ); $is_http_error = is_wp_error( $response ); self::$_logger[] = array( 'id' => count( self::$_logger ), 'start' => $start, 'end' => $end, 'total' => ( $end - $start ), 'method' => $pWPRemoteArgs['method'], 'path' => $pUrl, 'body' => $has_body ? $pWPRemoteArgs['body'] : null, 'result' => ! $is_http_error ? $response['body'] : json_encode( $response->get_error_messages() ), 'code' => ! $is_http_error ? $response['response']['code'] : null, 'backtrace' => $bt, ); } return $response; } /** * @author Leo Fajardo (@leorw) * * @param string $pUrl * @param array $pWPRemoteArgs * * @return array|WP_Error The response array or a WP_Error on failure. */ static function RemoteRequest( $pUrl, $pWPRemoteArgs ) { $response = wp_remote_request( $pUrl, $pWPRemoteArgs ); if ( is_array( $response ) && ( empty( $response['headers'] ) || empty( $response['headers']['x-api-server'] ) ) ) { // API is considered blocked if the response doesn't include the `x-api-server` header. When there's no error but this header doesn't exist, the response is usually not in the expected form (e.g., cannot be JSON-decoded). $response = new WP_Error( 'api_blocked', htmlentities( $response['body'] ) ); } return $response; } /** * @return array */ static function GetLogger() { return self::$_logger; } /** * @param string $pCanonizedPath * @param string $pMethod * @param array $pParams * @param null|array $pWPRemoteArgs * @param bool $pIsSandbox * @param null|callable $pBeforeExecutionFunction * * @return object[]|object|null * * @throws \Freemius_Exception */ private static function MakeStaticRequest( $pCanonizedPath, $pMethod = 'GET', $pParams = array(), $pWPRemoteArgs = null, $pIsSandbox = false, $pBeforeExecutionFunction = null ) { // Connectivity errors simulation. if ( FS_SDK__SIMULATE_NO_API_CONNECTIVITY_CLOUDFLARE ) { self::ThrowCloudFlareDDoSException(); } else if ( FS_SDK__SIMULATE_NO_API_CONNECTIVITY_SQUID_ACL ) { self::ThrowSquidAclException(); } if ( empty( $pWPRemoteArgs ) ) { $user_agent = 'Freemius/WordPress-SDK/' . Freemius_Api_Base::VERSION . '; ' . home_url(); $pWPRemoteArgs = array( 'method' => strtoupper( $pMethod ), 'connect_timeout' => 10, 'timeout' => 60, 'follow_redirects' => true, 'redirection' => 5, 'user-agent' => $user_agent, 'blocking' => true, ); } if ( ! isset( $pWPRemoteArgs['headers'] ) || ! is_array( $pWPRemoteArgs['headers'] ) ) { $pWPRemoteArgs['headers'] = array(); } if ( in_array( $pMethod, array( 'POST', 'PUT' ) ) ) { $pWPRemoteArgs['headers']['Content-type'] = 'application/json'; if ( is_array( $pParams ) && 0 < count( $pParams ) ) { $pWPRemoteArgs['body'] = json_encode( $pParams ); } } $request_url = self::GetUrl( $pCanonizedPath, $pIsSandbox ); $resource = explode( '?', $pCanonizedPath ); if ( FS_SDK__HAS_CURL ) { // Disable the 'Expect: 100-continue' behaviour. This causes cURL to wait // for 2 seconds if the server does not support this header. $pWPRemoteArgs['headers']['Expect'] = ''; } if ( 'https' === substr( strtolower( $request_url ), 0, 5 ) ) { $pWPRemoteArgs['sslverify'] = FS_SDK__SSLVERIFY; } if ( false !== $pBeforeExecutionFunction && is_callable( $pBeforeExecutionFunction ) ) { $pWPRemoteArgs = call_user_func( $pBeforeExecutionFunction, $resource[0], $pWPRemoteArgs ); } $result = self::ExecuteRequest( $request_url, $pWPRemoteArgs ); if ( is_wp_error( $result ) ) { /** * @var WP_Error $result */ if ( self::IsCurlError( $result ) ) { /** * With dual stacked DNS responses, it's possible for a server to * have IPv6 enabled but not have IPv6 connectivity. If this is * the case, cURL will try IPv4 first and if that fails, then it will * fall back to IPv6 and the error EHOSTUNREACH is returned by the * operating system. */ $matches = array(); $regex = '/Failed to connect to ([^:].*): Network is unreachable/'; if ( preg_match( $regex, $result->get_error_message( 'http_request_failed' ), $matches ) ) { /** * Validate IP before calling `inet_pton()` to avoid PHP un-catchable warning. * @author Vova Feldman (@svovaf) */ if ( filter_var( $matches[1], FILTER_VALIDATE_IP ) ) { if ( strlen( inet_pton( $matches[1] ) ) === 16 ) { /** * error_log('Invalid IPv6 configuration on server, Please disable or get native IPv6 on your server.'); * Hook to an action triggered just before cURL is executed to resolve the IP version to v4. * * @phpstan-ignore-next-line */ add_action( 'http_api_curl', 'Freemius_Api_WordPress::CurlResolveToIPv4', 10, 1 ); // Re-run request. $result = self::ExecuteRequest( $request_url, $pWPRemoteArgs ); } } } } if ( is_wp_error( $result ) ) { self::ThrowWPRemoteException( $result ); } } $response_body = $result['body']; if ( empty( $response_body ) ) { return null; } $decoded = json_decode( $response_body ); if ( is_null( $decoded ) ) { if ( preg_match( '/Please turn JavaScript on/i', $response_body ) && preg_match( '/text\/javascript/', $response_body ) ) { self::ThrowCloudFlareDDoSException( $response_body ); } else if ( preg_match( '/Access control configuration prevents your request from being allowed at this time. Please contact your service provider if you feel this is incorrect./', $response_body ) && preg_match( '/squid/', $response_body ) ) { self::ThrowSquidAclException( $response_body ); } else { $decoded = (object) array( 'error' => (object) array( 'type' => 'Unknown', 'message' => $response_body, 'code' => 'unknown', 'http' => 402 ) ); } } return $decoded; } /** * Makes an HTTP request. This method can be overridden by subclasses if * developers want to do fancier things or use something other than wp_remote_request() * to make the request. * * @param string $pCanonizedPath The URL to make the request to * @param string $pMethod HTTP method * @param array $pParams The parameters to use for the POST body * @param null|array $pWPRemoteArgs wp_remote_request options. * * @return object[]|object|null * * @throws Freemius_Exception */ public function MakeRequest( $pCanonizedPath, $pMethod = 'GET', $pParams = array(), $pWPRemoteArgs = null ) { $resource = explode( '?', $pCanonizedPath ); // Only sign request if not ping.json connectivity test. $sign_request = ( '/v1/ping.json' !== strtolower( substr( $resource[0], - strlen( '/v1/ping.json' ) ) ) ); return self::MakeStaticRequest( $pCanonizedPath, $pMethod, $pParams, $pWPRemoteArgs, $this->_isSandbox, $sign_request ? array( &$this, 'SignRequest' ) : null ); } /** * Sets CURLOPT_IPRESOLVE to CURL_IPRESOLVE_V4 for cURL-Handle provided as parameter * * @param resource $handle A cURL handle returned by curl_init() * * @return resource $handle A cURL handle returned by curl_init() with CURLOPT_IPRESOLVE set to * CURL_IPRESOLVE_V4 * * @link https://gist.github.com/golderweb/3a2aaec2d56125cc004e */ static function CurlResolveToIPv4( $handle ) { curl_setopt( $handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 ); return $handle; } #---------------------------------------------------------------------------------- #region Connectivity Test #---------------------------------------------------------------------------------- /** * This method exists only for backward compatibility to prevent a fatal error from happening when called from an outdated piece of code. * * @param mixed $pPong * * @return bool */ public static function Test( $pPong = null ) { return ( is_object( $pPong ) && isset( $pPong->api ) && 'pong' === $pPong->api ); } /** * Ping API to test connectivity. * * @return object */ public static function Ping() { try { $result = self::MakeStaticRequest( '/v' . FS_API__VERSION . '/ping.json' ); } catch ( Freemius_Exception $e ) { // Map to error object. $result = (object) $e->getResult(); } catch ( Exception $e ) { // Map to error object. $result = (object) array( 'error' => (object) array( 'type' => 'Unknown', 'message' => $e->getMessage() . ' (' . $e->getFile() . ': ' . $e->getLine() . ')', 'code' => 'unknown', 'http' => 402 ) ); } return $result; } #endregion #---------------------------------------------------------------------------------- #region Connectivity Exceptions #---------------------------------------------------------------------------------- /** * @param \WP_Error $pError * * @return bool */ private static function IsCurlError( WP_Error $pError ) { $message = $pError->get_error_message( 'http_request_failed' ); return ( 0 === strpos( $message, 'cURL' ) ); } /** * @param WP_Error $pError * * @throws Freemius_Exception */ private static function ThrowWPRemoteException( WP_Error $pError ) { if ( self::IsCurlError( $pError ) ) { $message = $pError->get_error_message( 'http_request_failed' ); #region Check if there are any missing cURL methods. $curl_required_methods = array( 'curl_version', 'curl_exec', 'curl_init', 'curl_close', 'curl_setopt', 'curl_setopt_array', 'curl_error', ); // Find all missing methods. $missing_methods = array(); foreach ( $curl_required_methods as $m ) { if ( ! function_exists( $m ) ) { $missing_methods[] = $m; } } if ( ! empty( $missing_methods ) ) { throw new Freemius_Exception( array( 'error' => (object) array( 'type' => 'cUrlMissing', 'message' => $message, 'code' => 'curl_missing', 'http' => 402 ), 'missing_methods' => $missing_methods, ) ); } #endregion // cURL error - "cURL error {{errno}}: {{error}}". $parts = explode( ':', substr( $message, strlen( 'cURL error ' ) ), 2 ); $code = ( 0 < count( $parts ) ) ? $parts[0] : 'http_request_failed'; $message = ( 1 < count( $parts ) ) ? $parts[1] : $message; $e = new Freemius_Exception( array( 'error' => (object) array( 'code' => $code, 'message' => $message, 'type' => 'CurlException', ), ) ); } else { $e = new Freemius_Exception( array( 'error' => (object) array( 'code' => $pError->get_error_code(), 'message' => $pError->get_error_message(), 'type' => 'WPRemoteException', ), ) ); } throw $e; } /** * @param string $pResult * * @throws Freemius_Exception */ private static function ThrowCloudFlareDDoSException( $pResult = '' ) { throw new Freemius_Exception( array( 'error' => (object) array( 'type' => 'CloudFlareDDoSProtection', 'message' => $pResult, 'code' => 'cloudflare_ddos_protection', 'http' => 402 ) ) ); } /** * @param string $pResult * * @throws Freemius_Exception */ private static function ThrowSquidAclException( $pResult = '' ) { throw new Freemius_Exception( array( 'error' => (object) array( 'type' => 'SquidCacheBlock', 'message' => $pResult, 'code' => 'squid_cache_block', 'http' => 402 ) ) ); } #endregion } } PK!AX8libraries/freemius/includes/sdk/Exceptions/Exception.phpnu[_result = $result; $code = 0; $message = 'Unknown error, please check GetResult().'; $type = ''; if ( isset( $result['error'] ) && is_array( $result['error'] ) ) { if ( isset( $result['error']['code'] ) ) { $code = $result['error']['code']; } if ( isset( $result['error']['message'] ) ) { $message = $result['error']['message']; } if ( isset( $result['error']['type'] ) ) { $type = $result['error']['type']; } } $this->_type = $type; $this->_code = $code; parent::__construct( $message, is_numeric( $code ) ? $code : 0 ); } /** * Return the associated result object returned by the API server. * * @return array The result from the API server */ public function getResult() { return $this->_result; } public function getStringCode() { return $this->_code; } public function getType() { return $this->_type; } /** * To make debugging easier. * * @return string The string representation of the error */ public function __toString() { $str = $this->getType() . ': '; if ( $this->code != 0 ) { $str .= $this->getStringCode() . ': '; } return $str . $this->getMessage(); } } } PK!p2WW4libraries/freemius/includes/sdk/Exceptions/index.phpnu[ $value ) { $input[ $key ] = fs_sanitize_input( $value ); } } else { // Allow empty values to pass through as-is, like `null`, `''`, `0`, `'0'` etc. $input = empty( $input ) ? $input : sanitize_text_field( $input ); } return $input; } } if ( ! function_exists( 'fs_request_get' ) ) { /** * A helper method to fetch GET/POST user input with an optional default value when the input is not set. * * @author Vova Feldman (@svovaf) * * @note The return value is always sanitized with sanitize_text_field(). * * @param string $key * @param mixed $def * @param string|bool $type Since 1.2.1.7 - when set to 'get' will look for the value passed via querystring, when * set to 'post' will look for the value passed via the POST request's body, otherwise, * will check if the parameter was passed in any of the two. * * * @return mixed */ function fs_request_get( $key, $def = false, $type = false ) { return fs_sanitize_input( fs_request_get_raw( $key, $def, $type ) ); } } if ( ! function_exists( 'fs_request_has' ) ) { function fs_request_has( $key ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return isset( $_REQUEST[ $key ] ); } } if ( ! function_exists( 'fs_request_get_bool' ) ) { /** * A helper method to fetch GET/POST user boolean input with an optional default value when the input is not set. * * @author Vova Feldman (@svovaf) * * @param string $key * @param bool $def * * @return bool|mixed */ function fs_request_get_bool( $key, $def = false ) { $val = fs_request_get( $key, null ); if ( is_null( $val ) ) { return $def; } if ( is_bool( $val ) ) { return $val; } else if ( is_numeric( $val ) ) { if ( 1 == $val ) { return true; } else if ( 0 == $val ) { return false; } } else if ( is_string( $val ) ) { $val = strtolower( $val ); if ( 'true' === $val ) { return true; } else if ( 'false' === $val ) { return false; } } return $def; } } if ( ! function_exists( 'fs_request_is_post' ) ) { function fs_request_is_post() { return ( 'post' === strtolower( $_SERVER['REQUEST_METHOD'] ) ); } } if ( ! function_exists( 'fs_request_is_get' ) ) { function fs_request_is_get() { return ( 'get' === strtolower( $_SERVER['REQUEST_METHOD'] ) ); } } if ( ! function_exists( 'fs_get_action' ) ) { function fs_get_action( $action_key = 'action' ) { // phpcs:disable WordPress.Security.NonceVerification.Recommended if ( ! empty( $_REQUEST[ $action_key ] ) && is_string( $_REQUEST[ $action_key ] ) ) { return strtolower( $_REQUEST[ $action_key ] ); } if ( 'action' == $action_key ) { $action_key = 'fs_action'; if ( ! empty( $_REQUEST[ $action_key ] ) && is_string( $_REQUEST[ $action_key ] ) ) { return strtolower( $_REQUEST[ $action_key ] ); } } return false; // phpcs:enable WordPress.Security.NonceVerification.Recommended } } if ( ! function_exists( 'fs_request_is_action' ) ) { function fs_request_is_action( $action, $action_key = 'action' ) { return ( strtolower( $action ) === fs_get_action( $action_key ) ); } } if ( ! function_exists( 'fs_request_is_action_secure' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.0.0 * * @since 1.2.1.5 Allow nonce verification. * * @param string $action * @param string $action_key * @param string $nonce_key * * @return bool */ function fs_request_is_action_secure( $action, $action_key = 'action', $nonce_key = 'nonce' ) { if ( strtolower( $action ) !== fs_get_action( $action_key ) ) { return false; } $nonce = ! empty( $_REQUEST[ $nonce_key ] ) ? $_REQUEST[ $nonce_key ] : ''; if ( empty( $nonce ) || ( false === wp_verify_nonce( $nonce, $action ) ) ) { return false; } return true; } } #endregion if ( ! function_exists( 'fs_is_plugin_page' ) ) { function fs_is_plugin_page( $page_slug ) { return ( is_admin() && $page_slug === fs_request_get( 'page' ) ); } } if ( ! function_exists( 'fs_get_raw_referer' ) ) { /** * Retrieves unvalidated referer from '_wp_http_referer' or HTTP referer. * * Do not use for redirects, use {@see wp_get_referer()} instead. * * @since 1.2.3 * * @return string|false Referer URL on success, false on failure. */ function fs_get_raw_referer() { if ( function_exists( 'wp_get_raw_referer' ) ) { return wp_get_raw_referer(); } if ( ! empty( $_REQUEST['_wp_http_referer'] ) ) { return wp_unslash( $_REQUEST['_wp_http_referer'] ); } else if ( ! empty( $_SERVER['HTTP_REFERER'] ) ) { return wp_unslash( $_SERVER['HTTP_REFERER'] ); } return false; } } /* Core UI. --------------------------------------------------------------------------------------------*/ if ( ! function_exists( 'fs_ui_action_button' ) ) { /** * @param number $module_id * @param string $page * @param string $action * @param string $title * @param string $button_class * @param array $params * @param bool $is_primary * @param bool $is_small * @param string|bool $icon_class Optional class for an icon (since 1.1.7). * @param string|bool $confirmation Optional confirmation message before submit (since 1.1.7). * @param string $method Since 1.1.7 * * @uses fs_ui_get_action_button() */ function fs_ui_action_button( $module_id, $page, $action, $title, $button_class = '', $params = array(), $is_primary = true, $is_small = false, $icon_class = false, $confirmation = false, $method = 'GET' ) { echo fs_ui_get_action_button( $module_id, $page, $action, $title, $button_class, $params, $is_primary, $is_small, $icon_class, $confirmation, $method ); } } if ( ! function_exists( 'fs_ui_get_action_button' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.1.7 * * @param number $module_id * @param string $page * @param string $action * @param string $title * @param string $button_class * @param array $params * @param bool $is_primary * @param bool $is_small * @param string|bool $icon_class Optional class for an icon. * @param string|bool $confirmation Optional confirmation message before submit. * @param string $method * * @return string */ function fs_ui_get_action_button( $module_id, $page, $action, $title, $button_class = '', $params = array(), $is_primary = true, $is_small = false, $icon_class = false, $confirmation = false, $method = 'GET' ) { // Prepend icon (if set). $title = ( is_string( $icon_class ) ? ' ' : '' ) . $title; if ( is_string( $confirmation ) ) { return sprintf( '%s%s', freemius( $module_id )->_get_admin_page_url( $page, $params ), $method, $action, wp_nonce_field( $action, '_wpnonce', true, false ), 'button' . ( ! empty( $button_class ) ? ' ' . $button_class : '' ) . ( $is_primary ? ' button-primary' : '' ) . ( $is_small ? ' button-small' : '' ), $confirmation, $title ); } else if ( 'GET' !== strtoupper( $method ) ) { return sprintf( '
%s%s
', freemius( $module_id )->_get_admin_page_url( $page, $params ), $method, $action, wp_nonce_field( $action, '_wpnonce', true, false ), 'button' . ( ! empty( $button_class ) ? ' ' . $button_class : '' ) . ( $is_primary ? ' button-primary' : '' ) . ( $is_small ? ' button-small' : '' ), $title ); } else { return sprintf( '%s', wp_nonce_url( freemius( $module_id )->_get_admin_page_url( $page, array_merge( $params, array( 'fs_action' => $action ) ) ), $action ), 'button' . ( ! empty( $button_class ) ? ' ' . $button_class : '' ) . ( $is_primary ? ' button-primary' : '' ) . ( $is_small ? ' button-small' : '' ), $title ); } } function fs_ui_action_link( $module_id, $page, $action, $title, $params = array() ) { ?> $entities_or_entity ) { if ( is_array( $entities_or_entity ) ) { $entities[ $key ] = fs_get_entities( $entities_or_entity, $class_name ); } else { $entities[ $key ] = fs_get_entity( $entities_or_entity, $class_name ); } } return $entities; } } if ( ! function_exists( 'fs_nonce_url' ) ) { /** * Retrieve URL with nonce added to URL query. * * Originally was using `wp_nonce_url()` but the new version * changed the return value to escaped URL, that's not the expected * behaviour. * * @author Vova Feldman (@svovaf) * @since ~1.1.3 * * @param string $actionurl URL to add nonce action. * @param int|string $action Optional. Nonce action name. Default -1. * @param string $name Optional. Nonce name. Default '_wpnonce'. * * @return string Escaped URL with nonce action added. */ function fs_nonce_url( $actionurl, $action = - 1, $name = '_wpnonce' ) { return add_query_arg( $name, wp_create_nonce( $action ), $actionurl ); } } if ( ! function_exists( 'fs_parse_url_params' ) ) { /** * Returns the query parameters of the given URL if there are any. * * @param string $url * @param bool $html_entity_decode * * @return array Key value pair where key represents the parameter name and value represents the parameter value. */ function fs_parse_url_params( $url, $html_entity_decode = false ) { $query_str = parse_url( $url, PHP_URL_QUERY ); $url_params = array(); if ( empty( $query_str ) ) { return $url_params; } if ( $html_entity_decode ) { $query_str = html_entity_decode( $query_str ); } parse_str( $query_str, $url_params ); return $url_params; } } if ( ! function_exists( 'fs_starts_with' ) ) { /** * Check if string starts with. * * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @param string $haystack * @param string $needle * * @return bool */ function fs_starts_with( $haystack, $needle ) { $length = strlen( $needle ); return ( substr( $haystack, 0, $length ) === $needle ); } } if ( ! function_exists( 'fs_ends_with' ) ) { /** * Check if string ends with. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $haystack * @param string $needle * * @return bool */ function fs_ends_with( $haystack, $needle ) { $length = strlen( $needle ); $start = $length * - 1; // negative return ( substr( $haystack, $start ) === $needle ); } } if ( ! function_exists( 'fs_strip_url_protocol' ) ) { function fs_strip_url_protocol( $url ) { if ( ! fs_starts_with( $url, 'http' ) ) { return $url; } $protocol_pos = strpos( $url, '://' ); if ( $protocol_pos > 5 ) { return $url; } return substr( $url, $protocol_pos + 3 ); } } #region Url Canonization ------------------------------------------------------------------ if ( ! function_exists( 'fs_canonize_url' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @param string $url * @param bool $omit_host * @param array $ignore_params * * @return string */ function fs_canonize_url( $url, $omit_host = false, $ignore_params = array() ) { $parsed_url = parse_url( strtolower( $url ) ); // if ( ! isset( $parsed_url['host'] ) ) { // return $url; // } $canonical = ( ( $omit_host || ! isset( $parsed_url['host'] ) ) ? '' : $parsed_url['host'] ) . $parsed_url['path']; if ( isset( $parsed_url['query'] ) ) { parse_str( $parsed_url['query'], $queryString ); $canonical .= '?' . fs_canonize_query_string( $queryString, $ignore_params ); } return $canonical; } } if ( ! function_exists( 'fs_canonize_query_string' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @param array $params * @param array $ignore_params * @param bool $params_prefix * * @return string */ function fs_canonize_query_string( array $params, array &$ignore_params, $params_prefix = false ) { if ( ! is_array( $params ) || 0 === count( $params ) ) { return ''; } // Url encode both keys and values $keys = fs_urlencode_rfc3986( array_keys( $params ) ); $values = fs_urlencode_rfc3986( array_values( $params ) ); $params = array_combine( $keys, $values ); // Parameters are sorted by name, using lexicographical byte value ordering. // Ref: Spec: 9.1.1 (1) uksort( $params, 'strcmp' ); $pairs = array(); foreach ( $params as $parameter => $value ) { $lower_param = strtolower( $parameter ); // Skip ignore params. if ( in_array( $lower_param, $ignore_params ) || ( false !== $params_prefix && fs_starts_with( $lower_param, $params_prefix ) ) ) { continue; } if ( is_array( $value ) ) { // If two or more parameters share the same name, they are sorted by their value // Ref: Spec: 9.1.1 (1) natsort( $value ); foreach ( $value as $duplicate_value ) { $pairs[] = $lower_param . '=' . $duplicate_value; } } else { $pairs[] = $lower_param . '=' . $value; } } if ( 0 === count( $pairs ) ) { return ''; } return implode( "&", $pairs ); } } if ( ! function_exists( 'fs_urlencode_rfc3986' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @param string|string[] $input * * @return array|mixed|string */ function fs_urlencode_rfc3986( $input ) { if ( is_array( $input ) ) { return array_map( 'fs_urlencode_rfc3986', $input ); } else if ( is_scalar( $input ) ) { return str_replace( '+', ' ', str_replace( '%7E', '~', rawurlencode( $input ) ) ); } return ''; } } #endregion Url Canonization ------------------------------------------------------------------ if ( ! function_exists( 'fs_download_image' ) ) { /** * @author Vova Feldman (@svovaf) * * @since 1.2.2 Changed to usage of WP_Filesystem_Direct. * * @param string $from URL * @param string $to File path. * * @return bool Is successfully downloaded. */ function fs_download_image( $from, $to ) { $dir = dirname( $to ); if ( 'direct' !== get_filesystem_method( array(), $dir ) ) { return false; } if ( ! class_exists( 'WP_Filesystem_Direct' ) ) { require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php'; require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php'; } $fs = new WP_Filesystem_Direct( '' ); $tmpfile = download_url( $from ); if ( $tmpfile instanceof WP_Error ) { // Issue downloading the file. return false; } $fs->copy( $tmpfile, $to ); $fs->delete( $tmpfile ); return true; } } /* General Utilities --------------------------------------------------------------------------------------------*/ if ( ! function_exists( 'fs_sort_by_priority' ) ) { /** * Sorts an array by the value of the priority key. * * @author Daniel Iser (@danieliser) * @since 1.1.7 * * @param $a * @param $b * * @return int */ function fs_sort_by_priority( $a, $b ) { // If b has a priority and a does not, b wins. if ( ! isset( $a['priority'] ) && isset( $b['priority'] ) ) { return 1; } // If b has a priority and a does not, b wins. elseif ( isset( $a['priority'] ) && ! isset( $b['priority'] ) ) { return - 1; } // If neither has a priority or both priorities are equal it's a tie. elseif ( ( ! isset( $a['priority'] ) && ! isset( $b['priority'] ) ) || $a['priority'] === $b['priority'] ) { return 0; } // If both have priority return the winner. return ( $a['priority'] < $b['priority'] ) ? - 1 : 1; } } #-------------------------------------------------------------------------------- #region Localization #-------------------------------------------------------------------------------- global $fs_text_overrides; if ( ! isset( $fs_text_overrides ) ) { $fs_text_overrides = array(); } if ( ! function_exists( 'fs_text' ) ) { /** * Retrieve a translated text by key. * * @author Vova Feldman (@svovaf) * @since 1.2.1.7 * * @param string $key * @param string $slug * * @return string * * @global $fs_text_overrides */ function fs_text( $key, $slug = 'freemius' ) { global $fs_text_overrides; if ( isset( $fs_text_overrides[ $slug ] ) ) { if ( isset( $fs_text_overrides[ $slug ][ $key ] ) ) { return $fs_text_overrides[ $slug ][ $key ]; } $lower_key = strtolower( $key ); if ( isset( $fs_text_overrides[ $slug ][ $lower_key ] ) ) { return $fs_text_overrides[ $slug ][ $lower_key ]; } } return $key; } #region Private /** * Retrieve an inline translated text by key with a context. * * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @param string $context Context information for the translators. * @param string $key String key for overrides. * @param string $slug Module slug for overrides. * * @return string * * @global $fs_text_overrides */ function _fs_text_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { list( $text, $text_domain ) = fs_text_and_domain( $text, $key, $slug ); // Avoid misleading Theme Check warning. $fn = 'translate_with_gettext_context'; return $fn( $text, $context, $text_domain ); } #endregion /** * Retrieve an inline translated text by key with a context. * * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @param string $context Context information for the translators. * @param string $key String key for overrides. * @param string $slug Module slug for overrides. * * @return string * * @global $fs_text_overrides */ function fs_text_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { return _fs_text_x_inline( $text, $context, $key, $slug ); } /** * Output a translated text by key. * * @author Vova Feldman (@svovaf) * @since 1.2.1.7 * * @param string $key * @param string $slug */ function fs_echo( $key, $slug = 'freemius' ) { echo fs_text( $key, $slug ); } /** * Output an inline translated text. * * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @param string $key String key for overrides. * @param string $slug Module slug for overrides. */ function fs_echo_inline( $text, $key = '', $slug = 'freemius' ) { echo _fs_text_inline( $text, $key, $slug ); } /** * Output an inline translated text with a context. * * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @param string $context Context information for the translators. * @param string $key String key for overrides. * @param string $slug Module slug for overrides. */ function fs_echo_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { echo _fs_text_x_inline( $text, $context, $key, $slug ); } } if ( ! function_exists( 'fs_text_override' ) ) { /** * Get a translatable text override if exists, or `false`. * * @author Vova Feldman (@svovaf) * @since 1.2.1.7 * * @param string $text Translatable string. * @param string $key String key for overrides. * @param string $slug Module slug for overrides. * * @return string|false */ function fs_text_override( $text, $key, $slug ) { global $fs_text_overrides; /** * Check if string is overridden. */ if ( ! isset( $fs_text_overrides[ $slug ] ) ) { return false; } if ( empty( $key ) ) { $key = strtolower( str_replace( ' ', '-', $text ) ); } if ( isset( $fs_text_overrides[ $slug ][ $key ] ) ) { return $fs_text_overrides[ $slug ][ $key ]; } $lower_key = strtolower( $key ); if ( isset( $fs_text_overrides[ $slug ][ $lower_key ] ) ) { return $fs_text_overrides[ $slug ][ $lower_key ]; } return false; } } if ( ! function_exists( 'fs_text_and_domain' ) ) { /** * Get a translatable text and its text domain. * * When the text is overridden by the module, returns the overridden text and the text domain of the module. Otherwise, returns the original text and 'freemius' as the text domain. * * @author Vova Feldman (@svovaf) * @since 1.2.1.7 * * @param string $text Translatable string. * @param string $key String key for overrides. * @param string $slug Module slug for overrides. * * @return string[] */ function fs_text_and_domain( $text, $key, $slug ) { $override = fs_text_override( $text, $key, $slug ); if ( false === $override ) { // No override, use FS text domain. $text_domain = 'freemius'; } else { // Found an override. $text = $override; // Use the module's text domain. $text_domain = $slug; } return array( $text, $text_domain ); } } if ( ! function_exists( '_fs_text_inline' ) ) { /** * Retrieve an inline translated text by key. * * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @param string $key String key for overrides. * @param string $slug Module slug for overrides. * * @return string * * @global $fs_text_overrides */ function _fs_text_inline( $text, $key = '', $slug = 'freemius' ) { list( $text, $text_domain ) = fs_text_and_domain( $text, $key, $slug ); // Avoid misleading Theme Check warning. $fn = 'translate'; return $fn( $text, $text_domain ); } } if ( ! function_exists( 'fs_text_inline' ) ) { /** * Retrieve an inline translated text by key. * * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @param string $key String key for overrides. * @param string $slug Module slug for overrides. * * @return string * * @global $fs_text_overrides */ function fs_text_inline( $text, $key = '', $slug = 'freemius' ) { return _fs_text_inline( $text, $key, $slug ); } } if ( ! function_exists( 'fs_esc_attr' ) ) { /** * @author Vova Feldman * @since 1.2.1.6 * * @param string $key * @param string $slug * * @return string */ function fs_esc_attr( $key, $slug ) { return esc_attr( fs_text( $key, $slug ) ); } } if ( ! function_exists( 'fs_esc_attr_inline' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @param string $key String key for overrides. * @param string $slug Module slug for overrides. * * @return string */ function fs_esc_attr_inline( $text, $key = '', $slug = 'freemius' ) { return esc_attr( _fs_text_inline( $text, $key, $slug ) ); } } if ( ! function_exists( 'fs_esc_attr_x_inline' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @param string $context Context information for the translators. * @param string $key String key for overrides. * @param string $slug Module slug for overrides. * * @return string */ function fs_esc_attr_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { return esc_attr( _fs_text_x_inline( $text, $context, $key, $slug ) ); } } if ( ! function_exists( 'fs_esc_attr_echo' ) ) { /** * @author Vova Feldman * @since 1.2.1.6 * * @param string $key * @param string $slug */ function fs_esc_attr_echo( $key, $slug ) { echo esc_attr( fs_text( $key, $slug ) ); } } if ( ! function_exists( 'fs_esc_attr_echo_inline' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @param string $key String key for overrides. * @param string $slug Module slug for overrides. */ function fs_esc_attr_echo_inline( $text, $key = '', $slug = 'freemius' ) { echo esc_attr( _fs_text_inline( $text, $key, $slug ) ); } } if ( ! function_exists( 'fs_esc_js' ) ) { /** * @author Vova Feldman * @since 1.2.1.6 * * @param string $key * @param string $slug * * @return string */ function fs_esc_js( $key, $slug ) { return esc_js( fs_text( $key, $slug ) ); } } if ( ! function_exists( 'fs_esc_js_inline' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @param string $key String key for overrides. * @param string $slug Module slug for overrides. * * @return string */ function fs_esc_js_inline( $text, $key = '', $slug = 'freemius' ) { return esc_js( _fs_text_inline( $text, $key, $slug ) ); } } if ( ! function_exists( 'fs_esc_js_x_inline' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @param string $context Context information for the translators. * @param string $key String key for overrides. * @param string $slug Module slug for overrides. * * @return string */ function fs_esc_js_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { return esc_js( _fs_text_x_inline( $text, $context, $key, $slug ) ); } } if ( ! function_exists( 'fs_esc_js_echo_x_inline' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @param string $context Context information for the translators. * @param string $key String key for overrides. * @param string $slug Module slug for overrides. * * @return void */ function fs_esc_js_echo_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { echo esc_js( _fs_text_x_inline( $text, $context, $key, $slug ) ); } } if ( ! function_exists( 'fs_esc_js_echo' ) ) { /** * @author Vova Feldman * @since 1.2.1.6 * * @param string $key * @param string $slug */ function fs_esc_js_echo( $key, $slug ) { echo esc_js( fs_text( $key, $slug ) ); } } if ( ! function_exists( 'fs_esc_js_echo_inline' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @param string $key String key for overrides. * @param string $slug Module slug for overrides. */ function fs_esc_js_echo_inline( $text, $key = '', $slug = 'freemius' ) { echo esc_js( _fs_text_inline( $text, $key, $slug ) ); } } if ( ! function_exists( 'fs_json_encode_echo' ) ) { /** * @author Vova Feldman * @since 1.2.1.6 * * @param string $key * @param string $slug */ function fs_json_encode_echo( $key, $slug ) { echo json_encode( fs_text( $key, $slug ) ); } } if ( ! function_exists( 'fs_json_encode_echo_inline' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @param string $key String key for overrides. * @param string $slug Module slug for overrides. */ function fs_json_encode_echo_inline( $text, $key = '', $slug = 'freemius' ) { echo json_encode( _fs_text_inline( $text, $key, $slug ) ); } } if ( ! function_exists( 'fs_esc_html' ) ) { /** * @author Vova Feldman * @since 1.2.1.6 * * @param string $key * @param string $slug * * @return string */ function fs_esc_html( $key, $slug ) { return esc_html( fs_text( $key, $slug ) ); } } if ( ! function_exists( 'fs_esc_html_inline' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @param string $key String key for overrides. * @param string $slug Module slug for overrides. * * @return string */ function fs_esc_html_inline( $text, $key = '', $slug = 'freemius' ) { return esc_html( _fs_text_inline( $text, $key, $slug ) ); } } if ( ! function_exists( 'fs_esc_html_x_inline' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @param string $context Context information for the translators. * @param string $key String key for overrides. * @param string $slug Module slug for overrides. * * @return string */ function fs_esc_html_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { return esc_html( _fs_text_x_inline( $text, $context, $key, $slug ) ); } } if ( ! function_exists( 'fs_esc_html_echo_x_inline' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @param string $context Context information for the translators. * @param string $key String key for overrides. * @param string $slug Module slug for overrides. */ function fs_esc_html_echo_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { echo esc_html( _fs_text_x_inline( $text, $context, $key, $slug ) ); } } if ( ! function_exists( 'fs_esc_html_echo' ) ) { /** * @author Vova Feldman * @since 1.2.1.6 * * @param string $key * @param string $slug */ function fs_esc_html_echo( $key, $slug ) { echo esc_html( fs_text( $key, $slug ) ); } } if ( ! function_exists( 'fs_esc_html_echo_inline' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @param string $key String key for overrides. * @param string $slug Module slug for overrides. */ function fs_esc_html_echo_inline( $text, $key = '', $slug = 'freemius' ) { echo esc_html( _fs_text_inline( $text, $key, $slug ) ); } } if ( ! function_exists( 'fs_override_i18n' ) ) { /** * Override default i18n text phrases. * * @author Vova Feldman (@svovaf) * @since 1.1.6 * * @param array[string]string $key_value * @param string $slug * * @global $fs_text_overrides */ function fs_override_i18n( array $key_value, $slug = 'freemius' ) { global $fs_text_overrides; if ( ! isset( $fs_text_overrides[ $slug ] ) ) { $fs_text_overrides[ $slug ] = array(); } foreach ( $key_value as $key => $value ) { $fs_text_overrides[ $slug ][ $key ] = $value; } } } #endregion #-------------------------------------------------------------------------------- #region Multisite Network #-------------------------------------------------------------------------------- if ( ! function_exists( 'fs_is_plugin_uninstall' ) ) { /** * @author Vova Feldman (@svovaf) * @since 2.0.0 */ function fs_is_plugin_uninstall() { return ( defined( 'WP_UNINSTALL_PLUGIN' ) || ( 0 < did_action( 'pre_uninstall_plugin' ) ) ); } } if ( ! function_exists( 'fs_is_network_admin' ) ) { /** * Unlike is_network_admin(), this one will also work properly when * the context execution is WP AJAX handler, and during plugin * uninstall. * * @author Vova Feldman (@svovaf) * @since 2.0.0 */ function fs_is_network_admin() { return ( WP_FS__IS_NETWORK_ADMIN || ( is_multisite() && fs_is_plugin_uninstall() ) ); } } if ( ! function_exists( 'fs_is_blog_admin' ) ) { /** * Unlike is_blog_admin(), this one will also work properly when * the context execution is WP AJAX handler, and during plugin * uninstall. * * @author Vova Feldman (@svovaf) * @since 2.0.0 */ function fs_is_blog_admin() { return ( WP_FS__IS_BLOG_ADMIN || ( ! is_multisite() && fs_is_plugin_uninstall() ) ); } } #endregion if ( ! function_exists( 'fs_apply_filter' ) ) { /** * Apply filter for specific plugin. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @param string $module_unique_affix Module's unique affix. * @param string $tag The name of the filter hook. * @param mixed $value The value on which the filters hooked to `$tag` are applied on. * * @return mixed The filtered value after all hooked functions are applied to it. * * @uses apply_filters() */ function fs_apply_filter( $module_unique_affix, $tag, $value ) { $args = func_get_args(); return call_user_func_array( 'apply_filters', array_merge( array( "fs_{$tag}_{$module_unique_affix}" ), array_slice( $args, 2 ) ) ); } } if ( ! function_exists( 'fs_get_optional_constant' ) ) { /** * Gets the value of an optional constant. If the constant is not defined, the default value will be returned. * * @author Swashata Ghosh (@swashata) * @since 2.5.12.5 * * @param string $constant_name * @param mixed $default_value * * @return mixed */ function fs_get_optional_constant( $constant_name, $default_value = null ) { return defined( $constant_name ) ? constant( $constant_name ) : $default_value; } } PK!oh-libraries/freemius/includes/class-fs-lock.phpnu[_lock_id = $lock_id; if ( ! isset( self::$_thread_id ) ) { self::$_thread_id = mt_rand( 0, 32000 ); } } /** * Try to acquire lock. If the lock is already set or is being acquired by another locker, don't do anything. * * @param int $expiration * * @return bool TRUE if successfully acquired lock. */ function try_lock( $expiration = 0 ) { if ( $this->is_locked() ) { // Already locked. return false; } set_site_transient( $this->_lock_id, self::$_thread_id, $expiration ); if ( $this->has_lock() ) { $this->lock($expiration); return true; } return false; } /** * Acquire lock regardless if it's already acquired by another locker or not. * * @author Vova Feldman (@svovaf) * @since 2.1.0 * * @param int $expiration */ function lock( $expiration = 0 ) { set_site_transient( $this->_lock_id, true, $expiration ); } /** * Checks if lock is currently acquired. * * @author Vova Feldman (@svovaf) * @since 2.1.0 * * @return bool */ function is_locked() { return ( false !== get_site_transient( $this->_lock_id ) ); } /** * Unlock the lock. * * @author Vova Feldman (@svovaf) * @since 2.1.0 */ function unlock() { delete_site_transient( $this->_lock_id ); } /** * Checks if lock is currently acquired by the current locker. * * @return bool */ protected function has_lock() { return ( self::$_thread_id == get_site_transient( $this->_lock_id ) ); } }PK!~uIuI0libraries/freemius/includes/class-fs-storage.phpnu[_module_type = $module_type; $this->_module_slug = $slug; $this->_is_multisite = is_multisite(); if ( $this->_is_multisite ) { $this->_blog_id = get_current_blog_id(); $this->_network_storage = FS_Key_Value_Storage::instance( $module_type . '_data', $slug, true ); } $this->_storage = FS_Key_Value_Storage::instance( $module_type . '_data', $slug, $this->_blog_id ); } /** * Tells this storage wrapper class that the context plugin is network active. This flag will affect how values * are retrieved/stored from/into the storage. * * @author Leo Fajardo (@leorw) * * @param bool $is_network_active * @param bool $is_delegated_connection */ function set_network_active( $is_network_active = true, $is_delegated_connection = false ) { $this->_is_network_active = $is_network_active; $this->_is_delegated_connection = $is_delegated_connection; } /** * Switch the context of the site level storage manager. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param int $blog_id */ function set_site_blog_context( $blog_id ) { $this->_storage = $this->get_site_storage( $blog_id ); $this->_blog_id = $blog_id; } /** * @author Leo Fajardo (@leorw) * * @param string $key * @param mixed $value * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP). * @param int $option_level Since 2.5.1 * @param bool $flush */ function store( $key, $value, $network_level_or_blog_id = null, $option_level = self::OPTION_LEVEL_UNDEFINED, $flush = true ) { if ( $this->should_use_network_storage( $key, $network_level_or_blog_id, $option_level ) ) { $this->_network_storage->store( $key, $value, $flush ); } else { $storage = $this->get_site_storage( $network_level_or_blog_id ); $storage->store( $key, $value, $flush ); } } /** * @author Leo Fajardo (@leorw) * * @param bool $store * @param string[] $exceptions Set of keys to keep and not clear. * @param int|null|bool $network_level_or_blog_id */ function clear_all( $store = true, $exceptions = array(), $network_level_or_blog_id = null ) { if ( ! $this->_is_multisite || false === $network_level_or_blog_id || is_null( $network_level_or_blog_id ) || is_numeric( $network_level_or_blog_id ) ) { $storage = $this->get_site_storage( $network_level_or_blog_id ); $storage->clear_all( $store, $exceptions ); } if ( $this->_is_multisite && ( true === $network_level_or_blog_id || is_null( $network_level_or_blog_id ) ) ) { $this->_network_storage->clear_all( $store, $exceptions ); } } /** * @author Leo Fajardo (@leorw) * * @param string $key * @param bool $store * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP). */ function remove( $key, $store = true, $network_level_or_blog_id = null ) { if ( $this->should_use_network_storage( $key, $network_level_or_blog_id ) ) { $this->_network_storage->remove( $key, $store ); } else { $storage = $this->get_site_storage( $network_level_or_blog_id ); $storage->remove( $key, $store ); } } /** * @author Leo Fajardo (@leorw) * * @param string $key * @param mixed $default * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP). * @param int $option_level Since 2.5.1 * * @return mixed */ function get( $key, $default = false, $network_level_or_blog_id = null, $option_level = self::OPTION_LEVEL_UNDEFINED ) { if ( $this->should_use_network_storage( $key, $network_level_or_blog_id, $option_level ) ) { return $this->_network_storage->get( $key, $default ); } else { $storage = $this->get_site_storage( $network_level_or_blog_id ); return $storage->get( $key, $default ); } } /** * Multisite activated: * true: Save network storage. * int: Save site specific storage. * false|0: Save current site storage. * null: Save network and current site storage. * Site level activated: * Save site storage. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param bool|int|null $network_level_or_blog_id */ function save( $network_level_or_blog_id = null ) { if ( $this->_is_network_active && ( true === $network_level_or_blog_id || is_null( $network_level_or_blog_id ) ) ) { $this->_network_storage->save(); } if ( ! $this->_is_network_active || true !== $network_level_or_blog_id ) { $storage = $this->get_site_storage( $network_level_or_blog_id ); $storage->save(); } } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return string */ function get_module_slug() { return $this->_module_slug; } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return string */ function get_module_type() { return $this->_module_type; } /** * Migration script to the new storage data structure that is network compatible. * * IMPORTANT: * This method should be executed only after it is determined if this is a network * level compatible product activation. * * @author Vova Feldman (@svovaf) * @since 2.0.0 */ function migrate_to_network() { if ( ! $this->_is_multisite ) { return; } $updated = false; if ( ! isset( self::$_NETWORK_OPTIONS_MAP ) ) { self::load_network_options_map(); } foreach ( self::$_NETWORK_OPTIONS_MAP as $option => $storage_level ) { if ( ! $this->is_multisite_option( $option ) ) { continue; } if ( isset( $this->_storage->{$option} ) && ! isset( $this->_network_storage->{$option} ) ) { // Migrate option to the network storage. $this->_network_storage->store( $option, $this->_storage->{$option}, false ); $updated = true; } } if ( ! $updated ) { return; } // Update network level storage. $this->_network_storage->save(); // $this->_storage->save(); } #-------------------------------------------------------------------------------- #region Helper Methods #-------------------------------------------------------------------------------- /** * We don't want to load the map right away since it's not even needed in a non-MS environment. * * Example: * array( * 'option1' => 0, // Means that the option should always be stored on the network level. * 'option2' => 1, // Means that the option should be stored on the network level only when the module was network level activated. * 'option2' => 2, // Means that the option should be stored on the network level only when the module was network level activated AND the connection was NOT delegated. * 'option3' => 3, // Means that the option should always be stored on the site level. * ) * * @author Vova Feldman (@svovaf) * @since 2.0.0 */ private static function load_network_options_map() { self::$_NETWORK_OPTIONS_MAP = array( // Network level options. 'affiliate_application_data' => self::OPTION_LEVEL_NETWORK, 'beta_data' => self::OPTION_LEVEL_NETWORK, 'connectivity_test' => self::OPTION_LEVEL_NETWORK, 'handle_gdpr_admin_notice' => self::OPTION_LEVEL_NETWORK, 'has_trial_plan' => self::OPTION_LEVEL_NETWORK, 'install_sync_timestamp' => self::OPTION_LEVEL_NETWORK, 'install_sync_cron' => self::OPTION_LEVEL_NETWORK, 'is_anonymous_ms' => self::OPTION_LEVEL_NETWORK, 'is_network_activated' => self::OPTION_LEVEL_NETWORK, 'is_on' => self::OPTION_LEVEL_NETWORK, 'is_plugin_new_install' => self::OPTION_LEVEL_NETWORK, 'last_load_timestamp' => self::OPTION_LEVEL_NETWORK, 'network_install_blog_id' => self::OPTION_LEVEL_NETWORK, 'pending_sites_info' => self::OPTION_LEVEL_NETWORK, 'plugin_last_version' => self::OPTION_LEVEL_NETWORK, 'plugin_main_file' => self::OPTION_LEVEL_NETWORK, 'plugin_version' => self::OPTION_LEVEL_NETWORK, 'sdk_downgrade_mode' => self::OPTION_LEVEL_NETWORK, 'sdk_last_version' => self::OPTION_LEVEL_NETWORK, 'sdk_upgrade_mode' => self::OPTION_LEVEL_NETWORK, 'sdk_version' => self::OPTION_LEVEL_NETWORK, 'sticky_optin_added_ms' => self::OPTION_LEVEL_NETWORK, 'subscriptions' => self::OPTION_LEVEL_NETWORK, 'sync_timestamp' => self::OPTION_LEVEL_NETWORK, 'sync_cron' => self::OPTION_LEVEL_NETWORK, 'was_plugin_loaded' => self::OPTION_LEVEL_NETWORK, 'network_user_id' => self::OPTION_LEVEL_NETWORK, 'plugin_upgrade_mode' => self::OPTION_LEVEL_NETWORK, 'plugin_downgrade_mode' => self::OPTION_LEVEL_NETWORK, 'is_network_connected' => self::OPTION_LEVEL_NETWORK, /** * Special flag that is used when a super-admin upgrades to the new version of the SDK that supports network level integration, when the connection decision wasn't made for all the sites in the network. */ 'is_network_activation' => self::OPTION_LEVEL_NETWORK, 'license_migration' => self::OPTION_LEVEL_NETWORK, // When network activated, then network level. 'install_timestamp' => self::OPTION_LEVEL_NETWORK_ACTIVATED, 'prev_is_premium' => self::OPTION_LEVEL_NETWORK_ACTIVATED, 'require_license_activation' => self::OPTION_LEVEL_NETWORK_ACTIVATED, // If not network activated OR delegated, then site level. 'activation_timestamp' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED, 'expired_license_notice_shown' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED, 'is_whitelabeled' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED, 'last_license_key' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED, 'last_license_user_id' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED, 'prev_user_id' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED, 'sticky_optin_added' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED, 'uninstall_reason' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED, 'is_pending_activation' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED, 'pending_license_key' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED, // Site level options. 'is_anonymous' => self::OPTION_LEVEL_SITE, 'clone_id' => self::OPTION_LEVEL_SITE, ); } /** * This method will and should only be executed when is_multisite() is true. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $key * @param int $option_level Since 2.5.1 * * @return bool */ private function is_multisite_option( $key, $option_level = self::OPTION_LEVEL_UNDEFINED ) { if ( ! isset( self::$_NETWORK_OPTIONS_MAP ) ) { self::load_network_options_map(); } if ( self::OPTION_LEVEL_UNDEFINED === $option_level && isset( self::$_NETWORK_OPTIONS_MAP[ $key ] ) ) { $option_level = self::$_NETWORK_OPTIONS_MAP[ $key ]; } if ( self::OPTION_LEVEL_UNDEFINED === $option_level ) { // Option not found -> use site level storage. return false; } if ( self::OPTION_LEVEL_NETWORK === $option_level ) { // Option found and set to always use the network level storage on a multisite. return true; } if ( self::OPTION_LEVEL_SITE === $option_level ) { // Option found and set to always use the site level storage on a multisite. return false; } if ( ! $this->_is_network_active ) { return false; } if ( self::OPTION_LEVEL_NETWORK_ACTIVATED === $option_level ) { // Network activated. return true; } if ( self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED === $option_level && ! $this->_is_delegated_connection ) { // Network activated and not delegated. return true; } return false; } /** * @author Leo Fajardo * * @param string $key * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP). * @param int $option_level Since 2.5.1 * * @return bool */ private function should_use_network_storage( $key, $network_level_or_blog_id = null, $option_level = self::OPTION_LEVEL_UNDEFINED ) { if ( ! $this->_is_multisite ) { // Not a multisite environment. return false; } if ( is_numeric( $network_level_or_blog_id ) ) { // Explicitly asked to use a specified blog storage. return false; } if ( is_bool( $network_level_or_blog_id ) ) { // Explicitly specified whether it should use the network or blog level storage. return $network_level_or_blog_id; } // Determine which storage to use based on the option. return $this->is_multisite_option( $key, $option_level ); } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param int $blog_id * * @return \FS_Key_Value_Storage */ private function get_site_storage( $blog_id = 0 ) { if ( ! is_numeric( $blog_id ) || $blog_id == $this->_blog_id || 0 == $blog_id ) { return $this->_storage; } return FS_Key_Value_Storage::instance( $this->_module_type . '_data', $this->_storage->get_secondary_id(), $blog_id ); } #endregion #-------------------------------------------------------------------------------- #region Magic methods #-------------------------------------------------------------------------------- function __set( $k, $v ) { if ( $this->should_use_network_storage( $k ) ) { $this->_network_storage->{$k} = $v; } else { $this->_storage->{$k} = $v; } } function __isset( $k ) { return $this->should_use_network_storage( $k ) ? isset( $this->_network_storage->{$k} ) : isset( $this->_storage->{$k} ); } function __unset( $k ) { if ( $this->should_use_network_storage( $k ) ) { unset( $this->_network_storage->{$k} ); } else { unset( $this->_storage->{$k} ); } } function __get( $k ) { return $this->should_use_network_storage( $k ) ? $this->_network_storage->{$k} : $this->_storage->{$k}; } #endregion } PK!\a11Hlibraries/freemius/includes/supplements/fs-essential-functions-2.2.1.phpnu[libraries/freemius/includes/supplements/fs-migration-2.5.1.phpnu[ $install ) { if ( true === $install->is_disconnected ) { $permission_manager->update_site_tracking( false, ( 0 == $blog_id ) ? null : $blog_id, // Update only if permissions are not yet set. true ); } } } }PK!p2WW1libraries/freemius/includes/supplements/index.phpnu[ $data ) { if ( 0 === strpos( $file_real_path, fs_normalize_path( dirname( realpath( WP_PLUGIN_DIR . '/' . $relative_path ) ) . '/' ) ) ) { if ( '.' !== dirname( trailingslashit( $relative_path ) ) ) { return $relative_path; } } } return null; } PK!h811:libraries/freemius/includes/class-fs-garbage-collector.phpnu[ Map of product slugs to their last load timestamp, only for products that are not active. */ private $_gc_timestamp; /** * @var array> Map of product slugs to their data, as stored by the primary storage of `Freemius` class. */ private $_storage_data; function __construct( FS_Options $_accounts, $option_names, $type ) { $this->_accounts = $_accounts; $this->_options_names = $option_names; $this->_type = $type; $this->_plural_type = ( $type . 's' ); } function clean() { $this->_gc_timestamp = $this->_accounts->get_option( 'gc_timestamp', array() ); $this->_storage_data = $this->_accounts->get_option( $this->_type . '_data', array() ); $options = $this->load_options(); $has_updated_option = false; $filtered_products = $this->get_filtered_products(); $products_to_clean = $filtered_products['products_to_clean']; $active_products_by_id_map = $filtered_products['active_products_by_id_map']; foreach( $products_to_clean as $product ) { $slug = $product->slug; // Clear the product's data. foreach( $options as $option_name => $option ) { $updated = false; /** * We expect to deal with only array like options here. * @todo - Refactor this to create dedicated GC classes for every option, then we can make the code mode predictable. * For example, depending on data integrity of `plugins` we can still miss something entirely in the `plugin_data` or vice-versa. * A better algorithm is to iterate over all options individually in separate classes and check against primary storage to see if those can be garbage collected. * But given the chance of data integrity issue is very low, we let this run for now and gather feedback. */ if ( ! is_array( $option ) ) { continue; } if ( array_key_exists( $slug, $option ) ) { unset( $option[ $slug ] ); $updated = true; } else if ( array_key_exists( "{$slug}:{$this->_type}", $option ) ) { /* admin_notices */ unset( $option[ "{$slug}:{$this->_type}" ] ); $updated = true; } else if ( isset( $product->id ) && array_key_exists( $product->id, $option ) ) { /* all_licenses, add-ons, and id_slug_type_path_map */ $is_inactive_by_id = ! isset( $active_products_by_id_map[ $product->id ] ); $is_inactive_by_slug = ( 'id_slug_type_path_map' === $option_name && ( ! isset( $option[ $product->id ]['slug'] ) || $slug === $option[ $product->id ]['slug'] ) ); if ( $is_inactive_by_id || $is_inactive_by_slug ) { unset( $option[ $product->id ] ); $updated = true; } } else if ( /* file_slug_map */ isset( $product->file ) && array_key_exists( $product->file, $option ) && $slug === $option[ $product->file ] ) { unset( $option[ $product->file ] ); $updated = true; } if ( $updated ) { $this->_accounts->set_option( $option_name, $option ); $options[ $option_name ] = $option; $has_updated_option = true; } } // Clear the product's data from the primary storage. if ( isset( $this->_storage_data[ $slug ] ) ) { unset( $this->_storage_data[ $slug ] ); $has_updated_option = true; } // Clear from GC timestamp. // @todo - This perhaps needs a separate garbage collector for all expired products. But the chance of left-over is very slim. if ( isset( $this->_gc_timestamp[ $slug ] ) ) { unset( $this->_gc_timestamp[ $slug ] ); $has_updated_option = true; } } $this->_accounts->set_option( 'gc_timestamp', $this->_gc_timestamp ); $this->_accounts->set_option( $this->_type . '_data', $this->_storage_data ); return $has_updated_option; } private function get_all_option_names() { return array_merge( array( 'admin_notices', 'updates', 'all_licenses', 'addons', 'id_slug_type_path_map', 'file_slug_map', ), $this->_options_names ); } private function get_products() { $products = $this->_accounts->get_option( $this->_plural_type, array() ); // Fill any missing product found in the primary storage. // @todo - This wouldn't be needed if we use dedicated GC design for every options. The options themselves would provide such information. foreach( $this->_storage_data as $slug => $product_data ) { if ( ! isset( $products[ $slug ] ) ) { $products[ $slug ] = (object) $product_data; } // This is needed to handle a scenario in which there are duplicate sets of data for the same product, but one of them needs to be removed. $products[ $slug ] = clone $products[ $slug ]; // The reason for having the line above. This also handles a scenario in which the slug is either empty or not empty but incorrect. $products[ $slug ]->slug = $slug; } $this->update_gc_timestamp( $products ); return $products; } private function get_filtered_products() { $products_to_clean = array(); $active_products_by_id_map = array(); $products = $this->get_products(); foreach ( $products as $slug => $product_data ) { if ( ! is_object( $product_data ) ) { continue; } if ( $this->is_product_active( $slug ) ) { $active_products_by_id_map[ $product_data->id ] = true; continue; } $is_addon = ( ! empty( $product_data->parent_plugin_id ) ); if ( ! $is_addon ) { $products_to_clean[] = $product_data; } else { /** * If add-on, add to the beginning of the array so that add-ons are removed before their parent. This is to prevent an unexpected issue when an add-on exists but its parent was already removed. */ array_unshift( $products_to_clean, $product_data ); } } return array( 'products_to_clean' => $products_to_clean, 'active_products_by_id_map' => $active_products_by_id_map, ); } /** * @param string $slug * * @return bool */ private function is_product_active( $slug ) { $instances = Freemius::_get_all_instances(); foreach ( $instances as $instance ) { if ( $instance->get_slug() === $slug ) { return true; } } $expiration_time = fs_get_optional_constant( 'WP_FS__GARBAGE_COLLECTOR_EXPIRATION_TIME_SECS', ( WP_FS__TIME_WEEK_IN_SEC * 4 ) ); if ( $this->get_last_load_timestamp( $slug ) > ( time() - $expiration_time ) ) { // Last activation was within the last 4 weeks. return true; } return false; } private function load_options() { $options = array(); $option_names = $this->get_all_option_names(); foreach ( $option_names as $option_name ) { $options[ $option_name ] = $this->_accounts->get_option( $option_name, array() ); } return $options; } /** * Updates the garbage collector timestamp, only if it was not already set by the product's primary storage. * * @param array $products * * @return void */ private function update_gc_timestamp( $products ) { foreach ($products as $slug => $product_data) { if ( ! is_object( $product_data ) && ! is_array( $product_data ) ) { continue; } // If the product is active, we don't need to update the gc_timestamp. if ( isset( $this->_storage_data[ $slug ]['last_load_timestamp'] ) ) { continue; } // First try to check if the product is present in the primary storage. If so update that. if ( isset( $this->_storage_data[ $slug ] ) ) { $this->_storage_data[ $slug ]['last_load_timestamp'] = time(); } else if ( ! isset( $this->_gc_timestamp[ $slug ] ) ) { // If not, fallback to the gc_timestamp, but we don't want to update it more than once. $this->_gc_timestamp[ $slug ] = time(); } } } private function get_last_load_timestamp( $slug ) { if ( isset( $this->_storage_data[ $slug ]['last_load_timestamp'] ) ) { return $this->_storage_data[ $slug ]['last_load_timestamp']; } return isset( $this->_gc_timestamp[ $slug ] ) ? $this->_gc_timestamp[ $slug ] : // This should never happen, but if it does, let's assume the product is not expired. time(); } } class FS_User_Garbage_Collector implements FS_I_Garbage_Collector { private $_accounts; private $_types; function __construct( FS_Options $_accounts, array $types ) { $this->_accounts = $_accounts; $this->_types = $types; } function clean() { $users = Freemius::get_all_users(); $user_has_install_map = $this->get_user_has_install_map(); if ( count( $users ) === count( $user_has_install_map ) ) { return false; } $products_user_id_license_ids_map = $this->_accounts->get_option( 'user_id_license_ids_map', array() ); $has_updated_option = false; foreach ( $users as $user_id => $user ) { if ( ! isset( $user_has_install_map[ $user_id ] ) ) { unset( $users[ $user_id ] ); foreach( $products_user_id_license_ids_map as $product_id => $user_id_license_ids_map ) { unset( $user_id_license_ids_map[ $user_id ] ); if ( empty( $user_id_license_ids_map ) ) { unset( $products_user_id_license_ids_map[ $product_id ] ); } else { $products_user_id_license_ids_map[ $product_id ] = $user_id_license_ids_map; } } $this->_accounts->set_option( 'users', $users ); $this->_accounts->set_option( 'user_id_license_ids_map', $products_user_id_license_ids_map ); $has_updated_option = true; } } return $has_updated_option; } private function get_user_has_install_map() { $user_has_install_map = array(); foreach ( $this->_types as $product_type ) { $option_name = ( WP_FS__MODULE_TYPE_PLUGIN !== $product_type ) ? "{$product_type}_sites" : 'sites'; $installs = $this->_accounts->get_option( $option_name, array() ); foreach ( $installs as $install ) { $user_has_install_map[ $install->user_id ] = true; } } return $user_has_install_map; } } // Main entry-level class. class FS_Garbage_Collector implements FS_I_Garbage_Collector { /** * @var FS_Garbage_Collector * @since 2.6.0 */ private static $_instance; /** * @return FS_Garbage_Collector */ static function instance() { if ( ! isset( self::$_instance ) ) { self::$_instance = new self(); } return self::$_instance; } #endregion private function __construct() { } function clean() { $_accounts = FS_Options::instance( WP_FS__ACCOUNTS_OPTION_NAME, true ); $products_cleaners = $this->get_product_cleaners( $_accounts ); $has_cleaned = false; foreach ( $products_cleaners as $products_cleaner ) { if ( $products_cleaner->clean() ) { $has_cleaned = true; } } if ( $has_cleaned ) { $user_cleaner = new FS_User_Garbage_Collector( $_accounts, array_keys( $products_cleaners ) ); $user_cleaner->clean(); } // @todo - We need a garbage collector for `all_plugins` and `active_plugins` (and variants of themes). // Always store regardless of whether there were cleaned products or not since during the process, the logic may set the last load timestamp of some products. $_accounts->store(); } /** * @param FS_Options $_accounts * * @return FS_I_Garbage_Collector[] */ private function get_product_cleaners( FS_Options $_accounts ) { /** * @var FS_I_Garbage_Collector[] $products_cleaners */ $products_cleaners = array(); $products_cleaners[ WP_FS__MODULE_TYPE_PLUGIN ] = new FS_Product_Garbage_Collector( $_accounts, array( 'sites', 'plans', 'plugins', ), WP_FS__MODULE_TYPE_PLUGIN ); $products_cleaners[ WP_FS__MODULE_TYPE_THEME ] = new FS_Product_Garbage_Collector( $_accounts, array( 'theme_sites', 'theme_plans', 'themes', ), WP_FS__MODULE_TYPE_THEME ); return $products_cleaners; } }PK!p2WW%libraries/freemius/includes/index.phpnu[register_control_type( 'FS_Customizer_Upsell_Control' ); parent::__construct( $manager, $id, $args ); } /** * Enqueue resources for the control. */ public function enqueue() { fs_enqueue_local_style( 'fs_customizer', 'customizer.css' ); } /** * Json conversion */ public function to_json() { $pricing_cta = esc_html( $this->fs->get_pricing_cta_label() ) . '  ' . ( is_rtl() ? '←' : '➤' ); parent::to_json(); $this->json['button_text'] = $pricing_cta; $this->json['button_url'] = $this->fs->is_in_trial_promotion() ? $this->fs->get_trial_url() : $this->fs->get_upgrade_url(); $api = FS_Plugin::is_valid_id( $this->fs->get_bundle_id() ) ? $this->fs->get_api_bundle_scope() : $this->fs->get_api_plugin_scope(); // Load features. $pricing = $api->get( $this->fs->add_show_pending( "pricing.json" ) ); if ( $this->fs->is_api_result_object( $pricing, 'plans' ) ) { // Add support features. if ( is_array( $pricing->plans ) && 0 < count( $pricing->plans ) ) { $support_features = array( 'kb' => 'Help Center', 'forum' => 'Support Forum', 'email' => 'Priority Email Support', 'phone' => 'Phone Support', 'skype' => 'Skype Support', 'is_success_manager' => 'Personal Success Manager', ); for ( $i = 0, $len = count( $pricing->plans ); $i < $len; $i ++ ) { if ( 'free' == $pricing->plans[$i]->name ) { continue; } if ( ! isset( $pricing->plans[ $i ]->features ) || ! is_array( $pricing->plans[ $i ]->features ) ) { $pricing->plans[$i]->features = array(); } foreach ( $support_features as $key => $label ) { $key = ( 'is_success_manager' !== $key ) ? "support_{$key}" : $key; if ( ! empty( $pricing->plans[ $i ]->{$key} ) ) { $support_feature = new stdClass(); $support_feature->title = $label; $pricing->plans[ $i ]->features[] = $support_feature; } } } $this->json['plans'] = $pricing->plans; } } $this->json['strings'] = array( 'plan' => $this->fs->get_text_x_inline( 'Plan', 'as product pricing plan', 'plan' ), ); } /** * Control content */ public function content_template() { ?>
<# if ( data.plans ) { #>
    <# for (i in data.plans) { #> <# if ( 'free' != data.plans[i].name && (null != data.plans[i].features && 0 < data.plans[i].features.length) ) { #>
  • <# if ( data.plans[i].description ) { #>

    {{ data.plans[i].description }}

    <# } #> <# if ( data.plans[i].features ) { #>
      <# for ( j in data.plans[i].features ) { #>
    • <# if ( data.plans[i].features[j].value ) { #>{{ data.plans[i].features[j].value }} <# } #>{{ data.plans[i].features[j].title }} <# if ( data.plans[i].features[j].description ) { #> {{ data.plans[i].features[j].description }} <# } #>
    • <# } #>
    <# } #> <# if ( 'free' != data.plans[i].name ) { #> {{{ data.button_text }}} <# } #>
  • <# } #> <# } #>
<# } #>
register_section_type( 'FS_Customizer_Support_Section' ); parent::__construct( $manager, $id, $args ); } /** * The type of customize section being rendered. * * @since 1.0.0 * @access public * @var string */ public $type = 'freemius-support-section'; /** * @var Freemius */ public $fs = null; /** * Add custom parameters to pass to the JS via JSON. * * @since 1.0.0 */ public function json() { $json = parent::json(); $is_contact_visible = $this->fs->is_page_visible( 'contact' ); $is_support_visible = $this->fs->is_page_visible( 'support' ); $json['theme_title'] = $this->fs->get_plugin_name(); if ( $is_contact_visible && $is_support_visible ) { $json['theme_title'] .= ' ' . $this->fs->get_text_inline( 'Support', 'support' ); } if ( $is_contact_visible ) { $json['contact'] = array( 'label' => $this->fs->get_text_inline( 'Contact Us', 'contact-us' ), 'url' => $this->fs->contact_url(), ); } if ( $is_support_visible ) { $json['support'] = array( 'label' => $this->fs->get_text_inline( 'Support Forum', 'support-forum' ), 'url' => $this->fs->get_support_forum_url() ); } return $json; } /** * Outputs the Underscore.js template. * * @since 1.0.0 */ protected function render_template() { ?>
  • {{ data.theme_title }} <# if ( data.contact && data.support ) { #>
    <# } #> <# if ( data.contact ) { #> {{ data.contact.label }} <# } #> <# if ( data.support ) { #> {{ data.support.label }} <# } #> <# if ( data.contact && data.support ) { #>
    <# } #>

  • name = strtolower( $plan->name ); } } static function get_type() { return 'plan'; } /** * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool */ function is_free() { return ( 'free' === $this->name ); } /** * Checks if this plan supports "Technical Support". * * @author Leo Fajardo (leorw) * @since 1.2.0 * * @return bool */ function has_technical_support() { return ( ! empty( $this->support_email ) || ! empty( $this->support_skype ) || ! empty( $this->support_phone ) || ! empty( $this->is_success_manager ) ); } /** * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool */ function has_trial() { return ! $this->is_free() && is_numeric( $this->trial_period ) && ( $this->trial_period > 0 ); } }PK!tA 8libraries/freemius/includes/entities/class-fs-entity.phpnu[ $def_value ) { $this->{$key} = isset( $entity->{$key} ) ? $entity->{$key} : $def_value; } } static function get_type() { return 'type'; } /** * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param FS_Entity $entity1 * @param FS_Entity $entity2 * * @return bool */ static function equals( $entity1, $entity2 ) { if ( is_null( $entity1 ) && is_null( $entity2 ) ) { return true; } else if ( is_object( $entity1 ) && is_object( $entity2 ) ) { return ( $entity1->id == $entity2->id ); } else if ( is_object( $entity1 ) ) { return is_null( $entity1->id ); } else { return is_null( $entity2->id ); } } private $_is_updated = false; /** * Update object property. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @param string|array[string]mixed $key * @param string|bool $val * * @return bool */ function update( $key, $val = false ) { if ( ! is_array( $key ) ) { $key = array( $key => $val ); } $is_updated = false; foreach ( $key as $k => $v ) { if ( $this->{$k} === $v ) { continue; } if ( ( is_string( $this->{$k} ) && is_numeric( $v ) || ( is_numeric( $this->{$k} ) && is_string( $v ) ) ) && $this->{$k} == $v ) { continue; } // Update value. $this->{$k} = $v; $is_updated = true; } $this->_is_updated = $is_updated; return $is_updated; } /** * Checks if entity was updated. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool */ function is_updated() { return $this->_is_updated; } /** * @param $id * * @author Vova Feldman (@svovaf) * @since 1.1.2 * * @return bool */ static function is_valid_id($id){ return is_numeric($id); } /** * @author Leo Fajardo (@leorw) * @since 2.3.1 * * @return string */ public static function get_class_name() { return get_called_class(); } }PK!Ly9libraries/freemius/includes/entities/class-fs-billing.phpnu[libraries/freemius/includes/entities/class-fs-scope-entity.phpnu[is_beta ); } } function get_name() { return trim( ucfirst( trim( is_string( $this->first ) ? $this->first : '' ) ) . ' ' . ucfirst( trim( is_string( $this->last ) ? $this->last : '' ) ) ); } function is_verified() { return ( isset( $this->is_verified ) && true === $this->is_verified ); } /** * @author Leo Fajardo (@leorw) * @since 2.4.2 * * @return bool */ function is_beta() { // Return `false` since this is just for backward compatibility. return false; } static function get_type() { return 'user'; } }PK!6k/G6libraries/freemius/includes/entities/class-fs-site.phpnu[plan_id = $site->plan_id; } if ( ! is_bool( $this->is_disconnected ) ) { $this->is_disconnected = false; } } static function get_type() { return 'install'; } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $url * * @return bool */ static function is_localhost_by_address( $url ) { if ( false !== strpos( $url, '127.0.0.1' ) || false !== strpos( $url, 'localhost' ) ) { return true; } if ( ! fs_starts_with( $url, 'http' ) ) { $url = 'http://' . $url; } $url_parts = parse_url( $url ); $subdomain = $url_parts['host']; return ( // Starts with. fs_starts_with( $subdomain, 'local.' ) || fs_starts_with( $subdomain, 'dev.' ) || fs_starts_with( $subdomain, 'test.' ) || fs_starts_with( $subdomain, 'stage.' ) || fs_starts_with( $subdomain, 'staging.' ) || // Ends with. fs_ends_with( $subdomain, '.dev' ) || fs_ends_with( $subdomain, '.test' ) || fs_ends_with( $subdomain, '.staging' ) || fs_ends_with( $subdomain, '.local' ) || fs_ends_with( $subdomain, '.example' ) || fs_ends_with( $subdomain, '.invalid' ) || // GoDaddy test/dev. fs_ends_with( $subdomain, '.myftpupload.com' ) || // ngrok tunneling. fs_ends_with( $subdomain, '.ngrok.io' ) || // wpsandbox. fs_ends_with( $subdomain, '.wpsandbox.pro' ) || // SiteGround staging. fs_starts_with( $subdomain, 'staging' ) || // WPEngine staging. fs_ends_with( $subdomain, '.staging.wpengine.com' ) || fs_ends_with( $subdomain, '.dev.wpengine.com' ) || fs_ends_with( $subdomain, '.wpengine.com' ) || fs_ends_with( $subdomain, '.wpenginepowered.com' ) || // Pantheon ( fs_ends_with( $subdomain, 'pantheonsite.io' ) && ( fs_starts_with( $subdomain, 'test-' ) || fs_starts_with( $subdomain, 'dev-' ) ) ) || // Cloudways fs_ends_with( $subdomain, '.cloudwaysapps.com' ) || // Kinsta ( ( fs_starts_with( $subdomain, 'stg-' ) || fs_starts_with( $subdomain, 'staging-' ) || fs_starts_with( $subdomain, 'env-' ) ) && ( fs_ends_with( $subdomain, '.kinsta.com' ) || fs_ends_with( $subdomain, '.kinsta.cloud' ) ) ) || // DesktopServer fs_ends_with( $subdomain, '.dev.cc' ) || // Pressable fs_ends_with( $subdomain, '.mystagingwebsite.com' ) || // WPMU DEV ( fs_ends_with( $subdomain, '.tempurl.host' ) || fs_ends_with( $subdomain, '.wpmudev.host' ) ) || // Vendasta ( fs_ends_with( $subdomain, '.websitepro-staging.com' ) || fs_ends_with( $subdomain, '.websitepro.hosting' ) ) || // InstaWP fs_ends_with( $subdomain, '.instawp.xyz' ) || // 10Web Hosting ( fs_ends_with( $subdomain, '-dev.10web.site' ) || fs_ends_with( $subdomain, '-dev.10web.cloud' ) ) ); } /** * @author Leo Fajardo (@leorw) * @since 2.9.1 * * @param string $host * * @return bool */ static function is_playground_wp_environment_by_host( $host ) { // Services aimed at providing a WordPress sandbox environment. $sandbox_wp_environment_domains = array( // InstaWP 'instawp.xyz', // TasteWP 'tastewp.com', // WordPress Playground 'playground.wordpress.net', ); foreach ( $sandbox_wp_environment_domains as $domain) { if ( ( $host === $domain ) || fs_ends_with( $host, '.' . $domain ) || fs_ends_with( $host, '-' . $domain ) ) { return true; } } return false; } function is_localhost() { return ( WP_FS__IS_LOCALHOST_FOR_SERVER || self::is_localhost_by_address( $this->url ) ); } /** * Check if site in trial. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool */ function is_trial() { return is_numeric( $this->trial_plan_id ) && ( strtotime( $this->trial_ends ) > WP_FS__SCRIPT_START_TIME ); } /** * Check if user already utilized the trial with the current install. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool */ function is_trial_utilized() { return is_numeric( $this->trial_plan_id ); } /** * @author Edgar Melkonyan * * @return bool */ function is_beta() { return ( isset( $this->is_beta ) && true === $this->is_beta ); } /** * @author Leo Fajardo (@leorw) * @since 2.5.1 * * @param string $site_url * * @return bool */ function is_clone( $site_url ) { $clone_install_url = trailingslashit( fs_strip_url_protocol( $this->url ) ); return ( $clone_install_url !== $site_url ); } }PK!s 8libraries/freemius/includes/entities/class-fs-plugin.phpnu[is_premium = false; $this->is_live = true; if ( empty( $this->premium_slug ) && ! empty( $plugin->slug ) ) { $this->premium_slug = "{$this->slug}-premium"; } if ( empty( $this->premium_suffix ) ) { $this->premium_suffix = '(Premium)'; } if ( isset( $plugin->info ) && is_object( $plugin->info ) ) { $this->info = new FS_Plugin_Info( $plugin->info ); } } /** * Check if plugin is an add-on (has parent). * * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @return bool */ function is_addon() { return isset( $this->parent_plugin_id ) && is_numeric( $this->parent_plugin_id ); } /** * @author Leo Fajardo (@leorw) * @since 1.2.3 * * @return bool */ function has_affiliate_program() { return ( ! empty( $this->affiliate_moderation ) ); } static function get_type() { return 'plugin'; } }PK!7=libraries/freemius/includes/entities/class-fs-plugin-info.phpnu[monthly_price ) && $this->monthly_price > 0 ); } /** * @author Vova Feldman (@svovaf) * @since 1.1.8 * * @return bool */ function has_annual() { return ( is_numeric( $this->annual_price ) && $this->annual_price > 0 ); } /** * @author Vova Feldman (@svovaf) * @since 1.1.8 * * @return bool */ function has_lifetime() { return ( is_numeric( $this->lifetime_price ) && $this->lifetime_price > 0 ); } /** * Check if unlimited licenses pricing. * * @author Vova Feldman (@svovaf) * @since 1.1.8 * * @return bool */ function is_unlimited() { return is_null( $this->licenses ); } /** * Check if pricing has more than one billing cycle. * * @author Vova Feldman (@svovaf) * @since 1.1.8 * * @return bool */ function is_multi_cycle() { $cycles = 0; if ( $this->has_monthly() ) { $cycles ++; } if ( $this->has_annual() ) { $cycles ++; } if ( $this->has_lifetime() ) { $cycles ++; } return $cycles > 1; } /** * Get annual over monthly discount. * * @author Vova Feldman (@svovaf) * @since 1.1.8 * * @return int */ function annual_discount_percentage() { return floor( $this->annual_savings() / ( $this->monthly_price * 12 * ( $this->is_unlimited() ? 1 : $this->licenses ) ) * 100 ); } /** * Get annual over monthly savings. * * @author Vova Feldman (@svovaf) * @since 1.1.8 * * @return float */ function annual_savings() { return ( $this->monthly_price * 12 - $this->annual_price ) * ( $this->is_unlimited() ? 1 : $this->licenses ); } /** * @author Leo Fajardo (@leorw) * @since 2.3.1 * * @return bool */ function is_usd() { return ( 'usd' === $this->currency ); } }PK!Ann;libraries/freemius/includes/entities/class-fs-affiliate.phpnu[status ); } /** * @author Leo Fajardo * * @return bool */ function is_pending() { return ( 'pending' === $this->status ); } /** * @author Leo Fajardo * * @return bool */ function is_suspended() { return ( 'suspended' === $this->status ); } /** * @author Leo Fajardo * * @return bool */ function is_rejected() { return ( 'rejected' === $this->status ); } /** * @author Leo Fajardo * * @return bool */ function is_blocked() { return ( 'blocked' === $this->status ); } }PK!.- - >libraries/freemius/includes/entities/class-fs-subscription.phpnu[is_canceled() ) { return false; } return ( ! empty( $this->next_payment ) && strtotime( $this->next_payment ) > WP_FS__SCRIPT_START_TIME ); } /** * @author Vova Feldman (@svovaf) * @since 2.3.1 * * @return bool */ function is_canceled() { return ! is_null( $this->canceled_at ); } /** * Subscription considered to be new without any payments * if the next payment should be made within less than 24 hours * from the subscription creation. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool */ function is_first_payment_pending() { return ( WP_FS__TIME_24_HOURS_IN_SEC >= strtotime( $this->next_payment ) - strtotime( $this->created ) ); } /** * @author Vova Feldman (@svovaf) * @since 1.1.7 */ function has_trial() { return ! is_null( $this->trial_ends ); } }PK!HbEEAlibraries/freemius/includes/entities/class-fs-affiliate-terms.phpnu[commission_type ) ? ( '$' . $this->commission ) : ( $this->commission . '%' ); } /** * @author Leo Fajardo (@leorw) * * @return bool */ function has_lifetime_commission() { return ( 0 !== $this->future_payments_days ); } /** * @author Leo Fajardo (@leorw) * * @return bool */ function is_session_cookie() { return ( 0 == $this->cookie_days ); } /** * @author Leo Fajardo (@leorw) * * @return bool */ function has_renewals_commission() { return ( is_null( $this->commission_renewals_days ) || $this->commission_renewals_days > 0 ); } }PK!!@libraries/freemius/includes/entities/class-fs-plugin-license.phpnu[is_features_enabled() ) { return 0; } if ( $this->is_unlimited() ) { return 999; } return ( $this->quota - $this->activated - ( $this->is_free_localhost ? 0 : $this->activated_local ) ); } /** * Check if single site license. * * @author Vova Feldman (@svovaf) * @since 1.1.8.1 * * @return bool */ function is_single_site() { return ( is_numeric( $this->quota ) && 1 == $this->quota ); } /** * @author Vova Feldman (@svovaf) * @since 1.0.5 * * @return bool */ function is_expired() { return ! $this->is_lifetime() && ( strtotime( $this->expiration ) < WP_FS__SCRIPT_START_TIME ); } /** * Check if license is not expired. * * @author Vova Feldman (@svovaf) * @since 1.2.1 * * @return bool */ function is_valid() { return ! $this->is_expired(); } /** * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @return bool */ function is_lifetime() { return is_null( $this->expiration ); } /** * @author Vova Feldman (@svovaf) * @since 1.2.0 * * @return bool */ function is_unlimited() { return is_null( $this->quota ); } /** * Check if license is fully utilized. * * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param bool|null $is_localhost * * @return bool */ function is_utilized( $is_localhost = null ) { if ( is_null( $is_localhost ) ) { $is_localhost = WP_FS__IS_LOCALHOST_FOR_SERVER; } if ( $this->is_unlimited() ) { return false; } return ! ( $this->is_free_localhost && $is_localhost ) && ( $this->quota <= $this->activated + ( $this->is_free_localhost ? 0 : $this->activated_local ) ); } /** * Check if license can be activated. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param bool|null $is_localhost * * @return bool */ function can_activate( $is_localhost = null ) { return ! $this->is_utilized( $is_localhost ) && $this->is_features_enabled(); } /** * Check if license can be activated on a given number of production and localhost sites. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param int $production_count * @param int $localhost_count * * @return bool */ function can_activate_bulk( $production_count, $localhost_count ) { if ( $this->is_unlimited() ) { return true; } /** * For simplicity, the logic will work as following: when given X sites to activate the license on, if it's * possible to activate on ALL of them, do the activation. If it's not possible to activate on ALL of them, * do NOT activate on any of them. */ return ( $this->quota >= $this->activated + $production_count + ( $this->is_free_localhost ? 0 : $this->activated_local + $localhost_count ) ); } /** * @author Vova Feldman (@svovaf) * @since 1.2.1 * * @return bool */ function is_active() { return ( ! $this->is_cancelled ); } /** * Check if license's plan features are enabled. * * - Either if plan not expired * - If expired, based on the configuration to block features or not. * * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @return bool */ function is_features_enabled() { return $this->is_active() && ( ! $this->is_block_features || ! $this->is_expired() ); } /** * Subscription considered to be new without any payments * if the license expires in less than 24 hours * from the license creation. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool */ function is_first_payment_pending() { if ( $this->is_lifetime() ) { return false; } return ( WP_FS__TIME_24_HOURS_IN_SEC >= strtotime( $this->expiration ) - strtotime( $this->created ) ); } /** * @return int */ function total_activations() { return ( $this->activated + $this->activated_local ); } /** * @author Vova Feldman (@svovaf) * @since 2.3.1 * * @return string */ function get_html_escaped_masked_secret_key() { return self::mask_secret_key_for_html( $this->secret_key ); } /** * @author Vova Feldman (@svovaf) * @since 2.3.1 * * @param string $secret_key * * @return string */ static function mask_secret_key_for_html( $secret_key ) { return ( // Initial 6 chars - sk_ABC htmlspecialchars( substr( $secret_key, 0, 6 ) ) . // Masking str_pad( '', ( strlen( $secret_key ) - 9 ) * 6, '•' ) . // Last 3 chars. htmlspecialchars( substr( $secret_key, - 3 ) ) ); } } PK!w 9libraries/freemius/includes/entities/class-fs-payment.phpnu[bound_payment_id ) && 0 > $this->gross ); } /** * Checks if the payment was migrated from another platform. * * @author Vova Feldman (@svovaf) * @since 2.0.2 * * @return bool */ function is_migrated() { return ( 0 != $this->source ); } /** * Returns the gross in this format: * `{symbol}{amount | 2 decimal digits} {currency | uppercase}` * * Examples: £9.99 GBP, -£9.99 GBP. * * @author Leo Fajardo (@leorw) * @since 2.3.0 * * @return string */ function formatted_gross() { return ( ( $this->gross < 0 ? '-' : '' ) . $this->get_symbol() . number_format( abs( $this->gross ), 2, '.', ',' ) . ' ' . strtoupper( $this->currency ) ); } /** * A map between supported currencies with their symbols. * * @var array */ static $CURRENCY_2_SYMBOL; /** * @author Leo Fajardo (@leorw) * @since 2.3.0 * * @return string */ private function get_symbol() { if ( ! isset( self::$CURRENCY_2_SYMBOL ) ) { // Lazy load. self::$CURRENCY_2_SYMBOL = array( self::CURRENCY_USD => '$', self::CURRENCY_GBP => '£', self::CURRENCY_EUR => '€', ); } return self::$CURRENCY_2_SYMBOL[ $this->currency ]; } }PK!%E|KK<libraries/freemius/includes/entities/class-fs-plugin-tag.phpnu[release_mode ); } }PK!ٺ//7libraries/freemius/includes/class-freemius-abstract.phpnu[is_registered() && $fs->is_tracking_allowed()` * * @since 1.0.1 * * @param bool $ignore_anonymous_state Since 2.5.1 * * @return bool */ abstract function is_registered( $ignore_anonymous_state = false ); /** * Check if the user skipped connecting the account with Freemius. * * @since 1.0.7 * * @return bool */ abstract function is_anonymous(); /** * Check if the user currently in activation mode. * * @since 1.0.7 * * @return bool */ abstract function is_activation_mode(); #endregion #---------------------------------------------------------------------------------- #region Module Type #---------------------------------------------------------------------------------- /** * Checks if the plugin's type is "plugin". The other type is "theme". * * @author Leo Fajardo (@leorw) * @since 1.2.2 * * @return bool */ abstract function is_plugin(); /** * Checks if the module type is "theme". The other type is "plugin". * * @author Leo Fajardo (@leorw) * @since 1.2.2 * * @return bool */ function is_theme() { return ( ! $this->is_plugin() ); } #endregion #---------------------------------------------------------------------------------- #region Permissions #---------------------------------------------------------------------------------- /** * Check if plugin must be WordPress.org compliant. * * @since 1.0.7 * * @return bool */ abstract function is_org_repo_compliant(); /** * Check if plugin is allowed to install executable files. * * @author Vova Feldman (@svovaf) * @since 1.0.5 * * @return bool */ function is_allowed_to_install() { return ( $this->is_premium() || ! $this->is_org_repo_compliant() ); } #endregion /** * Check if user in trial or in free plan (not paying). * * @author Vova Feldman (@svovaf) * @since 1.0.4 * * @return bool */ function is_not_paying() { return ( $this->is_trial() || $this->is_free_plan() ); } /** * Check if the user has an activated and valid paid license on current plugin's install. * * @since 1.0.9 * * @return bool */ abstract function is_paying(); /** * Check if the user is paying or in trial. * * @since 1.0.9 * * @return bool */ function is_paying_or_trial() { return ( $this->is_paying() || $this->is_trial() ); } /** * Check if user in a trial or have feature enabled license. * * @author Vova Feldman (@svovaf) * @since 1.1.7 * * @return bool */ abstract function can_use_premium_code(); #---------------------------------------------------------------------------------- #region Premium Only #---------------------------------------------------------------------------------- /** * All logic wrapped in methods with "__premium_only()" suffix will be only * included in the premium code. * * Example: * if ( freemius()->is__premium_only() ) { * ... * } */ /** * Returns true when running premium plugin code. * * @since 1.0.9 * * @return bool */ function is__premium_only() { return $this->is_premium(); } /** * Check if the user has an activated and valid paid license on current plugin's install. * * @since 1.0.9 * * @return bool * */ function is_paying__premium_only() { return ( $this->is__premium_only() && $this->is_paying() ); } /** * All code wrapped in this statement will be only included in the premium code. * * @since 1.0.9 * * @param string $plan Plan name. * @param bool $exact If true, looks for exact plan. If false, also check "higher" plans. * * @return bool */ function is_plan__premium_only( $plan, $exact = false ) { return ( $this->is_premium() && $this->is_plan( $plan, $exact ) ); } /** * Check if plan matches active license' plan or active trial license' plan. * * All code wrapped in this statement will be only included in the premium code. * * @since 1.0.9 * * @param string $plan Plan name. * @param bool $exact If true, looks for exact plan. If false, also check "higher" plans. * * @return bool */ function is_plan_or_trial__premium_only( $plan, $exact = false ) { return ( $this->is_premium() && $this->is_plan_or_trial( $plan, $exact ) ); } /** * Check if the user is paying or in trial. * * All code wrapped in this statement will be only included in the premium code. * * @since 1.0.9 * * @return bool */ function is_paying_or_trial__premium_only() { return $this->is_premium() && $this->is_paying_or_trial(); } /** * Check if the user has an activated and valid paid license on current plugin's install. * * @since 1.0.4 * * @return bool * * @deprecated Method name is confusing since it's not clear from the name the code will be removed. * @using Alias to is_paying__premium_only() */ function is_paying__fs__() { return $this->is_paying__premium_only(); } /** * Check if user in a trial or have feature enabled license. * * All code wrapped in this statement will be only included in the premium code. * * @author Vova Feldman (@svovaf) * @since 1.1.9 * * @return bool */ function can_use_premium_code__premium_only() { return $this->is_premium() && $this->can_use_premium_code(); } #endregion #---------------------------------------------------------------------------------- #region Trial #---------------------------------------------------------------------------------- /** * Check if the user in a trial. * * @since 1.0.3 * * @return bool */ abstract function is_trial(); /** * Check if trial already utilized. * * @since 1.0.9 * * @return bool */ abstract function is_trial_utilized(); #endregion #---------------------------------------------------------------------------------- #region Plans #---------------------------------------------------------------------------------- /** * Check if the user is on the free plan of the product. * * @since 1.0.4 * * @return bool */ abstract function is_free_plan(); /** * @since 1.0.2 * * @param string $plan Plan name. * @param bool $exact If true, looks for exact plan. If false, also check "higher" plans. * * @return bool */ abstract function is_plan( $plan, $exact = false ); /** * Check if plan based on trial. If not in trial mode, should return false. * * @since 1.0.9 * * @param string $plan Plan name. * @param bool $exact If true, looks for exact plan. If false, also check "higher" plans. * * @return bool */ abstract function is_trial_plan( $plan, $exact = false ); /** * Check if plan matches active license' plan or active trial license' plan. * * @since 1.0.9 * * @param string $plan Plan name. * @param bool $exact If true, looks for exact plan. If false, also check "higher" plans. * * @return bool */ function is_plan_or_trial( $plan, $exact = false ) { return $this->is_plan( $plan, $exact ) || $this->is_trial_plan( $plan, $exact ); } /** * Check if plugin has any paid plans. * * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @return bool */ abstract function has_paid_plan(); /** * Check if plugin has any free plan, or is it premium only. * * Note: If no plans configured, assume plugin is free. * * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @return bool */ abstract function has_free_plan(); /** * Check if plugin is premium only (no free plans). * * NOTE: is__premium_only() is very different method, don't get confused. * * @author Vova Feldman (@svovaf) * @since 1.1.9 * * @return bool */ abstract function is_only_premium(); /** * Check if module has a premium code version. * * Serviceware module might be freemium without any * premium code version, where the paid features * are all part of the service. * * @author Vova Feldman (@svovaf) * @since 1.2.1.6 * * @return bool */ abstract function has_premium_version(); /** * Check if module has any release on Freemius, * or all plugin's code is on WordPress.org (Serviceware). * * @return bool */ function has_release_on_freemius() { return ! $this->is_org_repo_compliant() || $this->has_premium_version(); } /** * Checks if it's a freemium plugin. * * @author Vova Feldman (@svovaf) * @since 1.1.9 * * @return bool */ function is_freemium() { return $this->has_paid_plan() && $this->has_free_plan(); } /** * Check if module has only one plan. * * @author Vova Feldman (@svovaf) * @since 1.2.1.7 * * @return bool */ abstract function is_single_plan(); #endregion /** * Check if running payments in sandbox mode. * * @since 1.0.4 * * @return bool */ abstract function is_payments_sandbox(); /** * Check if running test vs. live plugin. * * @since 1.0.5 * * @return bool */ abstract function is_live(); /** * Check if running premium plugin code. * * @since 1.0.5 * * @return bool */ abstract function is_premium(); /** * Get upgrade URL. * * @author Vova Feldman (@svovaf) * @since 1.0.2 * * @param string $period Billing cycle. * * @return string */ abstract function get_upgrade_url( $period = WP_FS__PERIOD_ANNUALLY ); /** * Check if Freemius was first added in a plugin update. * * @author Vova Feldman (@svovaf) * @since 1.1.5 * * @return bool */ function is_plugin_update() { return ! $this->is_plugin_new_install(); } /** * Check if Freemius was part of the plugin when the user installed it first. * * @author Vova Feldman (@svovaf) * @since 1.1.5 * * @return bool */ abstract function is_plugin_new_install(); #---------------------------------------------------------------------------------- #region Marketing #---------------------------------------------------------------------------------- /** * Check if current user purchased any other plugins before. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool */ abstract function has_purchased_before(); /** * Check if current user classified as an agency. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool */ abstract function is_agency(); /** * Check if current user classified as a developer. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool */ abstract function is_developer(); /** * Check if current user classified as a business. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool */ abstract function is_business(); #endregion }PK!v $$7libraries/freemius/includes/class-fs-plugin-updater.phpnu[get_id(); if ( ! isset( self::$_INSTANCES[ $key ] ) ) { self::$_INSTANCES[ $key ] = new self( $freemius ); } return self::$_INSTANCES[ $key ]; } #endregion private function __construct( Freemius $freemius ) { $this->_fs = $freemius; $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $freemius->get_slug() . '_updater', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); $this->filters(); } /** * Initiate required filters. * * @author Vova Feldman (@svovaf) * @since 1.0.4 */ private function filters() { // Override request for plugin information add_filter( 'plugins_api', array( &$this, 'plugins_api_filter' ), 10, 3 ); $this->add_transient_filters(); /** * If user has the premium plugin's code but do NOT have an active license, * encourage him to upgrade by showing that there's a new release, but instead * of showing an update link, show upgrade link to the pricing page. * * @since 1.1.6 * */ // WP 2.9+ add_action( "after_plugin_row_{$this->_fs->get_plugin_basename()}", array( &$this, 'catch_plugin_update_row' ), 9 ); add_action( "after_plugin_row_{$this->_fs->get_plugin_basename()}", array( &$this, 'edit_and_echo_plugin_update_row' ), 11, 2 ); if ( ! $this->_fs->has_any_active_valid_license() ) { add_action( 'admin_head', array( &$this, 'catch_plugin_information_dialog_contents' ) ); } else { add_action( 'admin_footer', array( &$this, '_add_fs_allow_updater_and_dialog_request_param' ) ); } if ( ! WP_FS__IS_PRODUCTION_MODE ) { add_filter( 'http_request_host_is_external', array( $this, 'http_request_host_is_external_filter' ), 10, 3 ); } if ( $this->_fs->is_premium() ) { if ( ! $this->is_correct_folder_name() ) { add_filter( 'upgrader_post_install', array( &$this, '_maybe_update_folder_name' ), 10, 3 ); } add_filter( 'upgrader_pre_install', array( 'FS_Plugin_Updater', '_store_basename_for_source_adjustment' ), 1, 2 ); add_filter( 'upgrader_source_selection', array( 'FS_Plugin_Updater', '_maybe_adjust_source_dir' ), 1, 3 ); if ( ! $this->_fs->has_any_active_valid_license() ) { add_filter( 'wp_prepare_themes_for_js', array( &$this, 'change_theme_update_info_html' ), 10, 1 ); } } } /** * @author Leo Fajardo (@leorw) * @since 2.7.4 */ function _add_fs_allow_updater_and_dialog_request_param() { if ( ! $this->is_plugin_information_dialog_for_plugin() ) { return; } ?> _fs->get_slug() === fs_request_get_raw( 'plugin', false ) ); } /** * @author Leo Fajardo (@leorw) * @since 2.1.4 */ function catch_plugin_information_dialog_contents() { if ( ! $this->is_plugin_information_dialog_for_plugin() ) { return; } add_action( 'admin_footer', array( &$this, 'edit_and_echo_plugin_information_dialog_contents' ), 0, 1 ); ob_start(); } /** * @author Leo Fajardo (@leorw) * @since 2.1.4 * * @param string $hook_suffix */ function edit_and_echo_plugin_information_dialog_contents( $hook_suffix ) { if ( 'plugin-information' !== fs_request_get( 'tab', false ) || $this->_fs->get_slug() !== fs_request_get_raw( 'plugin', false ) ) { return; } $license = $this->_fs->_get_license(); $subscription = ( is_object( $license ) && ! $license->is_lifetime() ) ? $this->_fs->_get_subscription( $license->id ) : null; $contents = ob_get_clean(); $install_or_update_button_id_attribute_pos = strpos( $contents, 'id="plugin_install_from_iframe"' ); if ( false === $install_or_update_button_id_attribute_pos ) { $install_or_update_button_id_attribute_pos = strpos( $contents, 'id="plugin_update_from_iframe"' ); } if ( false !== $install_or_update_button_id_attribute_pos ) { $install_or_update_button_start_pos = strrpos( substr( $contents, 0, $install_or_update_button_id_attribute_pos ), '', $install_or_update_button_id_attribute_pos ) + strlen( '' ) ); /** * The part of the contents without the update button. * * @author Leo Fajardo (@leorw) * @since 2.2.5 */ $modified_contents = substr( $contents, 0, $install_or_update_button_start_pos ); $install_or_update_button = substr( $contents, $install_or_update_button_start_pos, ( $install_or_update_button_end_pos - $install_or_update_button_start_pos ) ); /** * Replace the plugin information dialog's "Install Update Now" button's text and URL. If there's a license, * the text will be "Renew license" and will link to the checkout page with the license's billing cycle * and quota. If there's no license, the text will be "Buy license" and will link to the pricing page. */ $install_or_update_button = preg_replace( '/(\)(.+)(\<\/a>)/is', is_object( $license ) ? sprintf( '$1$4%s$6%s$8', $this->_fs->checkout_url( is_object( $subscription ) ? ( 1 == $subscription->billing_cycle ? WP_FS__PERIOD_MONTHLY : WP_FS__PERIOD_ANNUALLY ) : WP_FS__PERIOD_LIFETIME, false, array( 'licenses' => $license->quota ) ), fs_text_inline( 'Renew license', 'renew-license', $this->_fs->get_slug() ) ) : sprintf( '$1$4%s$6%s$8', $this->_fs->pricing_url(), fs_text_inline( 'Buy license', 'buy-license', $this->_fs->get_slug() ) ), $install_or_update_button ); /** * Append the modified button. * * @author Leo Fajardo (@leorw) * @since 2.2.5 */ $modified_contents .= $install_or_update_button; /** * Append the remaining part of the contents after the update button. * * @author Leo Fajardo (@leorw) * @since 2.2.5 */ $modified_contents .= substr( $contents, $install_or_update_button_end_pos ); $contents = $modified_contents; } echo $contents; } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 */ private function add_transient_filters() { if ( $this->_fs->is_premium() && $this->_fs->is_registered() && ! FS_Permission_Manager::instance( $this->_fs )->is_essentials_tracking_allowed() ) { $this->_logger->log( 'Opted out sites cannot receive automatic software updates.' ); return; } add_filter( 'pre_set_site_transient_update_plugins', array( &$this, 'pre_set_site_transient_update_plugins_filter' ) ); add_filter( 'pre_set_site_transient_update_themes', array( &$this, 'pre_set_site_transient_update_plugins_filter' ) ); } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 */ private function remove_transient_filters() { remove_filter( 'pre_set_site_transient_update_plugins', array( &$this, 'pre_set_site_transient_update_plugins_filter' ) ); remove_filter( 'pre_set_site_transient_update_themes', array( &$this, 'pre_set_site_transient_update_plugins_filter' ) ); } /** * Capture plugin update row by turning output buffering. * * @author Vova Feldman (@svovaf) * @since 1.1.6 */ function catch_plugin_update_row() { ob_start(); } /** * Overrides default update message format with "renew your license" message. * * @author Vova Feldman (@svovaf) * @since 1.1.6 * * @param string $file * @param array $plugin_data */ function edit_and_echo_plugin_update_row( $file, $plugin_data ) { $plugin_update_row = ob_get_clean(); $current = get_site_transient( 'update_plugins' ); if ( ! isset( $current->response[ $file ] ) ) { echo $plugin_update_row; return; } $r = $current->response[ $file ]; $has_beta_update = $this->_fs->has_beta_update(); if ( $this->_fs->has_any_active_valid_license() ) { if ( $has_beta_update ) { /** * Turn the "new version" text into "new Beta version". * * Sample input: * There is a new version of Awesome Plugin available. update now. * Output: * There is a new Beta version of Awesome Plugin available. update now. * * @author Leo Fajardo (@leorw) * @since 2.3.0 */ $plugin_update_row = preg_replace( '/(\)(.+)(\.+\)/is', ( '$1' . sprintf( fs_text_inline( 'There is a %s of %s available.', 'new-version-available', $this->_fs->get_slug() ), $has_beta_update ? fs_text_inline( 'new Beta version', 'new-beta-version', $this->_fs->get_slug() ) : fs_text_inline( 'new version', 'new-version', $this->_fs->get_slug() ), $this->_fs->get_plugin_title() ) . ' ' . '$3' . '$6' ), $plugin_update_row ); } } else { /** * Turn the "new version" text into a link that opens the plugin information dialog when clicked and * make the "View version x details" text link to the checkout page instead of opening the plugin * information dialog when clicked. * * Sample input: * There is a new version of Awesome Plugin available. update now. * Output: * There is a Buy a license now to access version x.y.z security & feature updates, and support. * OR * There is a Buy a license now to access version x.y.z security & feature updates, and support. * * @author Leo Fajardo (@leorw) */ $plugin_update_row = preg_replace( '/(\)(.+)(\.+\)/is', ( '$1' . sprintf( fs_text_inline( 'There is a %s of %s available.', 'new-version-available', $this->_fs->get_slug() ), sprintf( '%s', '$5', $has_beta_update ? fs_text_inline( 'new Beta version', 'new-beta-version', $this->_fs->get_slug() ) : fs_text_inline( 'new version', 'new-version', $this->_fs->get_slug() ) ), $this->_fs->get_plugin_title() ) . ' ' . $this->_fs->version_upgrade_checkout_link( $r->new_version ) . '$6' ), $plugin_update_row ); } if ( $this->_fs->is_plugin() && isset( $r->upgrade_notice ) && strlen( trim( $r->upgrade_notice ) ) > 0 ) { $slug = $this->_fs->get_slug(); $upgrade_notice_html = sprintf( '

    %3$s %4$s

    ', $slug, $this->_fs->get_module_type(), fs_text_inline( 'Important Upgrade Notice:', 'upgrade_notice', $slug ), esc_html( $r->upgrade_notice ) ); $plugin_update_row = str_replace( '', '' . $upgrade_notice_html, $plugin_update_row ); } echo $plugin_update_row; } /** * @author Leo Fajardo (@leorw) * @since 2.0.2 * * @param array $prepared_themes * * @return array */ function change_theme_update_info_html( $prepared_themes ) { $theme_basename = $this->_fs->get_plugin_basename(); if ( ! isset( $prepared_themes[ $theme_basename ] ) ) { return $prepared_themes; } $themes_update = get_site_transient( 'update_themes' ); if ( ! isset( $themes_update->response[ $theme_basename ] ) || empty( $themes_update->response[ $theme_basename ]['package'] ) ) { return $prepared_themes; } $prepared_themes[ $theme_basename ]['update'] = preg_replace( '/(\)(.+)(\)/is', '$1 $2 ' . $this->_fs->version_upgrade_checkout_link( $themes_update->response[ $theme_basename ]['new_version'] ) . '$4', $prepared_themes[ $theme_basename ]['update'] ); // Set to false to prevent the "Update now" link for the context theme from being shown on the "Themes" page. $prepared_themes[ $theme_basename ]['hasPackage'] = false; return $prepared_themes; } /** * Since WP version 3.6, a new security feature was added that denies access to repository with a local ip. * During development mode we want to be able updating plugin versions via our localhost repository. This * filter white-list all domains including "api.freemius". * * @link http://www.emanueletessore.com/wordpress-download-failed-valid-url-provided/ * * @author Vova Feldman (@svovaf) * @since 1.0.4 * * @param bool $allow * @param string $host * @param string $url * * @return bool */ function http_request_host_is_external_filter( $allow, $host, $url ) { return ( false !== strpos( $host, 'freemius' ) ) ? true : $allow; } /** * Check for Updates at the defined API endpoint and modify the update array. * * This function dives into the update api just when WordPress creates its update array, * then adds a custom API call and injects the custom plugin data retrieved from the API. * It is reassembled from parts of the native WordPress plugin update code. * See wp-includes/update.php line 121 for the original wp_update_plugins() function. * * @author Vova Feldman (@svovaf) * @since 1.0.4 * * @uses FS_Api * * @param object $transient_data Update array build by WordPress. * * @return object Modified update array with custom plugin data. */ function pre_set_site_transient_update_plugins_filter( $transient_data ) { $this->_logger->entrance(); /** * "plugins" or "themes". * * @author Leo Fajardo (@leorw) * @since 1.2.2 */ $module_type = $this->_fs->get_module_type() . 's'; /** * Ensure that we don't mix plugins update info with themes update info. * * @author Leo Fajardo (@leorw) * @since 1.2.2 */ if ( "pre_set_site_transient_update_{$module_type}" !== current_filter() ) { return $transient_data; } if ( empty( $transient_data ) || defined( 'WP_FS__UNINSTALL_MODE' ) ) { return $transient_data; } global $wp_current_filter; if ( ! empty( $wp_current_filter ) && in_array( 'upgrader_process_complete', $wp_current_filter ) ) { return $transient_data; } if ( ! isset( $this->_update_details ) ) { // Get plugin's newest update. $new_version = $this->_fs->get_update( false, fs_request_get_bool( 'force-check' ), FS_Plugin_Updater::UPDATES_CHECK_CACHE_EXPIRATION, $this->_fs->get_plugin_version() ); $this->_update_details = false; if ( is_object( $new_version ) && $this->is_new_version_premium( $new_version ) ) { $this->_logger->log( 'Found newer plugin version ' . $new_version->version ); /** * Cache plugin details locally since set_site_transient( 'update_plugins' ) * called multiple times and the non wp.org plugins are filtered after the * call to .org. * * @since 1.1.8.1 */ $this->_update_details = $this->get_update_details( $new_version ); } } // Alias. $basename = $this->_fs->premium_plugin_basename(); if ( is_object( $this->_update_details ) ) { if ( isset( $transient_data->no_update ) ) { unset( $transient_data->no_update[ $basename ] ); } if ( ! isset( $transient_data->response ) ) { $transient_data->response = array(); } // Add plugin to transient data. $transient_data->response[ $basename ] = $this->_fs->is_plugin() ? $this->_update_details : (array) $this->_update_details; } else { if ( isset( $transient_data->response ) ) { /** * Ensure that there's no update data for the plugin to prevent upgrading the premium version to the latest free version. * * @author Leo Fajardo (@leorw) * @since 2.3.0 */ unset( $transient_data->response[ $basename ] ); } if ( ! isset( $transient_data->no_update ) ) { $transient_data->no_update = array(); } /** * Add product to no_update transient data to properly integrate with WP 5.5 auto-updates UI. * * @since 2.4.1 * @link https://make.wordpress.org/core/2020/07/30/recommended-usage-of-the-updates-api-to-support-the-auto-updates-ui-for-plugins-and-themes-in-wordpress-5-5/ */ $transient_data->no_update[ $basename ] = $this->_fs->is_plugin() ? (object) array( 'id' => $basename, 'slug' => $this->_fs->get_slug(), 'plugin' => $basename, 'new_version' => $this->_fs->get_plugin_version(), 'url' => '', 'package' => '', 'icons' => array(), 'banners' => array(), 'banners_rtl' => array(), 'tested' => '', 'requires_php' => '', 'compatibility' => new stdClass(), ) : array( 'theme' => $basename, 'new_version' => $this->_fs->get_plugin_version(), 'url' => '', 'package' => '', 'requires' => '', 'requires_php' => '', ); } $slug = $this->_fs->get_slug(); if ( $this->can_fetch_data_from_wp_org() ) { if ( ! isset( $this->_translation_updates ) ) { $this->_translation_updates = array(); $translation_updates = $this->fetch_wp_org_module_translation_updates( $module_type, $slug ); if ( ! empty( $translation_updates ) ) { $this->_translation_updates = $translation_updates; } } if ( ! empty( $this->_translation_updates ) ) { $all_translation_updates = ( isset( $transient_data->translations ) && is_array( $transient_data->translations ) ) ? $transient_data->translations : array(); $current_plugin_translation_updates_map = array(); foreach ( $all_translation_updates as $key => $translation_update ) { if ( $module_type === ( $translation_update['type'] . 's' ) && $slug === $translation_update['slug'] ) { $current_plugin_translation_updates_map[ $translation_update['language'] ] = $translation_update; unset( $all_translation_updates[ $key ] ); } } foreach ( $this->_translation_updates as $translation_update ) { $lang = $translation_update['language']; if ( ! isset( $current_plugin_translation_updates_map[ $lang ] ) || version_compare( $translation_update['version'], $current_plugin_translation_updates_map[ $lang ]['version'], '>' ) ) { $current_plugin_translation_updates_map[ $lang ] = $translation_update; } } $transient_data->translations = array_merge( $all_translation_updates, array_values( $current_plugin_translation_updates_map ) ); } } return $transient_data; } /** * Get module's required data for the updates' mechanism. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param \FS_Plugin_Tag $new_version * * @return object */ function get_update_details( FS_Plugin_Tag $new_version ) { $update = new stdClass(); $update->slug = $this->_fs->get_slug(); $update->new_version = $new_version->version; $update->url = WP_FS__ADDRESS; $update->package = $new_version->url; $update->tested = self::get_tested_wp_version( $new_version->tested_up_to_version ); $update->requires = $new_version->requires_platform_version; $update->requires_php = $new_version->requires_programming_language_version; $icon = $this->_fs->get_local_icon_url(); if ( ! empty( $icon ) ) { $update->icons = array( // '1x' => $icon, // '2x' => $icon, 'default' => $icon, ); } if ( $this->_fs->is_premium() ) { $latest_tag = $this->_fs->_fetch_latest_version( $this->_fs->get_id(), false ); if ( isset( $latest_tag->readme ) && isset( $latest_tag->readme->upgrade_notice ) && ! empty( $latest_tag->readme->upgrade_notice ) ) { $update->upgrade_notice = $latest_tag->readme->upgrade_notice; } } $update->{$this->_fs->get_module_type()} = $this->_fs->get_plugin_basename(); return $update; } /** * @author Leo Fajardo (@leorw) * @since 2.3.0 * * @param FS_Plugin_Tag $new_version * * @return bool */ private function is_new_version_premium( FS_Plugin_Tag $new_version ) { $params = fs_parse_url_params( $new_version->url ); return ( isset( $params['is_premium'] ) && 'true' == $params['is_premium'] ); } /** * Update the updates transient with the module's update information. * * This method is required for multisite environment. * If a module is site activated (not network) and not on the main site, * the module will NOT be executed on the network level, therefore, the * custom updates logic will not be executed as well, so unless we force * the injection of the update into the updates transient, premium updates * will not work. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param \FS_Plugin_Tag $new_version */ function set_update_data( FS_Plugin_Tag $new_version ) { $this->_logger->entrance(); if ( ! $this->is_new_version_premium( $new_version ) ) { return; } $transient_key = "update_{$this->_fs->get_module_type()}s"; $transient_data = get_site_transient( $transient_key ); $transient_data = is_object( $transient_data ) ? $transient_data : new stdClass(); // Alias. $basename = $this->_fs->get_plugin_basename(); $is_plugin = $this->_fs->is_plugin(); if ( ! isset( $transient_data->response ) || ! is_array( $transient_data->response ) ) { $transient_data->response = array(); } else if ( ! empty( $transient_data->response[ $basename ] ) ) { $version = $is_plugin ? ( ! empty( $transient_data->response[ $basename ]->new_version ) ? $transient_data->response[ $basename ]->new_version : null ) : ( ! empty( $transient_data->response[ $basename ]['new_version'] ) ? $transient_data->response[ $basename ]['new_version'] : null ); if ( $version == $new_version->version ) { // The update data is already set. return; } } // Remove the added filters. $this->remove_transient_filters(); $this->_update_details = $this->get_update_details( $new_version ); // Set update data in transient. $transient_data->response[ $basename ] = $is_plugin ? $this->_update_details : (array) $this->_update_details; if ( ! isset( $transient_data->checked ) || ! is_array( $transient_data->checked ) ) { $transient_data->checked = array(); } // Flag the module as if it was already checked. $transient_data->checked[ $basename ] = $this->_fs->get_plugin_version(); $transient_data->last_checked = time(); set_site_transient( $transient_key, $transient_data ); $this->add_transient_filters(); } /** * @author Leo Fajardo (@leorw) * @since 2.0.2 */ function delete_update_data() { $this->_logger->entrance(); $transient_key = "update_{$this->_fs->get_module_type()}s"; $transient_data = get_site_transient( $transient_key ); // Alias $basename = $this->_fs->get_plugin_basename(); if ( ! is_object( $transient_data ) || ! isset( $transient_data->response ) || ! is_array( $transient_data->response ) || empty( $transient_data->response[ $basename ] ) ) { return; } unset( $transient_data->response[ $basename ] ); // Remove the added filters. $this->remove_transient_filters(); set_site_transient( $transient_key, $transient_data ); $this->add_transient_filters(); } /** * Try to fetch plugin's info from .org repository. * * @author Vova Feldman (@svovaf) * @since 1.0.5 * * @param string $action * @param object $args * * @return bool|mixed */ static function _fetch_plugin_info_from_repository( $action, $args ) { $url = $http_url = 'http://api.wordpress.org/plugins/info/1.2/'; $url = add_query_arg( array( 'action' => $action, 'request' => $args, ), $url ); if ( wp_http_supports( array( 'ssl' ) ) ) { $url = set_url_scheme( $url, 'https' ); } // The new endpoint version serves only GET requests. $request = wp_remote_get( $url, array( 'timeout' => 15 ) ); if ( is_wp_error( $request ) ) { return false; } $res = json_decode( wp_remote_retrieve_body( $request ), true ); if ( is_array( $res ) ) { // Object casting is required in order to match the info/1.0 format. We are not decoding directly into an object as we need some fields to remain an array (e.g., $res->sections). $res = (object) $res; } if ( ! is_object( $res ) || isset( $res->error ) ) { return false; } return $res; } /** * Returns true if the product can fetch data from WordPress.org. * * @author Leo Fajardo (@leorw) * @since 2.7.4 */ private function can_fetch_data_from_wp_org() { return ( $this->_fs->is_org_repo_compliant() && $this->_fs->is_freemium() ); } /** * Fetches module translation updates from wordpress.org. * * @author Leo Fajardo (@leorw) * @since 2.1.2 * * @param string $module_type * @param string $slug * * @return array|null */ private function fetch_wp_org_module_translation_updates( $module_type, $slug ) { $plugin_data = $this->_fs->get_plugin_data(); $locales = array_values( get_available_languages() ); $locales = apply_filters( "{$module_type}_update_check_locales", $locales ); $locales = array_unique( $locales ); $plugin_basename = $this->_fs->get_plugin_basename(); if ( 'themes' === $module_type ) { $plugin_basename = $slug; } global $wp_version; $request_args = array( 'timeout' => 15, 'body' => array( "{$module_type}" => json_encode( array( "{$module_type}" => array( $plugin_basename => array( 'Name' => trim( str_replace( $this->_fs->get_plugin()->premium_suffix, '', $plugin_data['Name'] ) ), 'Author' => $plugin_data['Author'], ) ) ) ), 'translations' => json_encode( $this->get_installed_translations( $module_type, $slug ) ), 'locale' => json_encode( $locales ) ), 'user-agent' => ( 'WordPress/' . $wp_version . '; ' . home_url( '/' ) ) ); $url = "http://api.wordpress.org/{$module_type}/update-check/1.1/"; if ( $ssl = wp_http_supports( array( 'ssl' ) ) ) { $url = set_url_scheme( $url, 'https' ); } $raw_response = Freemius::safe_remote_post( $url, $request_args, WP_FS__TIME_24_HOURS_IN_SEC, WP_FS__TIME_12_HOURS_IN_SEC, false ); if ( is_wp_error( $raw_response ) ) { return null; } $response = json_decode( wp_remote_retrieve_body( $raw_response ), true ); if ( ! is_array( $response ) ) { return null; } if ( ! isset( $response['translations'] ) || empty( $response['translations'] ) ) { return null; } return $response['translations']; } /** * @author Leo Fajardo (@leorw) * @since 2.1.2 * * @param string $module_type * @param string $slug * * @return array */ private function get_installed_translations( $module_type, $slug ) { if ( function_exists( 'wp_get_installed_translations' ) ) { return wp_get_installed_translations( $module_type ); } $dir = "/{$module_type}"; if ( ! is_dir( WP_LANG_DIR . $dir ) ) return array(); $files = scandir( WP_LANG_DIR . $dir ); if ( ! $files ) return array(); $language_data = array(); foreach ( $files as $file ) { if ( 0 !== strpos( $file, $slug ) ) { continue; } if ( '.' === $file[0] || is_dir( WP_LANG_DIR . "{$dir}/{$file}" ) ) { continue; } if ( substr( $file, -3 ) !== '.po' ) { continue; } if ( ! preg_match( '/(?:(.+)-)?([a-z]{2,3}(?:_[A-Z]{2})?(?:_[a-z0-9]+)?).po/', $file, $match ) ) { continue; } if ( ! in_array( substr( $file, 0, -3 ) . '.mo', $files ) ) { continue; } list( , $textdomain, $language ) = $match; if ( '' === $textdomain ) { $textdomain = 'default'; } $language_data[ $textdomain ][ $language ] = wp_get_pomo_file_data( WP_LANG_DIR . "{$dir}/{$file}" ); } return $language_data; } /** * Updates information on the "View version x.x details" page with custom data. * * @author Vova Feldman (@svovaf) * @since 1.0.4 * * @uses FS_Api * * @param object $data * @param string $action * @param mixed $args * * @return object */ function plugins_api_filter( $data, $action = '', $args = null ) { $this->_logger->entrance(); if ( ( 'plugin_information' !== $action ) || ! isset( $args->slug ) ) { return $data; } $addon = false; $is_addon = false; $addon_version = false; if ( $this->_fs->get_slug() !== $args->slug ) { $addon = $this->_fs->get_addon_by_slug( $args->slug ); if ( ! is_object( $addon ) ) { return $data; } if ( $this->_fs->is_addon_activated( $addon->id ) ) { $addon_version = $this->_fs->get_addon_instance( $addon->id )->get_plugin_version(); } else if ( $this->_fs->is_addon_installed( $addon->id ) ) { $addon_plugin_data = get_plugin_data( ( WP_PLUGIN_DIR . '/' . $this->_fs->get_addon_basename( $addon->id ) ), false, false ); if ( ! empty( $addon_plugin_data ) ) { $addon_version = $addon_plugin_data['Version']; } } $is_addon = true; } $plugin_in_repo = false; if ( ! $is_addon && $this->can_fetch_data_from_wp_org() ) { // Try to fetch info from .org repository. $data = self::_fetch_plugin_info_from_repository( $action, $args ); $plugin_in_repo = ( false !== $data ); } if ( ! $plugin_in_repo ) { $data = $args; // Fetch as much as possible info from local files. $plugin_local_data = $this->_fs->get_plugin_data(); $data->name = $plugin_local_data['Name']; $data->author = $plugin_local_data['Author']; $data->sections = array( 'description' => 'Upgrade ' . $plugin_local_data['Name'] . ' to latest.', ); // @todo Store extra plugin info on Freemius or parse readme.txt markup. /*$info = $this->_fs->get_api_site_scope()->call('/information.json'); if ( !isset($info->error) ) { $data = $info; }*/ } $plugin_version = $is_addon ? $addon_version : $this->_fs->get_plugin_version(); // Get plugin's newest update. $new_version = $this->get_latest_download_details( $is_addon ? $addon->id : false, $plugin_version ); if ( ! is_object( $new_version ) || empty( $new_version->version ) ) { $data->version = $plugin_version; } else { if ( $is_addon ) { $data->name = $addon->title . ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' ); $data->slug = $addon->slug; $data->url = WP_FS__ADDRESS; $data->package = $new_version->url; } if ( ! $plugin_in_repo ) { $data->last_updated = ! is_null( $new_version->updated ) ? $new_version->updated : $new_version->created; $data->requires = $new_version->requires_platform_version; $data->requires_php = $new_version->requires_programming_language_version; $data->tested = $new_version->tested_up_to_version; } $data->version = $new_version->version; $data->download_link = $new_version->url; if ( isset( $new_version->readme ) && is_object( $new_version->readme ) ) { $new_version_readme_data = $new_version->readme; if ( isset( $new_version_readme_data->sections ) ) { $new_version_readme_data->sections = (array) $new_version_readme_data->sections; } else { $new_version_readme_data->sections = array(); } if ( isset( $data->sections ) ) { if ( isset( $data->sections['screenshots'] ) ) { $new_version_readme_data->sections['screenshots'] = $data->sections['screenshots']; } if ( isset( $data->sections['reviews'] ) ) { $new_version_readme_data->sections['reviews'] = $data->sections['reviews']; } } if ( isset( $new_version_readme_data->banners ) ) { $new_version_readme_data->banners = (array) $new_version_readme_data->banners; } else if ( isset( $data->banners ) ) { $new_version_readme_data->banners = $data->banners; } $wp_org_sections = array( 'author', 'author_profile', 'rating', 'ratings', 'num_ratings', 'support_threads', 'support_threads_resolved', 'active_installs', 'added', 'homepage' ); foreach ( $wp_org_sections as $wp_org_section ) { if ( isset( $data->{$wp_org_section} ) ) { $new_version_readme_data->{$wp_org_section} = $data->{$wp_org_section}; } } $data = $new_version_readme_data; } } if ( ! empty( $data->tested ) ) { $data->tested = self::get_tested_wp_version( $data->tested ); } return $data; } /** * @since 2.5.3 If the current WordPress version is a patch of the tested version (e.g., 6.1.2 is a patch of 6.1), then override the tested version with the patch so developers won't need to release a new version just to bump the latest supported WP version. * * @param string|null $tested_up_to * * @return string|null */ private static function get_tested_wp_version( $tested_up_to ) { $current_wp_version = get_bloginfo( 'version' ); return ( ! empty($tested_up_to) && fs_starts_with( $current_wp_version, $tested_up_to . '.' ) ) ? $current_wp_version : $tested_up_to; } /** * @author Vova Feldman (@svovaf) * @since 1.2.1.7 * * @param number|bool $addon_id * @param bool|string $newer_than Since 2.2.1 * @param bool|string $fetch_readme Since 2.2.1 * * @return object */ private function get_latest_download_details( $addon_id = false, $newer_than = false, $fetch_readme = true ) { return $this->_fs->_fetch_latest_version( $addon_id, true, FS_Plugin_Updater::UPDATES_CHECK_CACHE_EXPIRATION, $newer_than, $fetch_readme ); } /** * Checks if a given basename has a matching folder name * with the current context plugin. * * @author Vova Feldman (@svovaf) * @since 1.2.1.6 * * @return bool */ private function is_correct_folder_name() { return ( $this->_fs->get_target_folder_name() == trim( dirname( $this->_fs->get_plugin_basename() ), '/\\' ) ); } /** * This is a special after upgrade handler for migrating modules * that didn't use the '-premium' suffix folder structure before * the migration. * * @author Vova Feldman (@svovaf) * @since 1.2.1.6 * * @param bool $response Install response. * @param array $hook_extra Extra arguments passed to hooked filters. * @param array $result Installation result data. * * @return bool */ function _maybe_update_folder_name( $response, $hook_extra, $result ) { $basename = $this->_fs->get_plugin_basename(); if ( true !== $response || empty( $hook_extra ) || empty( $hook_extra['plugin'] ) || $basename !== $hook_extra['plugin'] ) { return $response; } $active_plugins_basenames = get_option( 'active_plugins' ); foreach ( $active_plugins_basenames as $key => $active_plugin_basename ) { if ( $basename === $active_plugin_basename ) { // Get filename including extension. $filename = basename( $basename ); $new_basename = plugin_basename( trailingslashit( $this->_fs->is_premium() ? $this->_fs->get_premium_slug() : $this->_fs->get_slug() ) . $filename ); // Verify that the expected correct path exists. if ( file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $new_basename ) ) ) { // Override active plugin name. $active_plugins_basenames[ $key ] = $new_basename; update_option( 'active_plugins', $active_plugins_basenames ); } break; } } return $response; } #---------------------------------------------------------------------------------- #region Auto Activation #---------------------------------------------------------------------------------- /** * Installs and active a plugin when explicitly requested that from a 3rd party service. * * This logic was inspired by the TGMPA GPL licensed library by Thomas Griffin. * * @link http://tgmpluginactivation.com/ * * @author Vova Feldman * @since 1.2.1.7 * * @link https://make.wordpress.org/plugins/2017/03/16/clarification-of-guideline-8-executable-code-and-installs/ * * @uses WP_Filesystem * @uses WP_Error * @uses WP_Upgrader * @uses Plugin_Upgrader * @uses Plugin_Installer_Skin * @uses Plugin_Upgrader_Skin * * @param number|bool $plugin_id * * @return array */ function install_and_activate_plugin( $plugin_id = false ) { if ( ! empty( $plugin_id ) && ! FS_Plugin::is_valid_id( $plugin_id ) ) { // Invalid plugin ID. return array( 'message' => $this->_fs->get_text_inline( 'Invalid module ID.', 'auto-install-error-invalid-id' ), 'code' => 'invalid_module_id', ); } $is_addon = false; if ( FS_Plugin::is_valid_id( $plugin_id ) && $plugin_id != $this->_fs->get_id() ) { $addon = $this->_fs->get_addon( $plugin_id ); if ( ! is_object( $addon ) ) { // Invalid add-on ID. return array( 'message' => $this->_fs->get_text_inline( 'Invalid module ID.', 'auto-install-error-invalid-id' ), 'code' => 'invalid_module_id', ); } $slug = $addon->slug; $premium_slug = $addon->premium_slug; $title = $addon->title . ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' ); $is_addon = true; } else { $slug = $this->_fs->get_slug(); $premium_slug = $this->_fs->get_premium_slug(); $title = $this->_fs->get_plugin_title() . ( $this->_fs->is_addon() ? ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' ) : '' ); } if ( $this->is_premium_plugin_active( $plugin_id ) ) { // Premium version already activated. return array( 'message' => $is_addon ? $this->_fs->get_text_inline( 'Premium add-on version already installed.', 'auto-install-error-premium-addon-activated' ) : $this->_fs->get_text_inline( 'Premium version already active.', 'auto-install-error-premium-activated' ), 'code' => 'premium_installed', ); } $latest_version = $this->get_latest_download_details( $plugin_id, false, false ); $target_folder = $premium_slug; // Prep variables for Plugin_Installer_Skin class. $extra = array(); $extra['slug'] = $target_folder; $source = $latest_version->url; $api = null; $install_url = add_query_arg( array( 'action' => 'install-plugin', 'plugin' => urlencode( $slug ), ), 'update.php' ); if ( ! class_exists( 'Plugin_Upgrader', false ) ) { // Include required resources for the installation. require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; } $skin_args = array( 'type' => 'web', 'title' => sprintf( $this->_fs->get_text_inline( 'Installing plugin: %s', 'installing-plugin-x' ), $title ), 'url' => esc_url_raw( $install_url ), 'nonce' => 'install-plugin_' . $slug, 'plugin' => '', 'api' => $api, 'extra' => $extra, ); // $skin = new Automatic_Upgrader_Skin( $skin_args ); // $skin = new Plugin_Installer_Skin( $skin_args ); $skin = new WP_Ajax_Upgrader_Skin( $skin_args ); // Create a new instance of Plugin_Upgrader. $upgrader = new Plugin_Upgrader( $skin ); // Perform the action and install the plugin from the $source urldecode(). add_filter( 'upgrader_source_selection', array( 'FS_Plugin_Updater', '_maybe_adjust_source_dir' ), 1, 3 ); $install_result = $upgrader->install( $source ); remove_filter( 'upgrader_source_selection', array( 'FS_Plugin_Updater', '_maybe_adjust_source_dir' ), 1 ); if ( is_wp_error( $install_result ) ) { return array( 'message' => $install_result->get_error_message(), 'code' => $install_result->get_error_code(), ); } elseif ( is_wp_error( $skin->result ) ) { return array( 'message' => $skin->result->get_error_message(), 'code' => $skin->result->get_error_code(), ); } elseif ( $skin->get_errors()->get_error_code() ) { return array( 'message' => $skin->get_error_messages(), 'code' => 'unknown', ); } elseif ( is_null( $install_result ) ) { global $wp_filesystem; $error_code = 'unable_to_connect_to_filesystem'; $error_message = $this->_fs->get_text_inline( 'Unable to connect to the filesystem. Please confirm your credentials.' ); // Pass through the error from WP_Filesystem if one was raised. if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) { $error_message = $wp_filesystem->errors->get_error_message(); } return array( 'message' => $error_message, 'code' => $error_code, ); } // Grab the full path to the main plugin's file. $plugin_activate = $upgrader->plugin_info(); // Try to activate the plugin. $activation_result = $this->try_activate_plugin( $plugin_activate ); if ( is_wp_error( $activation_result ) ) { return array( 'message' => $activation_result->get_error_message(), 'code' => $activation_result->get_error_code(), ); } return $skin->get_upgrade_messages(); } /** * Tries to activate a plugin. If fails, returns the error. * * @author Vova Feldman * @since 1.2.1.7 * * @param string $file_path Path within wp-plugins/ to main plugin file. * This determines the styling of the output messages. * * @return bool|WP_Error */ protected function try_activate_plugin( $file_path ) { $activate = activate_plugin( $file_path, '', $this->_fs->is_network_active() ); return is_wp_error( $activate ) ? $activate : true; } /** * Check if a premium module version is already active. * * @author Vova Feldman * @since 1.2.1.7 * * @param number|bool $plugin_id * * @return bool */ private function is_premium_plugin_active( $plugin_id = false ) { if ( $plugin_id != $this->_fs->get_id() ) { return $this->_fs->is_addon_activated( $plugin_id, true ); } return is_plugin_active( $this->_fs->premium_plugin_basename() ); } /** * Store the basename since it's not always available in the `_maybe_adjust_source_dir` method below. * * @author Leo Fajardo (@leorw) * @since 2.2.1 * * @param bool|WP_Error $response Response. * @param array $hook_extra Extra arguments passed to hooked filters. * * @return bool|WP_Error */ static function _store_basename_for_source_adjustment( $response, $hook_extra ) { if ( isset( $hook_extra['plugin'] ) ) { self::$_upgrade_basename = $hook_extra['plugin']; } else if ( isset( $hook_extra['theme'] ) ) { self::$_upgrade_basename = $hook_extra['theme']; } else { self::$_upgrade_basename = null; } return $response; } /** * Adjust the plugin directory name if necessary. * Assumes plugin has a folder (not a single file plugin). * * The final destination directory of a plugin is based on the subdirectory name found in the * (un)zipped source. In some cases this subdirectory name is not the same as the expected * slug and the plugin will not be recognized as installed. This is fixed by adjusting * the temporary unzipped source subdirectory name to the expected plugin slug. * * @author Vova Feldman * @since 1.2.1.7 * @since 2.2.1 The method was converted to static since when the admin update bulk products via the Updates section, the logic applies the `upgrader_source_selection` filter for every product that is being updated. * * @param string $source Path to upgrade/zip-file-name.tmp/subdirectory/. * @param string $remote_source Path to upgrade/zip-file-name.tmp. * @param \WP_Upgrader $upgrader Instance of the upgrader which installs the plugin. * * @return string|WP_Error */ static function _maybe_adjust_source_dir( $source, $remote_source, $upgrader ) { if ( ! is_object( $GLOBALS['wp_filesystem'] ) ) { return $source; } $basename = self::$_upgrade_basename; $is_theme = false; // Figure out what the slug is supposed to be. if ( isset( $upgrader->skin->options['extra'] ) ) { // Set by the auto-install logic. $desired_slug = $upgrader->skin->options['extra']['slug']; } else if ( ! empty( $basename ) ) { /** * If it doesn't end with ".php", it's a theme. * * @author Leo Fajardo (@leorw) * @since 2.2.1 */ $is_theme = ( ! fs_ends_with( $basename, '.php' ) ); $desired_slug = ( ! $is_theme ) ? dirname( $basename ) : // Theme slug $basename; } else { // Can't figure out the desired slug, stop the execution. return $source; } if ( is_multisite() ) { /** * If we are running in a multisite environment and the product is not network activated, * the instance will not exist anyway. Therefore, try to update the source if necessary * regardless if the Freemius instance of the product exists or not. * * @author Vova Feldman */ } else if ( ! empty( $basename ) ) { $fs = Freemius::get_instance_by_file( $basename, $is_theme ? WP_FS__MODULE_TYPE_THEME : WP_FS__MODULE_TYPE_PLUGIN ); if ( ! is_object( $fs ) ) { /** * If the Freemius instance does not exist on a non-multisite network environment, it means that: * 1. The product is not powered by Freemius; OR * 2. The product is not activated, therefore, we don't mind if after the update the folder name will change. * * @author Leo Fajardo (@leorw) * @since 2.2.1 */ return $source; } } $subdir_name = untrailingslashit( str_replace( trailingslashit( $remote_source ), '', $source ) ); if ( ! empty( $subdir_name ) && $subdir_name !== $desired_slug ) { $from_path = untrailingslashit( $source ); $to_path = trailingslashit( $remote_source ) . $desired_slug; if ( true === $GLOBALS['wp_filesystem']->move( $from_path, $to_path ) ) { return trailingslashit( $to_path ); } return new WP_Error( 'rename_failed', fs_text_inline( 'The remote plugin package does not contain a folder with the desired slug and renaming did not work.', 'module-package-rename-failure' ), array( 'found' => $subdir_name, 'expected' => $desired_slug ) ); } return $source; } #endregion } PK!2rAlibraries/freemius/includes/managers/class-fs-license-manager.phpnu[get_slug() ); // // if ( ! isset( self::$_instances[ $slug ] ) ) { // self::$_instances[ $slug ] = new FS_License_Manager( $slug, $fs ); // } // // return self::$_instances[ $slug ]; // } // //// private function __construct($slug) { //// parent::__construct($slug); //// } // // function entry_id() { // return 'licenses'; // } // // function sync( $id ) { // // } // // /** // * @author Vova Feldman (@svovaf) // * @since 1.0.5 // * @uses FS_Api // * // * @param number|bool $plugin_id // * // * @return FS_Plugin_License[]|stdClass Licenses or API error. // */ // function api_get_user_plugin_licenses( $plugin_id = false ) { // $api = $this->_fs->get_api_user_scope(); // // if ( ! is_numeric( $plugin_id ) ) { // $plugin_id = $this->_fs->get_id(); // } // // $result = $api->call( "/plugins/{$plugin_id}/licenses.json" ); // // if ( ! isset( $result->error ) ) { // for ( $i = 0, $len = count( $result->licenses ); $i < $len; $i ++ ) { // $result->licenses[ $i ] = new FS_Plugin_License( $result->licenses[ $i ] ); // } // // $result = $result->licenses; // } // // return $result; // } // // function api_get_many() { // // } // // function api_activate( $id ) { // // } // // function api_deactivate( $id ) { // // } /** * @param FS_Plugin_License[] $licenses * * @return bool */ static function has_premium_license( $licenses ) { if ( is_array( $licenses ) ) { foreach ( $licenses as $license ) { /** * @var FS_Plugin_License $license */ if ( ! $license->is_utilized() && $license->is_features_enabled() ) { return true; } } } return false; } }PK!4?libraries/freemius/includes/managers/class-fs-cache-manager.phpnu[_logger = FS_Logger::get_logger( WP_FS__SLUG . '_cach_mngr_' . $id, WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); $this->_logger->entrance(); $this->_logger->log( 'id = ' . $id ); $this->_options = FS_Option_Manager::get_manager( $id, true, true, false ); } /** * @author Vova Feldman (@svovaf) * @since 1.1.6 * * @param $id * * @return FS_Cache_Manager */ static function get_manager( $id ) { $id = strtolower( $id ); if ( ! isset( self::$_MANAGERS[ $id ] ) ) { self::$_MANAGERS[ $id ] = new FS_Cache_Manager( $id ); } return self::$_MANAGERS[ $id ]; } /** * @author Vova Feldman (@svovaf) * @since 1.1.6 * * @return bool */ function is_empty() { $this->_logger->entrance(); return $this->_options->is_empty(); } /** * @author Vova Feldman (@svovaf) * @since 1.1.6 */ function clear() { $this->_logger->entrance(); $this->_options->clear( true ); } /** * Delete cache manager from DB. * * @author Vova Feldman (@svovaf) * @since 1.0.9 */ function delete() { $this->_options->delete(); } /** * Check if there's a cached item. * * @author Vova Feldman (@svovaf) * @since 1.1.6 * * @param string $key * * @return bool */ function has( $key ) { $cache_entry = $this->_options->get_option( $key, false ); return ( is_object( $cache_entry ) && isset( $cache_entry->timestamp ) && is_numeric( $cache_entry->timestamp ) ); } /** * Check if there's a valid cached item. * * @author Vova Feldman (@svovaf) * @since 1.1.6 * * @param string $key * @param null|int $expiration Since 1.2.2.7 * * @return bool */ function has_valid( $key, $expiration = null ) { $cache_entry = $this->_options->get_option( $key, false ); $is_valid = ( is_object( $cache_entry ) && isset( $cache_entry->timestamp ) && is_numeric( $cache_entry->timestamp ) && $cache_entry->timestamp > WP_FS__SCRIPT_START_TIME ); if ( $is_valid && is_numeric( $expiration ) && isset( $cache_entry->created ) && is_numeric( $cache_entry->created ) && $cache_entry->created + $expiration < WP_FS__SCRIPT_START_TIME ) { /** * Even if the cache is still valid, since we are checking for validity * with an explicit expiration period, if the period has past, return * `false` as if the cache is invalid. * * @since 1.2.2.7 */ $is_valid = false; } return $is_valid; } /** * @author Vova Feldman (@svovaf) * @since 1.1.6 * * @param string $key * @param mixed $default * * @return mixed */ function get( $key, $default = null ) { $this->_logger->entrance( 'key = ' . $key ); $cache_entry = $this->_options->get_option( $key, false ); if ( is_object( $cache_entry ) && isset( $cache_entry->timestamp ) && is_numeric( $cache_entry->timestamp ) ) { return $cache_entry->result; } return is_object( $default ) ? clone $default : $default; } /** * @author Vova Feldman (@svovaf) * @since 1.1.6 * * @param string $key * @param mixed $default * * @return mixed */ function get_valid( $key, $default = null ) { $this->_logger->entrance( 'key = ' . $key ); $cache_entry = $this->_options->get_option( $key, false ); if ( is_object( $cache_entry ) && isset( $cache_entry->timestamp ) && is_numeric( $cache_entry->timestamp ) && $cache_entry->timestamp > WP_FS__SCRIPT_START_TIME ) { return $cache_entry->result; } return is_object( $default ) ? clone $default : $default; } /** * @author Vova Feldman (@svovaf) * @since 1.1.6 * * @param string $key * @param mixed $value * @param int $expiration * @param int $created Since 2.0.0 Cache creation date. */ function set( $key, $value, $expiration = WP_FS__TIME_24_HOURS_IN_SEC, $created = WP_FS__SCRIPT_START_TIME ) { $this->_logger->entrance( 'key = ' . $key ); $cache_entry = new stdClass(); $cache_entry->result = $value; $cache_entry->created = $created; $cache_entry->timestamp = $created + $expiration; $this->_options->set_option( $key, $cache_entry, true ); } /** * Get cached record expiration, or false if not cached or expired. * * @author Vova Feldman (@svovaf) * @since 1.1.7.3 * * @param string $key * * @return bool|int */ function get_record_expiration( $key ) { $this->_logger->entrance( 'key = ' . $key ); $cache_entry = $this->_options->get_option( $key, false ); if ( is_object( $cache_entry ) && isset( $cache_entry->timestamp ) && is_numeric( $cache_entry->timestamp ) && $cache_entry->timestamp > WP_FS__SCRIPT_START_TIME ) { return $cache_entry->timestamp; } return false; } /** * Purge cached item. * * @author Vova Feldman (@svovaf) * @since 1.1.6 * * @param string $key */ function purge( $key ) { $this->_logger->entrance( 'key = ' . $key ); $this->_options->unset_option( $key, true ); } /** * Extend cached item caching period. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $key * @param int $expiration * * @return bool */ function update_expiration( $key, $expiration = WP_FS__TIME_24_HOURS_IN_SEC ) { $this->_logger->entrance( 'key = ' . $key ); $cache_entry = $this->_options->get_option( $key, false ); if ( ! is_object( $cache_entry ) || ! isset( $cache_entry->timestamp ) || ! is_numeric( $cache_entry->timestamp ) ) { return false; } $this->set( $key, $cache_entry->result, $expiration, $cache_entry->created ); return true; } /** * Set cached item as expired. * * @author Vova Feldman (@svovaf) * @since 1.2.2.7 * * @param string $key */ function expire( $key ) { $this->_logger->entrance( 'key = ' . $key ); $cache_entry = $this->_options->get_option( $key, false ); if ( is_object( $cache_entry ) && isset( $cache_entry->timestamp ) && is_numeric( $cache_entry->timestamp ) ) { // Set to expired. $cache_entry->timestamp = WP_FS__SCRIPT_START_TIME; $this->_options->set_option( $key, $cache_entry, true ); } } #-------------------------------------------------------------------------------- #region Migration #-------------------------------------------------------------------------------- /** * Migrate options from site level. * * @author Vova Feldman (@svovaf) * @since 2.0.0 */ function migrate_to_network() { $this->_options->migrate_to_network(); } #endregion }PK!(a88Flibraries/freemius/includes/managers/class-fs-admin-notice-manager.phpnu[ 0 ) { $key .= ":{$network_level_or_blog_id}"; } else { $network_level_or_blog_id = get_current_blog_id(); $key .= ":{$network_level_or_blog_id}"; } } if ( ! isset( self::$_instances[ $key ] ) ) { self::$_instances[ $key ] = new FS_Admin_Notice_Manager( $id, $title, $module_unique_affix, $is_network_and_blog_admins, $network_level_or_blog_id ); } return self::$_instances[ $key ]; } /** * @param string $id * @param string $title * @param string $module_unique_affix * @param bool $is_network_and_blog_admins Whether or not the message should be shown both on network and * blog admin pages. * @param bool|int $network_level_or_blog_id */ protected function __construct( $id, $title = '', $module_unique_affix = '', $is_network_and_blog_admins = false, $network_level_or_blog_id = false ) { $this->_id = $id; $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $this->_id . '_data', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); $this->_title = ! empty( $title ) ? $title : ''; $this->_module_unique_affix = $module_unique_affix; $this->_sticky_storage = FS_Key_Value_Storage::instance( 'admin_notices', $this->_id, $network_level_or_blog_id ); if ( is_multisite() ) { $this->_is_network_notices = ( true === $network_level_or_blog_id ); if ( is_numeric( $network_level_or_blog_id ) ) { $this->_blog_id = $network_level_or_blog_id; } } else { $this->_is_network_notices = false; } $is_network_admin = fs_is_network_admin(); $is_blog_admin = fs_is_blog_admin(); if ( ( $this->_is_network_notices && $is_network_admin ) || ( ! $this->_is_network_notices && $is_blog_admin ) || ( $is_network_and_blog_admins && ( $is_network_admin || $is_blog_admin ) ) ) { if ( 0 < count( $this->_sticky_storage ) ) { $ajax_action_suffix = str_replace( ':', '-', $this->_id ); // If there are sticky notices for the current slug, add a callback // to the AJAX action that handles message dismiss. add_action( "wp_ajax_fs_dismiss_notice_action_{$ajax_action_suffix}", array( &$this, 'dismiss_notice_ajax_callback' ) ); foreach ( $this->_sticky_storage as $msg ) { // Add admin notice. $this->add( $msg['message'], $msg['title'], $msg['type'], true, $msg['id'], false, isset( $msg['wp_user_id'] ) ? $msg['wp_user_id'] : null, ! empty( $msg['plugin'] ) ? $msg['plugin'] : null, $is_network_and_blog_admins, isset( $msg['dismissible'] ) ? $msg['dismissible'] : null ); } } } } /** * Remove sticky message by ID. * * @author Vova Feldman (@svovaf) * @since 1.0.7 * */ function dismiss_notice_ajax_callback() { check_admin_referer( 'fs_dismiss_notice_action' ); if ( ! is_numeric( $_POST['message_id'] ) ) { $this->_sticky_storage->remove( $_POST['message_id'] ); } wp_die(); } /** * Rendered sticky message dismiss JavaScript. * * @author Vova Feldman (@svovaf) * @since 1.0.7 */ static function _add_sticky_dismiss_javascript() { $sticky_admin_notice_js_template_name = 'sticky-admin-notice-js.php'; if ( ! file_exists( fs_get_template_path( $sticky_admin_notice_js_template_name ) ) ) { return; } $params = array(); fs_require_once_template( $sticky_admin_notice_js_template_name, $params ); } private static $_added_sticky_javascript = false; /** * Hook to the admin_footer to add sticky message dismiss JavaScript handler. * * @author Vova Feldman (@svovaf) * @since 1.0.7 */ private static function has_sticky_messages() { if ( ! self::$_added_sticky_javascript ) { add_action( 'admin_footer', array( 'FS_Admin_Notice_Manager', '_add_sticky_dismiss_javascript' ) ); } } /** * Handle admin_notices by printing the admin messages stacked in the queue. * * @author Vova Feldman (@svovaf) * @since 1.0.4 * */ function _admin_notices_hook() { if ( function_exists( 'current_user_can' ) && ! current_user_can( 'manage_options' ) ) { // Only show messages to admins. return; } foreach ( $this->_notices as $id => $msg ) { if ( isset( $msg['wp_user_id'] ) && is_numeric( $msg['wp_user_id'] ) ) { if ( get_current_user_id() != $msg['wp_user_id'] ) { continue; } } /** * Added a filter to control the visibility of admin notices. * * Usage example: * * /** * * @param bool $show * * @param array $msg { * * @var string $message The actual message. * * @var string $title An optional message title. * * @var string $type The type of the message ('success', 'update', 'warning', 'promotion'). * * @var string $id The unique identifier of the message. * * @var string $manager_id The unique identifier of the notices manager. For plugins it would be the plugin's slug, for themes - `-theme`. * * @var string $plugin The product's title. * * @var string $wp_user_id An optional WP user ID that this admin notice is for. * * } * * * * @return bool * *\/ * function my_custom_show_admin_notice( $show, $msg ) { * if ('trial_promotion' != $msg['id']) { * return false; * } * * return $show; * } * * my_fs()->add_filter( 'show_admin_notice', 'my_custom_show_admin_notice', 10, 2 ); * * @author Vova Feldman * @since 2.2.0 */ $show_notice = call_user_func_array( 'fs_apply_filter', array( $this->_module_unique_affix, 'show_admin_notice', $this->show_admin_notices(), $msg ) ); if ( true !== $show_notice ) { continue; } fs_require_template( 'admin-notice.php', $msg ); if ( $msg['sticky'] ) { self::has_sticky_messages(); } } } /** * Enqueue common stylesheet to style admin notice. * * @author Vova Feldman (@svovaf) * @since 1.0.7 */ function _enqueue_styles() { fs_enqueue_local_style( 'fs_common', '/admin/common.css' ); } /** * Check if the current page is the Gutenberg block editor. * * @author Vova Feldman (@svovaf) * @since 2.2.3 * * @return bool */ function is_gutenberg_page() { if ( function_exists( 'is_gutenberg_page' ) && is_gutenberg_page() ) { // The Gutenberg plugin is on. return true; } $current_screen = get_current_screen(); if ( method_exists( $current_screen, 'is_block_editor' ) && $current_screen->is_block_editor() ) { // Gutenberg page on 5+. return true; } return false; } /** * Check if admin notices should be shown on page. E.g., we don't want to show notices in the Visual Editor. * * @author Xiaheng Chen (@xhchen) * @since 2.4.2 * * @return bool */ function show_admin_notices() { global $pagenow; if ( 'about.php' === $pagenow ) { // Don't show admin notices on the About page. return false; } if ( $this->is_gutenberg_page() ) { // Don't show admin notices in Gutenberg (visual editor). return false; } return true; } /** * Add admin message to admin messages queue, and hook to admin_notices / all_admin_notices if not yet hooked. * * @author Vova Feldman (@svovaf) * @since 1.0.4 * * @param string $message * @param string $title * @param string $type * @param bool $is_sticky * @param string $id Message ID * @param bool $store_if_sticky * @param number|null $wp_user_id * @param string|null $plugin_title * @param bool $is_network_and_blog_admins Whether or not the message should be shown both on network * and blog admin pages. * @param bool|null $is_dismissible * @param array $data * * @uses add_action() */ function add( $message, $title = '', $type = 'success', $is_sticky = false, $id = '', $store_if_sticky = true, $wp_user_id = null, $plugin_title = null, $is_network_and_blog_admins = false, $is_dismissible = null, $data = array() ) { $notices_type = $this->get_notices_type(); if ( empty( $this->_notices ) ) { if ( ! $is_network_and_blog_admins ) { add_action( $notices_type, array( &$this, "_admin_notices_hook" ) ); } else { add_action( 'network_admin_notices', array( &$this, "_admin_notices_hook" ) ); add_action( 'admin_notices', array( &$this, "_admin_notices_hook" ) ); } add_action( 'admin_enqueue_scripts', array( &$this, '_enqueue_styles' ) ); } if ( '' === $id ) { $id = md5( $title . ' ' . $message . ' ' . $type ); } $message_object = array( 'message' => $message, 'title' => $title, 'type' => $type, 'sticky' => $is_sticky, 'id' => $id, 'manager_id' => $this->_id, 'plugin' => ( ! is_null( $plugin_title ) ? $plugin_title : $this->_title ), 'wp_user_id' => $wp_user_id, 'dismissible' => $is_dismissible, 'data' => $data ); if ( $is_sticky && $store_if_sticky ) { $this->_sticky_storage->{$id} = $message_object; } $this->_notices[ $id ] = $message_object; } /** * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @param string|string[] $ids * @param bool $store */ function remove_sticky( $ids, $store = true ) { if ( ! is_array( $ids ) ) { $ids = array( $ids ); } foreach ( $ids as $id ) { // Remove from sticky storage. $this->_sticky_storage->remove( $id, $store ); if ( isset( $this->_notices[ $id ] ) ) { unset( $this->_notices[ $id ] ); } } } /** * Check if sticky message exists by id. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @param $id * * @return bool */ function has_sticky( $id ) { return isset( $this->_sticky_storage[ $id ] ); } /** * Adds sticky admin notification. * * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @param string $message * @param string $id Message ID * @param string $title * @param string $type * @param number|null $wp_user_id * @param string|null $plugin_title * @param bool $is_network_and_blog_admins Whether or not the message should be shown both on network * and blog admin pages. * @param bool $is_dimissible * @param array $data */ function add_sticky( $message, $id, $title = '', $type = 'success', $wp_user_id = null, $plugin_title = null, $is_network_and_blog_admins = false, $is_dimissible = true, $data = array() ) { if ( ! empty( $this->_module_unique_affix ) ) { $message = fs_apply_filter( $this->_module_unique_affix, "sticky_message_{$id}", $message ); $title = fs_apply_filter( $this->_module_unique_affix, "sticky_title_{$id}", $title ); } $this->add( $message, $title, $type, true, $id, true, $wp_user_id, $plugin_title, $is_network_and_blog_admins, $is_dimissible, $data ); } /** * Retrieves the data of an sticky notice. * * @author Leo Fajardo (@leorw) * @since 2.4.3 * * @param string $id Message ID. * * @return array|null */ function get_sticky( $id ) { return isset( $this->_sticky_storage->{$id} ) ? $this->_sticky_storage->{$id} : null; } /** * Clear all sticky messages. * * @author Vova Feldman (@svovaf) * @since 1.0.8 * * @param bool $is_temporary @since 2.5.1 */ function clear_all_sticky( $is_temporary = false ) { if ( $is_temporary ) { $this->_notices = array(); } else { $this->_sticky_storage->clear_all(); } } #-------------------------------------------------------------------------------- #region Helper Method #-------------------------------------------------------------------------------- /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return string */ private function get_notices_type() { return $this->_is_network_notices ? 'network_admin_notices' : 'admin_notices'; } #endregion } PK!R%xx>libraries/freemius/includes/managers/class-fs-plan-manager.phpnu[is_utilized() && $license->is_features_enabled() ) { return true; } } } return false; } /** * Check if plugin has any paid plans. * * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @param FS_Plugin_Plan[] $plans * * @return bool */ function has_paid_plan( $plans ) { if ( ! is_array( $plans ) || 0 === count( $plans ) ) { return false; } /** * @var FS_Plugin_Plan[] $plans */ for ( $i = 0, $len = count( $plans ); $i < $len; $i ++ ) { if ( ! $plans[ $i ]->is_free() ) { return true; } } return false; } /** * Check if plugin has any free plan, or is it premium only. * * Note: If no plans configured, assume plugin is free. * * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @param FS_Plugin_Plan[] $plans * * @return bool */ function has_free_plan( $plans ) { if ( ! is_array( $plans ) || 0 === count( $plans ) ) { return true; } /** * @var FS_Plugin_Plan[] $plans */ for ( $i = 0, $len = count( $plans ); $i < $len; $i ++ ) { if ( $plans[ $i ]->is_free() ) { return true; } } return false; } /** * Find all plans that have trial. * Since 2.6.2 call get_filtered_plan * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @param FS_Plugin_Plan[] $plans * * @return FS_Plugin_Plan[] */ function get_trial_plans( $plans ) { return $this->get_filtered_plans( $plans, true ); } /** * Find all plans that are not hidden and have trial. * * @author Daniele Alessandra (@danielealessandra) * * @param FS_Plugin_Plan[] $plans * * @return FS_Plugin_Plan[] * @since 2.6.3 * */ function get_visible_trial_plans( $plans ) { return $this->get_filtered_plans( $plans, true, true ); } /** * Find all plans filtered by trial or visibility. * * @author Daniele Alessandra (@danielealessandra) * * @param FS_Plugin_Plan[] $plans * @param boolean $should_have_trials * @param boolean $should_be_visible * * @return FS_Plugin_Plan[] * @since 2.6.3 * */ function get_filtered_plans( $plans, $should_have_trials = false, $should_be_visible = false ) { $filtered_plans = array(); if ( is_array( $plans ) && count( $plans ) > 0 ) { foreach ( $plans as $plan ) { if ( ( $should_have_trials && ! $plan->has_trial() ) || ( $should_be_visible && $plan->is_hidden ) ) { continue; } $filtered_plans[] = $plan; } } return $filtered_plans; } /** * Check if plugin has any trial plan. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @param FS_Plugin_Plan[] $plans * * @return bool */ function has_trial_plan( $plans ) { if ( ! is_array( $plans ) || 0 === count( $plans ) ) { return true; } /** * @var FS_Plugin_Plan[] $plans */ for ( $i = 0, $len = count( $plans ); $i < $len; $i ++ ) { if ( $plans[ $i ]->has_trial() ) { return true; } } return false; } }PK!/1c)++@libraries/freemius/includes/managers/class-fs-option-manager.phpnu[_logger = FS_Logger::get_logger( WP_FS__SLUG . '_opt_mngr_' . $id, WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); $this->_logger->entrance(); $this->_logger->log( 'id = ' . $id ); $this->_id = $id; $this->_autoload = $autoload; if ( is_multisite() ) { $this->_is_network_storage = ( true === $network_level_or_blog_id ); if ( is_numeric( $network_level_or_blog_id ) ) { $this->_blog_id = $network_level_or_blog_id; } } else { $this->_is_network_storage = false; } if ( $load ) { $this->load(); } } /** * @author Vova Feldman (@svovaf) * @since 1.0.3 * * @param string $id * @param bool $load * @param bool|int $network_level_or_blog_id Since 2.0.0 * @param bool|null $autoload * * @return \FS_Option_Manager */ static function get_manager( $id, $load = false, $network_level_or_blog_id = false, $autoload = null ) { $key = strtolower( $id ); if ( is_multisite() ) { if ( true === $network_level_or_blog_id ) { $key .= ':ms'; } else if ( is_numeric( $network_level_or_blog_id ) && $network_level_or_blog_id > 0 ) { $key .= ":{$network_level_or_blog_id}"; } else { $network_level_or_blog_id = get_current_blog_id(); $key .= ":{$network_level_or_blog_id}"; } } if ( ! isset( self::$_MANAGERS[ $key ] ) ) { self::$_MANAGERS[ $key ] = new FS_Option_Manager( $id, $load, $network_level_or_blog_id, $autoload ); } // If load required but not yet loaded, load. else if ( $load && ! self::$_MANAGERS[ $key ]->is_loaded() ) { self::$_MANAGERS[ $key ]->load(); } return self::$_MANAGERS[ $key ]; } /** * @author Vova Feldman (@svovaf) * @since 1.0.3 * * @param bool $flush */ function load( $flush = false ) { $this->_logger->entrance(); if ( ! $flush && isset( $this->_options ) ) { return; } if ( isset( $this->_options ) ) { // Clear prev options. $this->clear(); } $option_name = $this->get_option_manager_name(); if ( $this->_is_network_storage ) { $this->_options = get_site_option( $option_name ); } else if ( $this->_blog_id > 0 ) { $this->_options = get_blog_option( $this->_blog_id, $option_name ); } else { $this->_options = get_option( $option_name ); } if ( is_string( $this->_options ) ) { $this->_options = json_decode( $this->_options ); } // $this->_logger->info('get_option = ' . var_export($this->_options, true)); if ( false === $this->_options ) { $this->clear(); } } /** * @author Vova Feldman (@svovaf) * @since 1.0.3 * * @return bool */ function is_loaded() { return isset( $this->_options ); } /** * @author Vova Feldman (@svovaf) * @since 1.0.3 * * @return bool */ function is_empty() { return ( $this->is_loaded() && false === $this->_options ); } /** * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param bool $flush */ function clear( $flush = false ) { $this->_logger->entrance(); $this->_options = array(); if ( $flush ) { $this->store(); } } /** * Delete options manager from DB. * * @author Vova Feldman (@svovaf) * @since 1.0.9 */ function delete() { $option_name = $this->get_option_manager_name(); if ( $this->_is_network_storage ) { delete_site_option( $option_name ); } else if ( $this->_blog_id > 0 ) { delete_blog_option( $this->_blog_id, $option_name ); } else { delete_option( $option_name ); } } /** * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param string $option * @param bool $flush * * @return bool */ function has_option( $option, $flush = false ) { if ( ! $this->is_loaded() || $flush ) { $this->load( $flush ); } return array_key_exists( $option, $this->_options ); } /** * @author Vova Feldman (@svovaf) * @since 1.0.3 * * @param string $option * @param mixed $default * @param bool $flush * * @return mixed */ function get_option( $option, $default = null, $flush = false ) { $this->_logger->entrance( 'option = ' . $option ); if ( ! $this->is_loaded() || $flush ) { $this->load( $flush ); } if ( is_array( $this->_options ) ) { $value = isset( $this->_options[ $option ] ) ? $this->_options[ $option ] : $default; } else if ( is_object( $this->_options ) ) { $value = isset( $this->_options->{$option} ) ? $this->_options->{$option} : $default; } else { $value = $default; } /** * If it's an object, return a clone of the object, otherwise, * external changes of the object will actually change the value * of the object in the option manager which may lead to an unexpected * behaviour and data integrity when a store() call is triggered. * * Example: * $object1 = $options->get_option( 'object1' ); * $object1->x = 123; * * $object2 = $options->get_option( 'object2' ); * $object2->y = 'dummy'; * * $options->set_option( 'object2', $object2, true ); * * If we don't return a clone of option 'object1', setting 'object2' * will also store the updated value of 'object1' which is quite not * an expected behaviour. * * @author Vova Feldman */ return is_object( $value ) ? clone $value : $value; } /** * @author Vova Feldman (@svovaf) * @since 1.0.3 * * @param string $option * @param mixed $value * @param bool $flush */ function set_option( $option, $value, $flush = false ) { $this->_logger->entrance( 'option = ' . $option ); if ( ! $this->is_loaded() ) { $this->clear(); } /** * If it's an object, store a clone of the object, otherwise, * external changes of the object will actually change the value * of the object in the options manager which may lead to an unexpected * behaviour and data integrity when a store() call is triggered. * * Example: * $object1 = new stdClass(); * $object1->x = 123; * * $options->set_option( 'object1', $object1 ); * * $object1->x = 456; * * $options->set_option( 'object2', $object2, true ); * * If we don't set the option as a clone of option 'object1', setting 'object2' * will also store the updated value of 'object1' ($object1->x = 456 instead of * $object1->x = 123) which is quite not an expected behaviour. * * @author Vova Feldman */ $copy = is_object( $value ) ? clone $value : $value; if ( is_array( $this->_options ) ) { $this->_options[ $option ] = $copy; } else if ( is_object( $this->_options ) ) { $this->_options->{$option} = $copy; } if ( $flush ) { $this->store(); } } /** * Unset option. * * @author Vova Feldman (@svovaf) * @since 1.0.3 * * @param string $option * @param bool $flush */ function unset_option( $option, $flush = false ) { $this->_logger->entrance( 'option = ' . $option ); if ( is_array( $this->_options ) ) { if ( ! isset( $this->_options[ $option ] ) ) { return; } unset( $this->_options[ $option ] ); } else if ( is_object( $this->_options ) ) { if ( ! isset( $this->_options->{$option} ) ) { return; } unset( $this->_options->{$option} ); } if ( $flush ) { $this->store(); } } /** * Dump options to database. * * @author Vova Feldman (@svovaf) * @since 1.0.3 */ function store() { $this->_logger->entrance(); $option_name = $this->get_option_manager_name(); if ( $this->_logger->is_on() ) { $this->_logger->info( $option_name . ' = ' . var_export( $this->_options, true ) ); } // Update DB. if ( $this->_is_network_storage ) { update_site_option( $option_name, $this->_options ); } else if ( $this->_blog_id > 0 ) { update_blog_option( $this->_blog_id, $option_name, $this->_options ); } else { update_option( $option_name, $this->_options, $this->_autoload ); } } /** * Get options keys. * * @author Vova Feldman (@svovaf) * @since 1.0.3 * * @return string[] */ function get_options_keys() { if ( is_array( $this->_options ) ) { return array_keys( $this->_options ); } else if ( is_object( $this->_options ) ) { return array_keys( get_object_vars( $this->_options ) ); } return array(); } #-------------------------------------------------------------------------------- #region Migration #-------------------------------------------------------------------------------- /** * Migrate options from site level. * * @author Vova Feldman (@svovaf) * @since 2.0.0 */ function migrate_to_network() { $site_options = FS_Option_Manager::get_manager($this->_id, true, false); $options = is_object( $site_options->_options ) ? get_object_vars( $site_options->_options ) : $site_options->_options; if ( ! empty( $options ) ) { foreach ( $options as $key => $val ) { $this->set_option( $key, $val, false ); } $this->store(); } } #endregion #-------------------------------------------------------------------------------- #region Helper Methods #-------------------------------------------------------------------------------- /** * @return string */ private function get_option_manager_name() { return $this->_id; } #endregion } PK!p2WW.libraries/freemius/includes/managers/index.phpnu[libraries/freemius/includes/managers/class-fs-gdpr-manager.phpnu[_storage = FS_Option_Manager::get_manager( WP_FS__GDPR_OPTION_NAME, true, true ); $this->_wp_user_id = Freemius::get_current_wp_user_id(); $this->_option_name = "u{$this->_wp_user_id}"; $this->_data = $this->_storage->get_option( $this->_option_name, array() ); $this->_notices = FS_Admin_Notices::instance( 'all_admins', '', '', true ); if ( ! is_array( $this->_data ) ) { $this->_data = array(); } } /** * Update a GDPR option for the current admin and store it. * * @author Vova Feldman (@svovaf) * @since 2.1.0 * * @param string $name * @param mixed $value */ private function update_option( $name, $value ) { $this->_data[ $name ] = $value; $this->_storage->set_option( $this->_option_name, $this->_data, true ); } /** * @author Leo Fajardo (@leorw) * @since 2.1.0 * * @param bool $is_required */ public function store_is_required( $is_required ) { $this->update_option( 'required', $is_required ); } /** * Checks if the GDPR opt-in sticky notice is currently shown. * * @author Vova Feldman (@svovaf) * @since 2.1.0 * * @return bool */ public function is_opt_in_notice_shown() { return $this->_notices->has_sticky( "gdpr_optin_actions_{$this->_wp_user_id}", true ); } /** * Remove the GDPR opt-in sticky notice. * * @author Vova Feldman (@svovaf) * @since 2.1.0 */ public function remove_opt_in_notice() { $this->_notices->remove_sticky( "gdpr_optin_actions_{$this->_wp_user_id}", true ); $this->disable_opt_in_notice(); } /** * Prevents the opt-in message from being added/shown. * * @author Leo Fajardo (@leorw) * @since 2.1.0 */ public function disable_opt_in_notice() { $this->update_option( 'show_opt_in_notice', false ); } /** * Checks if a GDPR opt-in message needs to be shown to the current admin. * * @author Vova Feldman (@svovaf) * @since 2.1.0 * * @return bool */ public function should_show_opt_in_notice() { return ( ! isset( $this->_data['show_opt_in_notice'] ) || true === $this->_data['show_opt_in_notice'] ); } /** * Get the last time the GDPR opt-in notice was shown. * * @author Vova Feldman (@svovaf) * @since 2.1.0 * * @return false|int */ public function last_time_notice_was_shown() { return isset( $this->_data['notice_shown_at'] ) ? $this->_data['notice_shown_at'] : false; } /** * Update the timestamp of the last time the GDPR opt-in message was shown to now. * * @author Vova Feldman (@svovaf) * @since 2.1.0 */ public function notice_was_just_shown() { $this->update_option( 'notice_shown_at', WP_FS__SCRIPT_START_TIME ); } /** * @param string $message * @param string|null $plugin_title * * @author Vova Feldman (@svovaf) * @since 2.1.0 */ public function add_opt_in_sticky_notice( $message, $plugin_title = null ) { $this->_notices->add_sticky( $message, "gdpr_optin_actions_{$this->_wp_user_id}", '', 'promotion', true, $this->_wp_user_id, $plugin_title, true ); } }PK!!Y9Q3Q3?libraries/freemius/includes/managers/class-fs-debug-manager.phpnu[entrance(); $title = sprintf( '%s [v.%s]', fs_text_inline( 'Freemius Debug' ), WP_FS__SDK_VERSION ); if ( WP_FS__DEV_MODE ) { // Add top-level debug menu item. $hook = FS_Admin_Menu_Manager::add_page( $title, $title, 'manage_options', 'freemius', array( self::class, '_debug_page_render' ) ); } else { // Add hidden debug page. $hook = FS_Admin_Menu_Manager::add_subpage( '', $title, $title, 'manage_options', 'freemius', array( self::class, '_debug_page_render' ) ); } if ( ! empty( $hook ) ) { add_action( "load-$hook", array( self::class, '_debug_page_actions' ) ); } } /** * @author Vova Feldman (@svovaf) * Moved from Freemius * * @since 1.0.8 */ static function _debug_page_actions() { Freemius::_clean_admin_content_section(); if ( fs_request_is_action( 'restart_freemius' ) ) { check_admin_referer( 'restart_freemius' ); if ( ! is_multisite() ) { // Clear accounts data. Freemius::get_accounts()->clear( null, true ); } else { $sites = Freemius::get_sites(); foreach ( $sites as $site ) { $blog_id = Freemius::get_site_blog_id( $site ); Freemius::get_accounts()->clear( $blog_id, true ); } // Clear network level storage. Freemius::get_accounts()->clear( true, true ); } // Clear SDK reference cache. delete_option( 'fs_active_plugins' ); } else if ( fs_request_is_action( 'clear_updates_data' ) ) { check_admin_referer( 'clear_updates_data' ); if ( ! is_multisite() ) { set_site_transient( 'update_plugins', null ); set_site_transient( 'update_themes', null ); } else { $current_blog_id = get_current_blog_id(); $sites = Freemius::get_sites(); foreach ( $sites as $site ) { switch_to_blog( Freemius::get_site_blog_id( $site ) ); set_site_transient( 'update_plugins', null ); set_site_transient( 'update_themes', null ); } switch_to_blog( $current_blog_id ); } } else if ( fs_request_is_action( 'reset_deactivation_snoozing' ) ) { check_admin_referer( 'reset_deactivation_snoozing' ); Freemius::reset_deactivation_snoozing(); } else if ( fs_request_is_action( 'simulate_trial' ) ) { check_admin_referer( 'simulate_trial' ); $fs = freemius( fs_request_get( 'module_id' ) ); // Update SDK install to at least 24 hours before. $fs->get_storage()->install_timestamp = ( time() - WP_FS__TIME_24_HOURS_IN_SEC ); // Unset the trial shown timestamp. unset( $fs->get_storage()->trial_promotion_shown ); } else if ( fs_request_is_action( 'simulate_network_upgrade' ) ) { check_admin_referer( 'simulate_network_upgrade' ); $fs = freemius( fs_request_get( 'module_id' ) ); Freemius::set_network_upgrade_mode( $fs->get_storage() ); } else if ( fs_request_is_action( 'delete_install' ) ) { check_admin_referer( 'delete_install' ); Freemius::_delete_site_by_slug( fs_request_get( 'slug' ), fs_request_get( 'module_type' ), true, fs_request_get( 'blog_id', null ) ); } else if ( fs_request_is_action( 'delete_user' ) ) { check_admin_referer( 'delete_user' ); self::delete_user( fs_request_get( 'user_id' ) ); } else if ( fs_request_is_action( 'download_logs' ) ) { check_admin_referer( 'download_logs' ); $download_url = FS_Logger::download_db_logs( fs_request_get( 'filters', false, 'post' ) ); if ( false === $download_url ) { wp_die( 'Oops... there was an error while generating the logs download file. Please try again and if it doesn\'t work contact support@freemius.com.' ); } fs_redirect( $download_url ); } else if ( fs_request_is_action( 'migrate_options_to_network' ) ) { check_admin_referer( 'migrate_options_to_network' ); Freemius::migrate_options_to_network(); } } /** * @author Vova Feldman (@svovaf) * Moved from Freemius * * @since 1.0.8 */ static function _debug_page_render() { Freemius::get_static_logger()->entrance(); $all_modules_sites = self::get_all_modules_sites(); $licenses_by_module_type = self::get_all_licenses_by_module_type(); $vars = array( 'plugin_sites' => $all_modules_sites[ WP_FS__MODULE_TYPE_PLUGIN ], 'theme_sites' => $all_modules_sites[ WP_FS__MODULE_TYPE_THEME ], 'users' => Freemius::get_all_users(), 'addons' => Freemius::get_all_addons(), 'account_addons' => Freemius::get_all_account_addons(), 'plugin_licenses' => $licenses_by_module_type[ WP_FS__MODULE_TYPE_PLUGIN ], 'theme_licenses' => $licenses_by_module_type[ WP_FS__MODULE_TYPE_THEME ], ); fs_enqueue_local_style( 'fs_debug', '/admin/debug.css' ); fs_require_once_template( 'debug.php', $vars ); } /** * @author Vova Feldman (@svovaf) * Moved from Freemius * * @since 1.2.1.6 */ static function _get_debug_log() { check_admin_referer( 'fs_get_debug_log' ); if ( ! is_super_admin() ) { return; } if (!FS_Logger::is_storage_logging_on()) { return; } $limit = min( ! empty( $_POST['limit'] ) ? absint( $_POST['limit'] ) : 200, 200 ); $offset = min( ! empty( $_POST['offset'] ) ? absint( $_POST['offset'] ) : 200, 200 ); $logs = FS_Logger::load_db_logs( fs_request_get( 'filters', false, 'post' ), $limit, $offset ); Freemius::shoot_ajax_success( $logs ); } /** * @author Vova Feldman (@svovaf) * Moved from Freemius * * @since 1.2.1.7 */ static function _get_db_option() { check_admin_referer( 'fs_get_db_option' ); $option_name = fs_request_get( 'option_name' ); if ( ! is_super_admin() || ! fs_starts_with( $option_name, 'fs_' ) ) { Freemius::shoot_ajax_failure(); } $value = get_option( $option_name ); $result = array( 'name' => $option_name, ); if ( false !== $value ) { if ( ! is_string( $value ) ) { $value = json_encode( $value ); } $result['value'] = $value; } Freemius::shoot_ajax_success( $result ); } /** * @author Vova Feldman (@svovaf) * Moved from Freemius * * @since 1.2.1.7 */ static function _set_db_option() { check_admin_referer( 'fs_set_db_option' ); $option_name = fs_request_get( 'option_name' ); if ( ! is_super_admin() || ! fs_starts_with( $option_name, 'fs_' ) ) { Freemius::shoot_ajax_failure(); } $option_value = fs_request_get_raw( 'option_value' ); if ( ! empty( $option_value ) ) { update_option( $option_name, $option_value ); } Freemius::shoot_ajax_success(); } /** * @author Vova Feldman (@svovaf) * Moved from Freemius * * @since 1.1.7.3 */ static function _toggle_debug_mode() { check_admin_referer( 'fs_toggle_debug_mode' ); if ( ! is_super_admin() ) { return; } $is_on = fs_request_get( 'is_on', false, 'post' ); if ( fs_request_is_post() && in_array( $is_on, array( 0, 1 ) ) ) { if ( $is_on ) { self::_turn_on_debug_mode(); } else { self::_turn_off_debug_mode(); } // Logic to turn debugging off automatically if ( 1 == $is_on ) { // Plan a single event triggering after 24 hours to turn debugging off. wp_schedule_single_event( time() + 24 * HOUR_IN_SECONDS, 'fs_debug_turn_off_logging_hook' ); } else { // Cancels any planned event when debugging is turned off manually. $timestamp = wp_next_scheduled( 'fs_debug_turn_off_logging_hook' ); if ( $timestamp ) { wp_unschedule_event( $timestamp, 'fs_debug_turn_off_logging_hook' ); } } } exit; } /** * @author Daniele Alessandra (@danielealessandra) * @since 2.6.2 * */ static function _turn_off_debug_mode() { self::update_debug_mode_option( 0 ); FS_Logger::_set_storage_logging( false ); } /** * @author Daniele Alessandra (@danielealessandra) * @since 2.6.2 * */ static function _turn_on_debug_mode() { self::update_debug_mode_option( 1 ); FS_Logger::_set_storage_logging(); } /** * @author Leo Fajardo (@leorw) * Moved from Freemius * * @param string $url * @param array $request * * @since 2.1.0 * */ public static function enrich_request_for_debug( &$url, &$request ) { if ( WP_FS__DEBUG_SDK || isset( $_COOKIE['XDEBUG_SESSION'] ) ) { $url = add_query_arg( 'XDEBUG_SESSION_START', rand( 0, 9999999 ), $url ); $url = add_query_arg( 'XDEBUG_SESSION', 'PHPSTORM', $url ); $request['cookies'] = array( new WP_Http_Cookie( array( 'name' => 'XDEBUG_SESSION', 'value' => 'PHPSTORM', ) ), ); } } /** * @author Leo Fajardo (@leorw) * Moved from Freemius * * @return array * * @since 2.0.0 * */ private static function get_all_licenses_by_module_type() { $licenses = Freemius::get_account_option( 'all_licenses' ); $licenses_by_module_type = array( WP_FS__MODULE_TYPE_PLUGIN => array(), WP_FS__MODULE_TYPE_THEME => array(), ); if ( ! is_array( $licenses ) ) { return $licenses_by_module_type; } foreach ( $licenses as $module_id => $module_licenses ) { $fs = Freemius::get_instance_by_id( $module_id ); if ( false === $fs ) { continue; } $licenses_by_module_type[ $fs->get_module_type() ] = array_merge( $licenses_by_module_type[ $fs->get_module_type() ], $module_licenses ); } return $licenses_by_module_type; } /** * Moved from the Freemius class. * * @author Leo Fajardo (@leorw) * * @return array * * @since 2.5.0 */ static function get_all_modules_sites() { Freemius::get_static_logger()->entrance(); $sites_by_type = array( WP_FS__MODULE_TYPE_PLUGIN => array(), WP_FS__MODULE_TYPE_THEME => array(), ); $module_types = array_keys( $sites_by_type ); if ( ! is_multisite() ) { foreach ( $module_types as $type ) { $sites_by_type[ $type ] = Freemius::get_all_sites( $type ); foreach ( $sites_by_type[ $type ] as $slug => $install ) { $sites_by_type[ $type ][ $slug ] = array( $install ); } } } else { $sites = Freemius::get_sites(); foreach ( $sites as $site ) { $blog_id = Freemius::get_site_blog_id( $site ); foreach ( $module_types as $type ) { $installs = Freemius::get_all_sites( $type, $blog_id ); foreach ( $installs as $slug => $install ) { if ( ! isset( $sites_by_type[ $type ][ $slug ] ) ) { $sites_by_type[ $type ][ $slug ] = array(); } $install->blog_id = $blog_id; $sites_by_type[ $type ][ $slug ][] = $install; } } } } return $sites_by_type; } /** * Delete user. * * @author Vova Feldman (@svovaf) * * @param number $user_id * @param bool $store * * @return false|int The user ID if deleted. Otherwise, FALSE (when install not exist). * @since 2.0.0 * */ public static function delete_user( $user_id, $store = true ) { $users = Freemius::get_all_users(); if ( ! is_array( $users ) || ! isset( $users[ $user_id ] ) ) { return false; } unset( $users[ $user_id ] ); self::$_accounts->set_option( 'users', $users, $store ); return $user_id; } /** * @author Daniele Alessandra (@danielealessandra) * * @return void * @since 2.6.2 * */ public static function load_required_static() { if ( ! WP_FS__DEMO_MODE ) { add_action( ( fs_is_network_admin() ? 'network_' : '' ) . 'admin_menu', array( self::class, '_add_debug_section', ) ); } add_action( "wp_ajax_fs_toggle_debug_mode", array( self::class, '_toggle_debug_mode' ) ); Freemius::add_ajax_action_static( 'get_debug_log', array( self::class, '_get_debug_log' ) ); Freemius::add_ajax_action_static( 'get_db_option', array( self::class, '_get_db_option' ) ); Freemius::add_ajax_action_static( 'set_db_option', array( self::class, '_set_db_option' ) ); } /** * @author Daniele Alessandra (@danielealessandra) * * @return void * * @since 2.6.2 */ public static function register_hooks() { add_action( 'fs_debug_turn_off_logging_hook', array( self::class, '_turn_off_debug_mode' ) ); } /** * @author Daniele Alessandra (@danielealessandra) * * @param int $is_on * * @return void * * @since 2.6.2 */ private static function update_debug_mode_option( $is_on ) { update_option( 'fs_debug_mode', $is_on ); } } PK!}{{@libraries/freemius/includes/managers/class-fs-plugin-manager.phpnu[_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $module_id . '_' . 'plugins', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); $this->_module_id = $module_id; $this->load(); } protected function get_option_manager() { return FS_Option_Manager::get_manager( WP_FS__ACCOUNTS_OPTION_NAME, true, true ); } /** * @author Leo Fajardo (@leorw) * @since 1.2.2 * * @param string|bool $module_type "plugin", "theme", or "false" for all modules. * * @return array */ protected function get_all_modules( $module_type = false ) { $option_manager = $this->get_option_manager(); if ( false !== $module_type ) { return fs_get_entities( $option_manager->get_option( $module_type . 's', array() ), FS_Plugin::get_class_name() ); } return array( self::OPTION_NAME_PLUGINS => fs_get_entities( $option_manager->get_option( self::OPTION_NAME_PLUGINS, array() ), FS_Plugin::get_class_name() ), self::OPTION_NAME_THEMES => fs_get_entities( $option_manager->get_option( self::OPTION_NAME_THEMES, array() ), FS_Plugin::get_class_name() ), ); } /** * Load plugin data from local DB. * * @author Vova Feldman (@svovaf) * @since 1.0.6 */ function load() { $all_modules = $this->get_all_modules(); if ( ! is_numeric( $this->_module_id ) ) { unset( $all_modules[ self::OPTION_NAME_THEMES ] ); } foreach ( $all_modules as $modules ) { /** * @since 1.2.2 * * @var $modules FS_Plugin[] */ foreach ( $modules as $module ) { $found_module = false; /** * If module ID is not numeric, it must be a plugin's slug. * * @author Leo Fajardo (@leorw) * @since 1.2.2 */ if ( ! is_numeric( $this->_module_id ) ) { if ( $this->_module_id === $module->slug ) { $this->_module_id = $module->id; $found_module = true; } } else if ( $this->_module_id == $module->id ) { $found_module = true; } if ( $found_module ) { $this->_module = $module; break; } } } } /** * Store plugin on local DB. * * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param bool|FS_Plugin $module * @param bool $flush * * @return bool|\FS_Plugin */ function store( $module = false, $flush = true ) { if ( false !== $module ) { $this->_module = $module; } $all_modules = $this->get_all_modules( $this->_module->type ); $all_modules[ $this->_module->slug ] = $this->_module; $options_manager = $this->get_option_manager(); $options_manager->set_option( $this->_module->type . 's', $all_modules, $flush ); return $this->_module; } /** * Update local plugin data if different. * * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param \FS_Plugin $plugin * @param bool $store * * @return bool True if plugin was updated. */ function update( FS_Plugin $plugin, $store = true ) { if ( ! ($this->_module instanceof FS_Plugin ) || $this->_module->slug != $plugin->slug || $this->_module->public_key != $plugin->public_key || $this->_module->secret_key != $plugin->secret_key || $this->_module->parent_plugin_id != $plugin->parent_plugin_id || $this->_module->title != $plugin->title ) { $this->store( $plugin, $store ); return true; } return false; } /** * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param FS_Plugin $plugin * @param bool $store */ function set( FS_Plugin $plugin, $store = false ) { $this->_module = $plugin; if ( $store ) { $this->store(); } } /** * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @return bool|\FS_Plugin */ function get() { if ( isset( $this->_module ) ) { return $this->_module; } if ( empty( $this->_module_id ) ) { return false; } /** * Return an FS_Plugin entity that has its `id` and `is_live` properties set (`is_live` is initialized in the FS_Plugin constructor) to avoid triggering an error that is relevant to these properties when the FS_Plugin entity is used before the `parse_settings()` method is called. This can happen when creating a regular WordPress site by cloning a subsite of a multisite network and the data that is stored in the network-level storage is not cloned. * * @author Leo Fajardo (@leorw) * @since 2.5.0 */ $plugin = new FS_Plugin(); $plugin->id = $this->_module_id; return $plugin; } }PK! 99Flibraries/freemius/includes/managers/class-fs-contact-form-manager.phpnu[ */ public function get_query_params( Freemius $fs ) { $context_params = array( 'plugin_id' => $fs->get_id(), 'plugin_public_key' => $fs->get_public_key(), 'plugin_version' => $fs->get_plugin_version(), ); // Get site context secure params. if ( $fs->is_registered() ) { $context_params = array_merge( $context_params, FS_Security::instance()->get_context_params( $fs->get_site(), time(), 'contact' ) ); } return array_merge( $_GET, array_merge( $context_params, array( 'plugin_version' => $fs->get_plugin_version(), 'wp_login_url' => wp_login_url(), 'site_url' => Freemius::get_unfiltered_site_url(), // 'wp_admin_css' => get_bloginfo('wpurl') . "/wp-admin/load-styles.php?c=1&load=buttons,wp-admin,dashicons", ) ) ); } /** * Retrieves the standalone link to the Freemius Contact Form. * * @param Freemius $fs * * @return string */ public function get_standalone_link( Freemius $fs ) { $query_params = $this->get_query_params( $fs ); $query_params['is_standalone'] = 'true'; $query_params['parent_url'] = admin_url( add_query_arg( null, null ) ); return WP_FS__ADDRESS . '/contact/?' . http_build_query( $query_params ); } }PK!KhaMaMDlibraries/freemius/includes/managers/class-fs-permission-manager.phpnu[ */ private static $_instances = array(); const PERMISSION_USER = 'user'; const PERMISSION_SITE = 'site'; const PERMISSION_EVENTS = 'events'; const PERMISSION_ESSENTIALS = 'essentials'; const PERMISSION_DIAGNOSTIC = 'diagnostic'; const PERMISSION_EXTENSIONS = 'extensions'; const PERMISSION_NEWSLETTER = 'newsletter'; /** * @param Freemius $fs * * @return self */ static function instance( Freemius $fs ) { $id = $fs->get_id(); if ( ! isset( self::$_instances[ $id ] ) ) { self::$_instances[ $id ] = new self( $fs ); } return self::$_instances[ $id ]; } /** * @param Freemius $fs */ protected function __construct( Freemius $fs ) { $this->_fs = $fs; $this->_storage = FS_Storage::instance( $fs->get_module_type(), $fs->get_slug() ); } /** * @return string[] */ static function get_all_permission_ids() { return array( self::PERMISSION_USER, self::PERMISSION_SITE, self::PERMISSION_EVENTS, self::PERMISSION_ESSENTIALS, self::PERMISSION_DIAGNOSTIC, self::PERMISSION_EXTENSIONS, self::PERMISSION_NEWSLETTER, ); } /** * @return string[] */ static function get_api_managed_permission_ids() { return array( self::PERMISSION_USER, self::PERMISSION_SITE, self::PERMISSION_EXTENSIONS, ); } /** * @param string $permission * * @return bool */ static function is_supported_permission( $permission ) { return in_array( $permission, self::get_all_permission_ids() ); } /** * @since 2.5.3 * * @return bool */ function is_premium_context() { return ( $this->_fs->is_premium() || $this->_fs->can_use_premium_code() ); } /** * @param bool $is_license_activation * @param array[] $extra_permissions * * @return array[] */ function get_permissions( $is_license_activation, array $extra_permissions = array() ) { return $is_license_activation ? $this->get_license_activation_permissions( $extra_permissions ) : $this->get_opt_in_permissions( $extra_permissions ); } #-------------------------------------------------------------------------------- #region Opt-In Permissions #-------------------------------------------------------------------------------- /** * @param array[] $extra_permissions * * @return array[] */ function get_opt_in_permissions( array $extra_permissions = array(), $load_default_from_storage = false, $is_optional = false ) { $permissions = array_merge( $this->get_opt_in_required_permissions( $load_default_from_storage ), $this->get_opt_in_optional_permissions( $load_default_from_storage, $is_optional ), $extra_permissions ); return $this->get_sorted_permissions_by_priority( $permissions ); } /** * @param bool $load_default_from_storage * * @return array[] */ function get_opt_in_required_permissions( $load_default_from_storage = false ) { return array( $this->get_user_permission( $load_default_from_storage ) ); } /** * @param bool $load_default_from_storage * @param bool $is_optional * * @return array[] */ function get_opt_in_optional_permissions( $load_default_from_storage = false, $is_optional = false ) { return array_merge( $this->get_opt_in_diagnostic_permissions( $load_default_from_storage, $is_optional ), array( $this->get_extensions_permission( false, false, $load_default_from_storage ) ) ); } /** * @param bool $load_default_from_storage * @param bool $is_optional * * @return array[] */ function get_opt_in_diagnostic_permissions( $load_default_from_storage = false, $is_optional = false ) { // Alias. $fs = $this->_fs; $permissions = array(); $permissions[] = $this->get_permission( self::PERMISSION_SITE, 'admin-links', $fs->get_text_inline( 'View Basic Website Info', 'permissions-site' ), $fs->get_text_inline( 'Homepage URL & title, WP & PHP versions, and site language', 'permissions-site_desc' ), sprintf( /* translators: %s: 'Plugin' or 'Theme' */ $fs->get_text_inline( 'To provide additional functionality that\'s relevant to your website, avoid WordPress or PHP version incompatibilities that can break your website, and recognize which languages & regions the %s should be translated and tailored to.', 'permissions-site_tooltip' ), $fs->get_module_label( true ) ), 10, $is_optional, true, $load_default_from_storage ); $permissions[] = $this->get_permission( self::PERMISSION_EVENTS, 'admin-' . ( $fs->is_plugin() ? 'plugins' : 'appearance' ), sprintf( $fs->get_text_inline( 'View Basic %s Info', 'permissions-events' ), $fs->get_module_label() ), sprintf( /* translators: %s: 'Plugin' or 'Theme' */ $fs->get_text_inline( 'Current %s & SDK versions, and if active or uninstalled', 'permissions-events_desc' ), $fs->get_module_label( true ) ), '', 20, $is_optional, true, $load_default_from_storage ); return $permissions; } #endregion #-------------------------------------------------------------------------------- #region License Activation Permissions #-------------------------------------------------------------------------------- /** * @param array[] $extra_permissions * * @return array[] */ function get_license_activation_permissions( array $extra_permissions = array(), $include_optional_label = true ) { $permissions = array_merge( $this->get_license_required_permissions(), $this->get_license_optional_permissions( $include_optional_label ), $extra_permissions ); return $this->get_sorted_permissions_by_priority( $permissions ); } /** * @param bool $load_default_from_storage * * @return array[] */ function get_license_required_permissions( $load_default_from_storage = false ) { // Alias. $fs = $this->_fs; $permissions = array(); $permissions[] = $this->get_permission( self::PERMISSION_ESSENTIALS, 'admin-links', $fs->get_text_inline( 'View License Essentials', 'permissions-essentials' ), $fs->get_text_inline( sprintf( /* translators: %s: 'Plugin' or 'Theme' */ 'Homepage URL, %s version, SDK version', $fs->get_module_label() ), 'permissions-essentials_desc' ), sprintf( /* translators: %s: 'Plugin' or 'Theme' */ $fs->get_text_inline( 'To let you manage & control where the license is activated and ensure %s security & feature updates are only delivered to websites you authorize.', 'permissions-essentials_tooltip' ), $fs->get_module_label( true ) ), 10, false, true, $load_default_from_storage ); $permissions[] = $this->get_permission( self::PERMISSION_EVENTS, 'admin-' . ( $fs->is_plugin() ? 'plugins' : 'appearance' ), sprintf( $fs->get_text_inline( 'View %s State', 'permissions-events' ), $fs->get_module_label() ), sprintf( /* translators: %s: 'Plugin' or 'Theme' */ $fs->get_text_inline( 'Is active, deactivated, or uninstalled', 'permissions-events_desc-paid' ), $fs->get_module_label( true ) ), sprintf( $fs->get_text_inline( 'So you can reuse the license when the %s is no longer active.', 'permissions-events_tooltip' ), $fs->get_module_label( true ) ), 20, false, true, $load_default_from_storage ); return $permissions; } /** * @return array[] */ function get_license_optional_permissions( $include_optional_label = false, $load_default_from_storage = false ) { return array( $this->get_diagnostic_permission( $include_optional_label, $load_default_from_storage ), $this->get_extensions_permission( true, $include_optional_label, $load_default_from_storage ), ); } /** * @param bool $include_optional_label * @param bool $load_default_from_storage * * @return array */ function get_diagnostic_permission( $include_optional_label = false, $load_default_from_storage = false ) { return $this->get_permission( self::PERMISSION_DIAGNOSTIC, 'wordpress-alt', $this->_fs->get_text_inline( 'View Diagnostic Info', 'permissions-diagnostic' ) . ( $include_optional_label ? ' (' . $this->_fs->get_text_inline( 'optional' ) . ')' : '' ), $this->_fs->get_text_inline( 'WordPress & PHP versions, site language & title', 'permissions-diagnostic_desc' ), sprintf( /* translators: %s: 'Plugin' or 'Theme' */ $this->_fs->get_text_inline( 'To avoid breaking your website due to WordPress or PHP version incompatibilities, and recognize which languages & regions the %s should be translated and tailored to.', 'permissions-diagnostic_tooltip' ), $this->_fs->get_module_label( true ) ), 25, true, true, $load_default_from_storage ); } #endregion #-------------------------------------------------------------------------------- #region Common Permissions #-------------------------------------------------------------------------------- /** * @param bool $is_license_activation * @param bool $include_optional_label * @param bool $load_default_from_storage * * @return array */ function get_extensions_permission( $is_license_activation, $include_optional_label = false, $load_default_from_storage = false ) { $is_on_by_default = ! $is_license_activation; return $this->get_permission( self::PERMISSION_EXTENSIONS, 'block-default', $this->_fs->get_text_inline( 'View Plugins & Themes List', 'permissions-extensions' ) . ( $is_license_activation ? ( $include_optional_label ? ' (' . $this->_fs->get_text_inline( 'optional' ) . ')' : '' ) : '' ), $this->_fs->get_text_inline( 'Names, slugs, versions, and if active or not', 'permissions-extensions_desc' ), $this->_fs->get_text_inline( 'To ensure compatibility and avoid conflicts with your installed plugins and themes.', 'permissions-events_tooltip' ), 25, true, $is_on_by_default, $load_default_from_storage ); } /** * @param bool $load_default_from_storage * * @return array */ function get_user_permission( $load_default_from_storage = false ) { return $this->get_permission( self::PERMISSION_USER, 'admin-users', $this->_fs->get_text_inline( 'View Basic Profile Info', 'permissions-profile' ), $this->_fs->get_text_inline( 'Your WordPress user\'s: first & last name, and email address', 'permissions-profile_desc' ), $this->_fs->get_text_inline( 'Never miss important updates, get security warnings before they become public knowledge, and receive notifications about special offers and awesome new features.', 'permissions-profile_tooltip' ), 5, false, true, $load_default_from_storage ); } #endregion #-------------------------------------------------------------------------------- #region Optional Permissions #-------------------------------------------------------------------------------- /** * @return array[] */ function get_newsletter_permission() { return $this->get_permission( self::PERMISSION_NEWSLETTER, 'email-alt', $this->_fs->get_text_inline( 'Newsletter', 'permissions-newsletter' ), $this->_fs->get_text_inline( 'Updates, announcements, marketing, no spam', 'permissions-newsletter_desc' ), '', 15 ); } #endregion #-------------------------------------------------------------------------------- #region Permissions Storage #-------------------------------------------------------------------------------- /** * @param int|null $blog_id * * @return bool */ function is_extensions_tracking_allowed( $blog_id = null ) { return $this->is_permission_allowed( self::PERMISSION_EXTENSIONS, ! $this->_fs->is_premium(), $blog_id ); } /** * @param int|null $blog_id * * @return bool */ function is_essentials_tracking_allowed( $blog_id = null ) { return $this->is_permission_allowed( self::PERMISSION_ESSENTIALS, true, $blog_id ); } /** * @param bool $default * * @return bool */ function is_diagnostic_tracking_allowed( $default = true ) { return $this->is_premium_context() ? $this->is_permission_allowed( self::PERMISSION_DIAGNOSTIC, $default ) : $this->is_permission_allowed( self::PERMISSION_SITE, $default ); } /** * @param int|null $blog_id * * @return bool */ function is_homepage_url_tracking_allowed( $blog_id = null ) { return $this->is_permission_allowed( $this->get_site_permission_name(), true, $blog_id ); } /** * @param int|null $blog_id * * @return bool */ function update_site_tracking( $is_enabled, $blog_id = null, $only_if_not_set = false ) { $permissions = $this->get_site_tracking_permission_names(); $result = true; foreach ( $permissions as $permission ) { if ( ! $only_if_not_set || ! $this->is_permission_set( $permission, $blog_id ) ) { $result = ( $result && $this->update_permission_tracking_flag( $permission, $is_enabled, $blog_id ) ); } } return $result; } /** * @param string $permission * @param bool $default * @param int|null $blog_id * * @return bool */ function is_permission_allowed( $permission, $default = false, $blog_id = null ) { if ( ! self::is_supported_permission( $permission ) ) { return $default; } return $this->is_permission( $permission, true, $blog_id ); } /** * @param string $permission * @param bool $is_allowed * @param int|null $blog_id * * @return bool */ function is_permission( $permission, $is_allowed, $blog_id = null ) { if ( ! self::is_supported_permission( $permission ) ) { return false; } $tag = "is_{$permission}_tracking_allowed"; return ( $is_allowed === $this->_fs->apply_filters( $tag, $this->_storage->get( $tag, $this->get_permission_default( $permission ), $blog_id, FS_Storage::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED ) ) ); } /** * @param string $permission * @param int|null $blog_id * * @return bool */ function is_permission_set( $permission, $blog_id = null ) { $tag = "is_{$permission}_tracking_allowed"; $permission = $this->_storage->get( $tag, null, $blog_id, FS_Storage::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED ); return is_bool( $permission ); } /** * @param string[] $permissions * @param bool $is_allowed * * @return bool `true` if all given permissions are in sync with `$is_allowed`. */ function are_permissions( $permissions, $is_allowed, $blog_id = null ) { foreach ( $permissions as $permission ) { if ( ! $this->is_permission( $permission, $is_allowed, $blog_id ) ) { return false; } } return true; } /** * @param string $permission * @param bool $is_enabled * @param int|null $blog_id * * @return bool `false` if permission not supported or `$is_enabled` is not a boolean. */ function update_permission_tracking_flag( $permission, $is_enabled, $blog_id = null ) { if ( is_bool( $is_enabled ) && self::is_supported_permission( $permission ) ) { $this->_storage->store( "is_{$permission}_tracking_allowed", $is_enabled, $blog_id, FS_Storage::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED ); return true; } return false; } /** * @param array $permissions */ function update_permissions_tracking_flag( $permissions ) { foreach ( $permissions as $permission => $is_enabled ) { $this->update_permission_tracking_flag( $permission, $is_enabled ); } } #endregion /** * @param string $permission * * @return bool */ function get_permission_default( $permission ) { if ( $this->_fs->is_premium() && self::PERMISSION_EXTENSIONS === $permission ) { return false; } // All permissions except for the extensions in paid version are on by default when the user opts in to usage tracking. return true; } /** * @return string */ function get_site_permission_name() { return $this->is_premium_context() ? self::PERMISSION_ESSENTIALS : self::PERMISSION_SITE; } /** * @return string[] */ function get_site_tracking_permission_names() { return $this->is_premium_context() ? array( FS_Permission_Manager::PERMISSION_ESSENTIALS, FS_Permission_Manager::PERMISSION_EVENTS, ) : array( FS_Permission_Manager::PERMISSION_SITE ); } #-------------------------------------------------------------------------------- #region Rendering #-------------------------------------------------------------------------------- /** * @param array $permission */ function render_permission( array $permission ) { fs_require_template( 'connect/permission.php', $permission ); } /** * @param array $permissions_group */ function render_permissions_group( array $permissions_group ) { $permissions_group[ 'fs' ] = $this->_fs; fs_require_template( 'connect/permissions-group.php', $permissions_group ); } function require_permissions_js() { fs_require_once_template( 'js/permissions.php', $params ); } #endregion #-------------------------------------------------------------------------------- #region Helper Methods #-------------------------------------------------------------------------------- /** * @param string $id * @param string $dashicon * @param string $label * @param string $desc * @param string $tooltip * @param int $priority * @param bool $is_optional * @param bool $is_on_by_default * @param bool $load_from_storage * * @return array */ private function get_permission( $id, $dashicon, $label, $desc, $tooltip = '', $priority = 10, $is_optional = false, $is_on_by_default = true, $load_from_storage = false ) { $is_on = $load_from_storage ? $this->is_permission_allowed( $id, $is_on_by_default ) : $is_on_by_default; return array( 'id' => $id, 'icon-class' => $this->_fs->apply_filters( "permission_{$id}_icon", "dashicons dashicons-{$dashicon}" ), 'label' => $this->_fs->apply_filters( "permission_{$id}_label", $label ), 'tooltip' => $this->_fs->apply_filters( "permission_{$id}_tooltip", $tooltip ), 'desc' => $this->_fs->apply_filters( "permission_{$id}_desc", $desc ), 'priority' => $this->_fs->apply_filters( "permission_{$id}_priority", $priority ), 'optional' => $is_optional, 'default' => $this->_fs->apply_filters( "permission_{$id}_default", $is_on ), ); } /** * @param array $permissions * * @return array[] */ private function get_sorted_permissions_by_priority( array $permissions ) { // Allow filtering of the permissions list. $permissions = $this->_fs->apply_filters( 'permission_list', $permissions ); // Sort by priority. uasort( $permissions, 'fs_sort_by_priority' ); return $permissions; } #endregion }PK!*>y`?libraries/freemius/includes/managers/class-fs-clone-manager.phpnu[_storage = FS_Option_Manager::get_manager( WP_FS___OPTION_PREFIX . self::OPTION_MANAGER_NAME, true ); $this->_network_storage = FS_Option_Manager::get_manager( WP_FS___OPTION_PREFIX . self::OPTION_MANAGER_NAME, true, true ); $this->maybe_migrate_options(); $this->_notices = FS_Admin_Notices::instance( 'global_clone_resolution_notices', '', '', true ); $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . '_clone_manager', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); } /** * Migrate clone resolution options from 2.5.0 array-based structure, to a new flat structure. * * The reason this logic is not in a separate migration script is that we want to be 100% sure data is migrated before any execution of clone logic. * * @todo Delete this one in the future. */ private function maybe_migrate_options() { $storages = array( $this->_storage, $this->_network_storage ); foreach ( $storages as $storage ) { $clone_data = $storage->get_option( self::OPTION_NAME ); if ( is_array( $clone_data ) && ! empty( $clone_data ) ) { foreach ( $clone_data as $key => $val ) { if ( ! is_null( $val ) ) { $storage->set_option( $key, $val ); } } $storage->unset_option( self::OPTION_NAME, true ); } } } /** * @author Leo Fajardo (@leorw) * @since 2.5.0 */ function _init() { if ( is_admin() ) { if ( Freemius::is_admin_post() ) { add_action( 'admin_post_fs_clone_resolution', array( $this, '_handle_clone_resolution' ) ); } if ( Freemius::is_ajax() ) { Freemius::add_ajax_action_static( 'handle_clone_resolution', array( $this, '_clone_resolution_action_ajax_handler' ) ); } else { if ( empty( $this->get_clone_identification_timestamp() ) && ( ! fs_is_network_admin() || ! ( $this->is_clone_resolution_options_notice_shown() || $this->is_temporary_duplicate_notice_shown() ) ) ) { $this->hide_clone_admin_notices(); } else if ( ! Freemius::is_cron() && ! Freemius::is_admin_post() ) { $this->try_resolve_clone_automatically(); $this->maybe_show_clone_admin_notice(); add_action( 'admin_footer', array( $this, '_add_clone_resolution_javascript' ) ); } } } } /** * Retrieves the timestamp that was stored when a clone was identified. * * @return int|null */ function get_clone_identification_timestamp() { return $this->get_option( 'clone_identification_timestamp', true ); } /** * @author Leo Fajardo (@leorw) * @since 2.5.1 * * @param string $sdk_last_version */ function maybe_update_clone_resolution_support_flag( $sdk_last_version ) { if ( null !== $this->hide_manual_resolution ) { return; } $this->hide_manual_resolution = ( ! empty( $sdk_last_version ) && version_compare( $sdk_last_version, '2.5.0', '<' ) ); } /** * Stores the time when a clone was identified. */ function store_clone_identification_timestamp() { $this->clone_identification_timestamp = time(); } /** * Retrieves the timestamp for the temporary duplicate mode's expiration. * * @return int */ function get_temporary_duplicate_expiration_timestamp() { $temporary_duplicate_mode_start_timestamp = $this->was_temporary_duplicate_mode_selected() ? $this->temporary_duplicate_mode_selection_timestamp : $this->get_clone_identification_timestamp(); return ( $temporary_duplicate_mode_start_timestamp + self::TEMPORARY_DUPLICATE_PERIOD ); } /** * Determines if the SDK should handle clones. The SDK handles clones only up to 3 times with 3 min interval. * * @return bool */ private function should_handle_clones() { if ( ! isset( $this->request_handler_timestamp ) ) { return true; } if ( $this->request_handler_retries_count >= self::CLONE_RESOLUTION_MAX_RETRIES ) { return false; } // Give the logic that handles clones enough time to finish (it is given 3 minutes for now). return ( time() > ( $this->request_handler_timestamp + self::CLONE_RESOLUTION_MAX_EXECUTION_TIME ) ); } /** * @author Leo Fajardo (@leorw) * @since 2.5.1 * * @return bool */ function should_hide_manual_resolution() { return ( true === $this->hide_manual_resolution ); } /** * Executes the clones handler logic if it should be executed, i.e., based on the return value of the should_handle_clones() method. * * @author Leo Fajardo (@leorw) * @since 2.5.0 */ function maybe_run_clone_resolution() { if ( ! $this->should_handle_clones() ) { return; } $this->request_handler_retries_count = isset( $this->request_handler_retries_count ) ? ( $this->request_handler_retries_count + 1 ) : 1; $this->request_handler_timestamp = time(); $handler_id = ( rand() . microtime() ); $this->request_handler_id = $handler_id; // Add cookies to trigger request with the same user access permissions. $cookies = array(); foreach ( $_COOKIE as $name => $value ) { $cookies[] = new WP_Http_Cookie( array( 'name' => $name, 'value' => $value, ) ); } wp_remote_post( admin_url( 'admin-post.php' ), array( 'method' => 'POST', 'body' => array( 'action' => 'fs_clone_resolution', 'handler_id' => $handler_id, ), 'timeout' => 0.01, 'blocking' => false, 'sslverify' => false, 'cookies' => $cookies, ) ); } /** * Executes the clones handler logic. * * @author Leo Fajardo (@leorw) * @since 2.5.0 */ function _handle_clone_resolution() { $handler_id = fs_request_get( 'handler_id' ); if ( empty( $handler_id ) ) { return; } if ( ! isset( $this->request_handler_id ) || $this->request_handler_id !== $handler_id ) { return; } if ( ! $this->try_automatic_resolution() ) { $this->clear_temporary_duplicate_notice_shown_timestamp(); } } #-------------------------------------------------------------------------------- #region Automatic Clone Resolution #-------------------------------------------------------------------------------- /** * @var array All installs cache. */ private $all_installs; /** * Checks if a given instance's install is a clone of another subsite in the network. * * @author Vova Feldman (@svovaf) * * @return FS_Site */ private function find_network_subsite_clone_install( Freemius $instance ) { if ( ! is_multisite() ) { // Not a multi-site network. return null; } if ( ! isset( $this->all_installs ) ) { $this->all_installs = FS_DebugManager::get_all_modules_sites(); } // Check if there's another blog that has the same site. $module_type = $instance->get_module_type(); $sites_by_module_type = ! empty( $this->all_installs[ $module_type ] ) ? $this->all_installs[ $module_type ] : array(); $slug = $instance->get_slug(); $sites_by_slug = ! empty( $sites_by_module_type[ $slug ] ) ? $sites_by_module_type[ $slug ] : array(); $current_blog_id = get_current_blog_id(); $current_install = $instance->get_site(); foreach ( $sites_by_slug as $site ) { if ( $current_install->id == $site->id && $current_blog_id != $site->blog_id ) { // Clone is identical to an install on another subsite in the network. return $site; } } return null; } /** * Tries to find a different install of the context product that is associated with the current URL and loads it. * * @author Leo Fajardo (@leorw) * @since 2.5.0 * * @param Freemius $instance * @param string $url * * @return object */ private function find_other_install_by_url( Freemius $instance, $url ) { $result = $instance->get_api_user_scope()->get( "/plugins/{$instance->get_id()}/installs.json?url=" . urlencode( $url ) . "&all=true", true ); $current_install = $instance->get_site(); if ( $instance->is_api_result_object( $result, 'installs' ) ) { foreach ( $result->installs as $install ) { if ( $install->id == $current_install->id ) { continue; } if ( $instance->is_only_premium() && ! FS_Plugin_License::is_valid_id( $install->license_id ) ) { continue; } // When searching for installs by a URL, the API will first strip any paths and search for any matching installs by the subdomain. Therefore, we need to test if there's a match between the current URL and the install's URL before continuing. if ( $url !== fs_strip_url_protocol( untrailingslashit( $install->url ) ) ) { continue; } // Found a different install that is associated with the current URL, load it and replace the current install with it if no updated install is found. return $install; } } return null; } /** * Delete the current install associated with a given instance and opt-in/activate-license to create a fresh install. * * @author Vova Feldman (@svovaf) * @since 2.5.0 * * @param Freemius $instance * @param string|false $license_key * * @return bool TRUE if successfully connected. FALSE if failed and had to restore install from backup. */ private function delete_install_and_connect( Freemius $instance, $license_key = false ) { $user = Freemius::_get_user_by_id( $instance->get_site()->user_id ); $instance->delete_current_install( true ); if ( ! is_object( $user ) ) { // Get logged-in WordPress user. $current_user = Freemius::_get_current_wp_user(); // Find the relevant FS user by email address. $user = Freemius::_get_user_by_email( $current_user->user_email ); } if ( is_object( $user ) ) { // When a clone is found, we prefer to use the same user of the original install for the opt-in. $instance->install_with_user( $user, $license_key, false, false ); } else { // If no user is found, activate with the license. $instance->opt_in( false, false, false, $license_key ); } if ( is_object( $instance->get_site() ) ) { // Install successfully created. return true; } // Restore from backup. $instance->restore_backup_site(); return false; } /** * Try to resolve the clone situation automatically. * * @param Freemius $instance * @param string $current_url * @param bool $is_localhost * @param bool|null $is_clone_of_network_subsite * * @return bool If managed to automatically resolve the clone. */ private function try_resolve_clone_automatically_by_instance( Freemius $instance, $current_url, $is_localhost, $is_clone_of_network_subsite = null ) { // Try to find a different install of the context product that is associated with the current URL. $associated_install = $this->find_other_install_by_url( $instance, $current_url ); if ( is_object( $associated_install ) ) { // Replace the current install with a different install that is associated with the current URL. $instance->store_site( new FS_Site( clone $associated_install ) ); $instance->sync_install( array( 'is_new_site' => true ), true ); return true; } if ( ! $instance->is_premium() ) { // For free products, opt-in with the context user to create new install. return $this->delete_install_and_connect( $instance ); } $license = $instance->_get_license(); $can_activate_license = ( is_object( $license ) && ! $license->is_utilized( $is_localhost ) ); if ( ! $can_activate_license ) { // License can't be activated, therefore, can't be automatically resolved. return false; } if ( ! WP_FS__IS_LOCALHOST_FOR_SERVER && ! $is_localhost ) { $is_clone_of_network_subsite = ( ! is_null( $is_clone_of_network_subsite ) ) ? $is_clone_of_network_subsite : is_object( $this->find_network_subsite_clone_install( $instance ) ); if ( ! $is_clone_of_network_subsite ) { return false; } } // If the site is a clone of another subsite in the network, or a localhost one, try to auto activate the license. return $this->delete_install_and_connect( $instance, $license->secret_key ); } /** * @author Leo Fajardo (@leorw) * @since 2.5.0 */ private function try_resolve_clone_automatically() { $clone_action = $this->get_clone_resolution_action_from_config(); if ( ! empty( $clone_action ) ) { $this->try_resolve_clone_automatically_by_config( $clone_action ); return; } $this->try_automatic_resolution(); } /** * Tries to resolve the clone situation automatically based on the config in the wp-config.php file. * * @author Leo Fajardo (@leorw) * @since 2.5.0 * * @param string $clone_action */ private function try_resolve_clone_automatically_by_config( $clone_action ) { $fs_instances = array(); if ( self::OPTION_LONG_TERM_DUPLICATE === $clone_action ) { $instances = Freemius::_get_all_instances(); foreach ( $instances as $instance ) { if ( ! $instance->is_registered() ) { continue; } if ( ! $instance->is_clone() ) { continue; } $license = $instance->has_features_enabled_license() ? $instance->_get_license() : null; if ( is_object( $license ) && ! $license->is_utilized( ( WP_FS__IS_LOCALHOST_FOR_SERVER || FS_Site::is_localhost_by_address( Freemius::get_unfiltered_site_url() ) ) ) ) { $fs_instances[] = $instance; } } if ( empty( $fs_instances ) ) { return; } } $this->resolve_cloned_sites( $clone_action, $fs_instances ); } /** * @author Leo Fajard (@leorw) * @since 2.5.0 * * @return string|null */ private function get_clone_resolution_action_from_config() { if ( ! defined( 'FS__RESOLVE_CLONE_AS' ) ) { return null; } if ( ! in_array( FS__RESOLVE_CLONE_AS, array( self::OPTION_NEW_HOME, self::OPTION_TEMPORARY_DUPLICATE, self::OPTION_LONG_TERM_DUPLICATE, ) ) ) { return null; } return FS__RESOLVE_CLONE_AS; } /** * Tries to recover the install of a newly created subsite or resolve it if it's a clone. * * @author Leo Fajardo (@leorw) * @since 2.5.0 * * @param Freemius $instance */ function maybe_resolve_new_subsite_install_automatically( Freemius $instance ) { if ( ! $instance->is_user_in_admin() ) { // Try to recover an install or resolve a clone only when there's a user in admin to prevent doing it prematurely (e.g., the install can get replaced with clone data again). return; } if ( ! is_multisite() ) { return; } $new_blog_install_map = $this->new_blog_install_map; if ( empty( $new_blog_install_map ) || ! is_array( $new_blog_install_map ) ) { return; } $is_network_admin = fs_is_network_admin(); if ( ! $is_network_admin ) { // If not in network admin, handle the current site. $blog_id = get_current_blog_id(); } else { // If in network admin, handle only the first site. $blog_ids = array_keys( $new_blog_install_map ); $blog_id = $blog_ids[0]; } if ( ! isset( $new_blog_install_map[ $blog_id ] ) ) { // There's no site to handle. return; } $expected_install_id = $new_blog_install_map[ $blog_id ]['install_id']; $current_install = $instance->get_install_by_blog_id( $blog_id ); $current_install_id = is_object( $current_install ) ? $current_install->id : null; if ( $expected_install_id == $current_install_id ) { // Remove the current site's information from the map to prevent handling it again. $this->remove_new_blog_install_info_from_storage( $blog_id ); return; } require_once WP_FS__DIR_INCLUDES . '/class-fs-lock.php'; $lock = new FS_Lock( self::OPTION_NAME . '_subsite' ); if ( ! $lock->try_lock(60) ) { return; } $instance->switch_to_blog( $blog_id ); $current_url = untrailingslashit( Freemius::get_unfiltered_site_url( null, true ) ); $current_install_url = is_object( $current_install ) ? fs_strip_url_protocol( untrailingslashit( $current_install->url ) ) : null; // This can be `false` even if the install is a clone as the URL can be updated as part of the cloning process. $is_clone = ( ! is_null( $current_install_url ) && $current_url !== $current_install_url ); if ( ! FS_Site::is_valid_id( $expected_install_id ) ) { $expected_install = null; } else { $expected_install = $instance->fetch_install_by_id( $expected_install_id ); } if ( FS_Api::is_api_result_entity( $expected_install ) ) { // Replace the current install with the expected install. $instance->store_site( new FS_Site( clone $expected_install ) ); $instance->sync_install( array( 'is_new_site' => true ), true ); } else { $network_subsite_clone_install = null; if ( ! $is_clone ) { // It is possible that `$is_clone` is `false` but the install is actually a clone as the following call checks the install ID and not the URL. $network_subsite_clone_install = $this->find_network_subsite_clone_install( $instance ); } if ( $is_clone || is_object( $network_subsite_clone_install ) ) { // If there's no expected install (or it couldn't be fetched) and the current install is a clone, try to resolve the clone automatically. $is_localhost = FS_Site::is_localhost_by_address( $current_url ); $resolved = $this->try_resolve_clone_automatically_by_instance( $instance, $current_url, $is_localhost, is_object( $network_subsite_clone_install ) ); if ( ! $resolved && is_object( $network_subsite_clone_install ) ) { if ( empty( $this->get_clone_identification_timestamp() ) ) { $this->store_clone_identification_timestamp(); } // Since the clone couldn't be identified based on the URL, replace the stored install with the cloned install so that the manual clone resolution notice will appear. $instance->store_site( clone $network_subsite_clone_install ); } } } $instance->restore_current_blog(); // Remove the current site's information from the map to prevent handling it again. $this->remove_new_blog_install_info_from_storage( $blog_id ); $lock->unlock(); } /** * If a new install was created after creating a new subsite, its ID is stored in the blog-install map so that it can be recovered in case it's replaced with a clone install (e.g., when the newly created subsite is a clone). The IDs of the clone subsites that were created while not running this version of the SDK or a higher version will also be stored in the said map so that the clone manager can also try to resolve them later on. * * @author Leo Fajardo (@leorw) * @since 2.5.0 * * @param int $blog_id * @param FS_Site $site */ function store_blog_install_info( $blog_id, $site = null ) { $new_blog_install_map = $this->new_blog_install_map; if ( empty( $new_blog_install_map ) || ! is_array( $new_blog_install_map ) ) { $new_blog_install_map = array(); } $install_id = null; if ( is_object( $site ) ) { $install_id = $site->id; } $new_blog_install_map[ $blog_id ] = array( 'install_id' => $install_id ); $this->new_blog_install_map = $new_blog_install_map; } /** * @author Leo Fajardo (@leorw) * @since 2.5.0 * * @param int $blog_id */ private function remove_new_blog_install_info_from_storage( $blog_id ) { $new_blog_install_map = $this->new_blog_install_map; unset( $new_blog_install_map[ $blog_id ] ); $this->new_blog_install_map = $new_blog_install_map; } /** * Tries to resolve all clones automatically. * * @author Leo Fajardo (@leorw) * @since 2.5.0 * * @return bool If managed to automatically resolve all clones. */ private function try_automatic_resolution() { $this->_logger->entrance(); require_once WP_FS__DIR_INCLUDES . '/class-fs-lock.php'; $lock = new FS_Lock( self::OPTION_NAME ); /** * Try to acquire lock for the next 60 sec based on the thread ID. */ if ( ! $lock->try_lock( 60 ) ) { return false; } $current_url = untrailingslashit( Freemius::get_unfiltered_site_url( null, true ) ); $is_localhost = FS_Site::is_localhost_by_address( $current_url ); $require_manual_resolution = false; $instances = Freemius::_get_all_instances(); foreach ( $instances as $instance ) { if ( ! $instance->is_registered() ) { continue; } if ( ! $instance->is_clone() ) { continue; } if ( ! $this->try_resolve_clone_automatically_by_instance( $instance, $current_url, $is_localhost ) ) { $require_manual_resolution = true; } } // Create a 1-day lock. $lock->lock( WP_FS__TIME_24_HOURS_IN_SEC ); return ( ! $require_manual_resolution ); } #endregion #-------------------------------------------------------------------------------- #region Manual Clone Resolution #-------------------------------------------------------------------------------- /** * @author Leo Fajardo (@leorw) * @since 2.5.0 */ function _add_clone_resolution_javascript() { $vars = array( 'ajax_action' => Freemius::get_ajax_action_static( 'handle_clone_resolution' ) ); fs_require_once_template( 'clone-resolution-js.php', $vars ); } /** * @author Leo Fajardo (@leorw) * @since 2.5.0 */ function _clone_resolution_action_ajax_handler() { $this->_logger->entrance(); check_ajax_referer( Freemius::get_ajax_action_static( 'handle_clone_resolution' ), 'security' ); $clone_action = fs_request_get( 'clone_action' ); $blog_id = is_multisite() ? fs_request_get( 'blog_id' ) : 0; if ( is_multisite() && $blog_id == get_current_blog_id() ) { $blog_id = 0; } if ( empty( $clone_action ) ) { Freemius::shoot_ajax_failure( array( 'message' => fs_text_inline( 'Invalid clone resolution action.', 'invalid-clone-resolution-action-error' ), 'redirect_url' => '', ) ); } $result = $this->resolve_cloned_sites( $clone_action, array(), $blog_id ); Freemius::shoot_ajax_success( $result ); } /** * @author Leo Fajardo (@leorw) * @since 2.5.0 * * @param string $clone_action * @param Freemius[] $fs_instances * @param int $blog_id * * @return array */ private function resolve_cloned_sites( $clone_action, $fs_instances = array(), $blog_id = 0 ) { $this->_logger->entrance(); $result = array(); $instances_with_clone = array(); $instances_with_clone_count = 0; $install_by_instance_id = array(); $instances = ( ! empty( $fs_instances ) ) ? $fs_instances : Freemius::_get_all_instances(); $should_switch_to_blog = ( $blog_id > 0 ); foreach ( $instances as $instance ) { if ( $should_switch_to_blog ) { $instance->switch_to_blog( $blog_id ); } if ( $instance->is_registered() && $instance->is_clone() ) { $instances_with_clone[] = $instance; $instances_with_clone_count ++; $install_by_instance_id[ $instance->get_id() ] = $instance->get_site(); } } if ( self::OPTION_TEMPORARY_DUPLICATE === $clone_action ) { $this->store_temporary_duplicate_timestamp(); } else { $redirect_url = ''; foreach ( $instances_with_clone as $instance ) { if ( $should_switch_to_blog ) { $instance->switch_to_blog( $blog_id ); } $has_error = false; if ( self::OPTION_NEW_HOME === $clone_action ) { $instance->sync_install( array( 'is_new_site' => true ), true ); if ( $instance->is_clone() ) { $has_error = true; } } else { $instance->_handle_long_term_duplicate(); if ( ! is_object( $instance->get_site() ) ) { $has_error = true; } } if ( $has_error && 1 === $instances_with_clone_count ) { $redirect_url = $instance->get_activation_url(); } } $result = ( array( 'redirect_url' => $redirect_url ) ); } foreach ( $instances_with_clone as $instance ) { if ( $should_switch_to_blog ) { $instance->switch_to_blog( $blog_id ); } // No longer a clone, send an update. if ( ! $instance->is_clone() ) { $instance->send_clone_resolution_update( $clone_action, $install_by_instance_id[ $instance->get_id() ] ); } } if ( 'temporary_duplicate_license_activation' !== $clone_action ) { $this->remove_clone_resolution_options_notice(); } else { $this->remove_temporary_duplicate_notice(); } if ( $should_switch_to_blog ) { foreach ( $instances as $instance ) { $instance->restore_current_blog(); } } return $result; } /** * @author Leo Fajardo (@leorw) * @since 2.5.0 */ private function hide_clone_admin_notices() { $this->remove_clone_resolution_options_notice( false ); $this->remove_temporary_duplicate_notice( false ); } /** * @author Leo Fajardo (@leorw) * @since 2.5.0 */ function maybe_show_clone_admin_notice() { $this->_logger->entrance(); if ( fs_is_network_admin() ) { $existing_notice_ids = $this->maybe_remove_notices(); if ( ! empty( $existing_notice_ids ) ) { fs_enqueue_local_style( 'fs_clone_resolution_notice', '/admin/clone-resolution.css' ); } return; } $first_instance_with_clone = null; $site_urls = array(); $sites_with_license_urls = array(); $sites_with_premium_version_count = 0; $product_ids = array(); $product_titles = array(); $instances = Freemius::_get_all_instances(); foreach ( $instances as $instance ) { if ( ! $instance->is_registered() ) { continue; } if ( ! $instance->is_clone( true ) ) { continue; } $install = $instance->get_site(); $site_urls[] = $install->url; $product_ids[] = $instance->get_id(); $product_titles[] = $instance->get_plugin_title(); if ( is_null( $first_instance_with_clone ) ) { $first_instance_with_clone = $instance; } if ( is_object( $instance->_get_license() ) ) { $sites_with_license_urls[] = $install->url; } if ( $instance->is_premium() ) { $sites_with_premium_version_count ++; } } if ( empty( $site_urls ) && empty( $sites_with_license_urls ) ) { $this->hide_clone_admin_notices(); return; } $site_urls = array_unique( $site_urls ); $sites_with_license_urls = array_unique( $sites_with_license_urls ); $module_label = fs_text_inline( 'products', 'products' ); $admin_notice_module_title = null; $has_temporary_duplicate_mode_expired = $this->has_temporary_duplicate_mode_expired(); if ( ! $this->was_temporary_duplicate_mode_selected() || $has_temporary_duplicate_mode_expired ) { if ( ! empty( $site_urls ) ) { fs_enqueue_local_style( 'fs_clone_resolution_notice', '/admin/clone-resolution.css' ); $doc_url = 'https://freemius.com/help/documentation/wordpress-sdk/safe-mode-clone-resolution-duplicate-website/'; if ( 1 === count( $instances ) ) { $doc_url = fs_apply_filter( $first_instance_with_clone->get_unique_affix(), 'clone_resolution_documentation_url', $doc_url ); } $this->add_manual_clone_resolution_admin_notice( $product_ids, $product_titles, $site_urls, Freemius::get_unfiltered_site_url(), ( count( $site_urls ) === count( $sites_with_license_urls ) ), ( count( $site_urls ) === $sites_with_premium_version_count ), $doc_url ); } return; } if ( empty( $sites_with_license_urls ) ) { return; } if ( ! $this->is_temporary_duplicate_notice_shown() ) { $last_time_temporary_duplicate_notice_shown = $this->temporary_duplicate_notice_shown_timestamp; $was_temporary_duplicate_notice_shown_before = is_numeric( $last_time_temporary_duplicate_notice_shown ); if ( $was_temporary_duplicate_notice_shown_before ) { $temporary_duplicate_mode_expiration_timestamp = $this->get_temporary_duplicate_expiration_timestamp(); $current_time = time(); if ( $current_time > $temporary_duplicate_mode_expiration_timestamp || $current_time < ( $temporary_duplicate_mode_expiration_timestamp - ( 2 * WP_FS__TIME_24_HOURS_IN_SEC ) ) ) { // Do not show the notice if the temporary duplicate mode has already expired or it will expire more than 2 days from now. return; } } } if ( 1 === count( $sites_with_license_urls ) ) { $module_label = $first_instance_with_clone->get_module_label( true ); $admin_notice_module_title = $first_instance_with_clone->get_plugin_title(); } fs_enqueue_local_style( 'fs_clone_resolution_notice', '/admin/clone-resolution.css' ); $this->add_temporary_duplicate_sticky_notice( $product_ids, $this->get_temporary_duplicate_admin_notice_string( $sites_with_license_urls, $product_titles, $module_label ), $admin_notice_module_title ); } /** * Removes the notices from the storage if the context product is either no longer active on the context subsite or it's active but there's no longer any clone. This prevents the notices from being shown on the network-level admin page when they are no longer relevant. * * @author Leo Fajardo (@leorw) * @since 2.5.1 * * @return string[] */ private function maybe_remove_notices() { $notices = array( 'clone_resolution_options_notice' => $this->_notices->get_sticky( 'clone_resolution_options_notice', true ), 'temporary_duplicate_notice' => $this->_notices->get_sticky( 'temporary_duplicate_notice', true ), ); $instances = Freemius::_get_all_instances(); foreach ( $notices as $id => $notice ) { if ( ! is_array( $notice ) ) { unset( $notices[ $id ] ); continue; } if ( empty( $notice['data'] ) || ! is_array( $notice['data'] ) ) { continue; } if ( empty( $notice['data']['product_ids'] ) || empty( $notice['data']['blog_id'] ) ) { continue; } $product_ids = $notice['data']['product_ids']; $blog_id = $notice['data']['blog_id']; $has_clone = false; if ( ! is_null( get_site( $blog_id ) ) ) { foreach ( $product_ids as $product_id ) { if ( ! isset( $instances[ 'm_' . $product_id ] ) ) { continue; } $instance = $instances[ 'm_' . $product_id ]; $plugin_basename = $instance->get_plugin_basename(); $is_plugin_active = is_plugin_active_for_network( $plugin_basename ); if ( ! $is_plugin_active ) { switch_to_blog( $blog_id ); $is_plugin_active = is_plugin_active( $plugin_basename ); restore_current_blog(); } if ( ! $is_plugin_active ) { continue; } $install = $instance->get_install_by_blog_id( $blog_id ); if ( ! is_object( $install ) ) { continue; } $subsite_url = Freemius::get_unfiltered_site_url( $blog_id, true, true ); $has_clone = ( fs_strip_url_protocol( trailingslashit( $install->url ) ) !== $subsite_url ); } } if ( ! $has_clone ) { $this->_notices->remove_sticky( $id, true, false ); unset( $notices[ $id ] ); } } return array_keys( $notices ); } /** * Adds a notice that provides the logged-in WordPress user with manual clone resolution options. * * @param number[] $product_ids * @param string[] $site_urls * @param string $current_url * @param bool $has_license * @param bool $is_premium * @param string $doc_url */ private function add_manual_clone_resolution_admin_notice( $product_ids, $product_titles, $site_urls, $current_url, $has_license, $is_premium, $doc_url ) { $this->_logger->entrance(); $total_sites = count( $site_urls ); $sites_list = ''; $total_products = count( $product_titles ); $products_list = ''; if ( 1 === $total_products ) { $notice_header = sprintf( '

    %s

    ', fs_esc_html_inline( '%1$s has been placed into safe mode because we noticed that %2$s is an exact copy of %3$s.', 'single-cloned-site-safe-mode-message' ) ); } else { $notice_header = sprintf( '

    %s

    ', ( 1 === $total_sites ) ? fs_esc_html_inline( 'The products below have been placed into safe mode because we noticed that %2$s is an exact copy of %3$s:%1$s', 'multiple-products-cloned-site-safe-mode-message' ) : fs_esc_html_inline( 'The products below have been placed into safe mode because we noticed that %2$s is an exact copy of these sites:%3$s%1$s', 'multiple-products-multiple-cloned-sites-safe-mode-message' ) ); foreach ( $product_titles as $product_title ) { $products_list .= sprintf( '
  • %s
  • ', $product_title ); } $products_list = '
      ' . $products_list . '
    '; foreach ( $site_urls as $site_url ) { $sites_list .= sprintf( '
  • %s
  • ', $site_url, fs_strip_url_protocol( $site_url ) ); } $sites_list = '
      ' . $sites_list . '
    '; } $remote_site_link = '' . (1 === $total_sites ? sprintf( '%s', $site_urls[0], fs_strip_url_protocol( $site_urls[0] ) ) : fs_text_inline( 'the above-mentioned sites', 'above-mentioned-sites' )) . ''; $current_site_link = sprintf( '%s', $current_url, fs_strip_url_protocol( $current_url ) ); $button_template = ''; $option_template = '
    %s

    %s

    %s
    '; $duplicate_option = sprintf( $option_template, fs_esc_html_inline( 'Is %2$s a duplicate of %4$s?', 'duplicate-site-confirmation-message' ), fs_esc_html_inline( 'Yes, %2$s is a duplicate of %4$s for the purpose of testing, staging, or development.', 'duplicate-site-message' ), ( $this->has_temporary_duplicate_mode_expired() ? sprintf( $button_template, 'long_term_duplicate', fs_text_inline( 'Long-Term Duplicate', 'long-term-duplicate' ) ) : sprintf( $button_template, 'temporary_duplicate', fs_text_inline( 'Duplicate Website', 'duplicate-site' ) ) ) ); $migration_option = sprintf( $option_template, fs_esc_html_inline( 'Is %2$s the new home of %4$s?', 'migrate-site-confirmation-message' ), sprintf( fs_esc_html_inline( 'Yes, %%2$s is replacing %%4$s. I would like to migrate my %s from %%4$s to %%2$s.', 'migrate-site-message' ), ( $has_license ? fs_text_inline( 'license', 'license' ) : fs_text_inline( 'data', 'data' ) ) ), sprintf( $button_template, 'new_home', $has_license ? fs_text_inline( 'Migrate License', 'migrate-product-license' ) : fs_text_inline( 'Migrate', 'migrate-product-data' ) ) ); $new_website = sprintf( $option_template, fs_esc_html_inline( 'Is %2$s a new website?', 'new-site-confirmation-message' ), fs_esc_html_inline( 'Yes, %2$s is a new and different website that is separate from %4$s.', 'new-site-message' ) . ($is_premium ? ' ' . fs_text_inline( 'It requires license activation.', 'new-site-requires-license-activation-message' ) : '' ), sprintf( $button_template, 'new_website', ( ! $is_premium || ! $has_license ) ? fs_text_inline( 'New Website', 'new-website' ) : fs_text_inline( 'Activate License', 'activate-license' ) ) ); $blog_id = get_current_blog_id(); /** * %1$s - single product's title or product titles list. * %2$s - site's URL. * %3$s - single install's URL or install URLs list. * %4$s - Clone site's link or "the above-mentioned sites" if there are multiple clone sites. */ $message = sprintf( $notice_header . '
    ' . $duplicate_option . $migration_option . $new_website . '
    ' . sprintf( '
    Unsure what to do? Read more here.
    ', $doc_url ), // %1$s ( 1 === $total_products ? sprintf( '%s', $product_titles[0] ) : ( 1 === $total_sites ? sprintf( '
    %s
    ', $products_list ) : sprintf( '

    %s:

    %s
    ', fs_esc_html_x_inline( 'Products', 'Clone resolution admin notice products list label', 'products' ), $products_list ) ) ), // %2$s $current_site_link, // %3$s ( 1 === $total_sites ? $remote_site_link : $sites_list ), // %4$s $remote_site_link ); $this->_notices->add_sticky( $message, 'clone_resolution_options_notice', '', 'warn', true, null, null, true, // Intentionally not dismissible. false, array( 'product_ids' => $product_ids, 'blog_id' => $blog_id ) ); } #endregion #-------------------------------------------------------------------------------- #region Temporary Duplicate (Short Term) #-------------------------------------------------------------------------------- /** * @author Leo Fajardo (@leorw) * @since 2.5.0 * * @return string */ private function get_temporary_duplicate_admin_notice_string( $site_urls, $product_titles, $module_label ) { $this->_logger->entrance(); $temporary_duplicate_end_date = $this->get_temporary_duplicate_expiration_timestamp(); $temporary_duplicate_end_date = date( 'M j, Y', $temporary_duplicate_end_date ); $current_url = Freemius::get_unfiltered_site_url(); $current_site_link = sprintf( '%s', $current_url, fs_strip_url_protocol( $current_url ) ); $total_sites = count( $site_urls ); $sites_list = ''; $total_products = count( $product_titles ); $products_list = ''; if ( $total_sites > 1 ) { foreach ( $site_urls as $site_url ) { $sites_list .= sprintf( '
  • %s
  • ', $site_url, fs_strip_url_protocol( $site_url ) ); } $sites_list = '
      ' . $sites_list . '
    '; } if ( $total_products > 1 ) { foreach ( $product_titles as $product_title ) { $products_list .= sprintf( '
  • %s
  • ', $product_title ); } $products_list = '
      ' . $products_list . '
    '; } return sprintf( sprintf( '
    %s
    ', ( 1 === $total_sites ? sprintf( '

    %s

    ', fs_esc_html_inline( 'You marked this website, %s, as a temporary duplicate of %s.', 'temporary-duplicate-message' ) ) : sprintf( '

    %s:

    ', fs_esc_html_inline( 'You marked this website, %s, as a temporary duplicate of these sites', 'temporary-duplicate-of-sites-message' ) ) . '%s' ) ) . '%s', $current_site_link, ( 1 === $total_sites ? sprintf( '%s', $site_urls[0], fs_strip_url_protocol( $site_urls[0] ) ) : $sites_list ), sprintf( '

    %s

    %s

    %s

    ', esc_attr( admin_url( 'admin-ajax.php?_fs_network_admin=false', 'relative' ) ), sprintf( fs_esc_html_inline( "%s automatic security & feature updates and paid functionality will keep working without interruptions until %s (or when your license expires, whatever comes first).", 'duplicate-site-confirmation-message' ), ( 1 === $total_products ? sprintf( fs_esc_html_x_inline( "The %s's", '"The ", e.g.: "The plugin"', 'the-product-x'), "{$module_label}" ) : fs_esc_html_inline( "The following products'", 'the-following-products' ) ), sprintf( '%s', $temporary_duplicate_end_date ) ), ( 1 === $total_products ? '' : sprintf( '
    %s
    ', $products_list ) ), sprintf( fs_esc_html_inline( 'If this is a long term duplicate, to keep automatic updates and paid functionality after %s, please %s.', 'duplicate-site-message' ), sprintf( '%s', $temporary_duplicate_end_date), sprintf( '%s', fs_esc_html_inline( 'activate a license here', 'activate-license-here' ) ) ) ) ); } /** * Determines if the temporary duplicate mode has already expired. * * @return bool */ function has_temporary_duplicate_mode_expired() { $temporary_duplicate_mode_start_timestamp = $this->was_temporary_duplicate_mode_selected() ? $this->get_option( 'temporary_duplicate_mode_selection_timestamp', true ) : $this->get_clone_identification_timestamp(); if ( ! is_numeric( $temporary_duplicate_mode_start_timestamp ) ) { return false; } return ( time() > ( $temporary_duplicate_mode_start_timestamp + self::TEMPORARY_DUPLICATE_PERIOD ) ); } /** * Determines if the logged-in WordPress user manually selected the temporary duplicate mode for the site. * * @return bool */ function was_temporary_duplicate_mode_selected() { return is_numeric( $this->temporary_duplicate_mode_selection_timestamp ); } /** * Stores the time when the logged-in WordPress user selected the temporary duplicate mode for the site. */ private function store_temporary_duplicate_timestamp() { $this->temporary_duplicate_mode_selection_timestamp = time(); } /** * Removes the notice that is shown when the logged-in WordPress user has selected the temporary duplicate mode for the site. * * @param bool $store */ function remove_clone_resolution_options_notice( $store = true ) { $this->_notices->remove_sticky( 'clone_resolution_options_notice', true, $store ); } /** * Removes the notice that is shown when the logged-in WordPress user has selected the temporary duplicate mode for the site. * * @param bool $store */ function remove_temporary_duplicate_notice( $store = true ) { $this->_notices->remove_sticky( 'temporary_duplicate_notice', true, $store ); } /** * Determines if the manual clone resolution options notice is currently being shown. * * @return bool */ function is_clone_resolution_options_notice_shown() { return $this->_notices->has_sticky( 'clone_resolution_options_notice', true ); } /** * Determines if the temporary duplicate notice is currently being shown. * * @return bool */ function is_temporary_duplicate_notice_shown() { return $this->_notices->has_sticky( 'temporary_duplicate_notice', true ); } /** * Determines if a site was marked as a temporary duplicate and if it's still a temporary duplicate. * * @return bool */ function is_temporary_duplicate_by_blog_id( $blog_id ) { $timestamp = $this->get_option( 'temporary_duplicate_mode_selection_timestamp', false, $blog_id ); return ( is_numeric( $timestamp ) && time() < ( $timestamp + self::TEMPORARY_DUPLICATE_PERIOD ) ); } /** * Determines the last time the temporary duplicate notice was shown. * * @return int|null */ function last_time_temporary_duplicate_notice_was_shown() { return $this->temporary_duplicate_notice_shown_timestamp; } /** * Clears the time that has been stored when the temporary duplicate notice was shown. */ function clear_temporary_duplicate_notice_shown_timestamp() { unset( $this->temporary_duplicate_notice_shown_timestamp ); } /** * Adds a temporary duplicate notice that provides the logged-in WordPress user with an option to activate a license for the site. * * @param number[] $product_ids * @param string $message * @param string|null $plugin_title */ function add_temporary_duplicate_sticky_notice( $product_ids, $message, $plugin_title = null ) { $this->_logger->entrance(); $this->_notices->add_sticky( $message, 'temporary_duplicate_notice', '', 'promotion', true, null, $plugin_title, true, true, array( 'product_ids' => $product_ids, 'blog_id' => get_current_blog_id() ) ); $this->temporary_duplicate_notice_shown_timestamp = time(); } #endregion /** * @author Leo Fajardo * @since 2.5.0 * * @param string $key * * @return bool */ private function should_use_network_storage( $key ) { return ( 'new_blog_install_map' === $key ); } /** * @param string $key * @param number|null $blog_id * * @return FS_Option_Manager */ private function get_storage( $key, $blog_id = null ) { if ( is_numeric( $blog_id ) ){ return FS_Option_Manager::get_manager( WP_FS___OPTION_PREFIX . self::OPTION_MANAGER_NAME, true, $blog_id ); } return $this->should_use_network_storage( $key ) ? $this->_network_storage : $this->_storage; } /** * @param string $name * @param bool $flush * @param number|null $blog_id * * @return mixed */ private function get_option( $name, $flush = false, $blog_id = null ) { return $this->get_storage( $name, $blog_id )->get_option( $name, null, $flush ); } #-------------------------------------------------------------------------------- #region Magic methods #-------------------------------------------------------------------------------- /** * @param string $name * @param int|string $value */ function __set( $name, $value ) { $this->get_storage( $name )->set_option( $name, $value, true ); } /** * @param string $name * * @return bool */ function __isset( $name ) { return $this->get_storage( $name )->has_option( $name, true ); } /** * @param string $name */ function __unset( $name ) { $this->get_storage( $name )->unset_option( $name, true ); } /** * @param string $name * * @return null|int|string */ function __get( $name ) { return $this->get_option( $name, // Reload storage from DB when accessing request_handler_* options to avoid race conditions. fs_starts_with( $name, 'request_handler' ) ); } #endregion } PK!FfP ' 'Clibraries/freemius/includes/managers/class-fs-key-value-storage.phpnu[ 0 ) { $key .= ":{$network_level_or_blog_id}"; } else { $network_level_or_blog_id = get_current_blog_id(); $key .= ":{$network_level_or_blog_id}"; } } if ( ! isset( self::$_instances[ $key ] ) ) { self::$_instances[ $key ] = new FS_Key_Value_Storage( $id, $secondary_id, $network_level_or_blog_id ); } return self::$_instances[ $key ]; } protected function __construct( $id, $secondary_id, $network_level_or_blog_id = false ) { $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $secondary_id . '_' . $id, WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); $this->_id = $id; $this->_secondary_id = $secondary_id; if ( is_multisite() ) { $this->_is_multisite_storage = ( true === $network_level_or_blog_id ); if ( is_numeric( $network_level_or_blog_id ) ) { $this->_blog_id = $network_level_or_blog_id; } } else { $this->_is_multisite_storage = false; } $this->load(); } protected function get_option_manager() { return FS_Option_Manager::get_manager( WP_FS__ACCOUNTS_OPTION_NAME, true, $this->_is_multisite_storage ? true : ( $this->_blog_id > 0 ? $this->_blog_id : false ) ); } protected function get_all_data() { return $this->get_option_manager()->get_option( $this->_id, array() ); } /** * Load plugin data from local DB. * * @author Vova Feldman (@svovaf) * @since 1.0.7 */ function load() { $all_plugins_data = $this->get_all_data(); $this->_data = isset( $all_plugins_data[ $this->_secondary_id ] ) ? $all_plugins_data[ $this->_secondary_id ] : array(); } /** * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @param string $key * @param mixed $value * @param bool $flush */ function store( $key, $value, $flush = true ) { if ( $this->_logger->is_on() ) { $this->_logger->entrance( $key . ' = ' . var_export( $value, true ) ); } if ( array_key_exists( $key, $this->_data ) && $value === $this->_data[ $key ] ) { // No need to store data if the value wasn't changed. return; } $all_data = $this->get_all_data(); $this->_data[ $key ] = $value; $all_data[ $this->_secondary_id ] = $this->_data; $options_manager = $this->get_option_manager(); $options_manager->set_option( $this->_id, $all_data, $flush ); } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 */ function save() { $this->get_option_manager()->store(); } /** * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @param bool $store * @param string[] $exceptions Set of keys to keep and not clear. */ function clear_all( $store = true, $exceptions = array() ) { $new_data = array(); foreach ( $exceptions as $key ) { if ( isset( $this->_data[ $key ] ) ) { $new_data[ $key ] = $this->_data[ $key ]; } } $this->_data = $new_data; if ( $store ) { $all_data = $this->get_all_data(); $all_data[ $this->_secondary_id ] = $this->_data; $options_manager = $this->get_option_manager(); $options_manager->set_option( $this->_id, $all_data, true ); } } /** * Delete key-value storage. * * @author Vova Feldman (@svovaf) * @since 1.0.9 */ function delete() { $this->_data = array(); $all_data = $this->get_all_data(); unset( $all_data[ $this->_secondary_id ] ); $options_manager = $this->get_option_manager(); $options_manager->set_option( $this->_id, $all_data, true ); } /** * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @param string $key * @param bool $store */ function remove( $key, $store = true ) { if ( ! array_key_exists( $key, $this->_data ) ) { return; } unset( $this->_data[ $key ] ); if ( $store ) { $all_data = $this->get_all_data(); $all_data[ $this->_secondary_id ] = $this->_data; $options_manager = $this->get_option_manager(); $options_manager->set_option( $this->_id, $all_data, true ); } } /** * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @param string $key * @param mixed $default * * @return bool|\FS_Plugin */ function get( $key, $default = false ) { return array_key_exists( $key, $this->_data ) ? $this->_data[ $key ] : $default; } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return string */ function get_secondary_id() { return $this->_secondary_id; } /* ArrayAccess + Magic Access (better for refactoring) -----------------------------------------------------------------------------------*/ function __set( $k, $v ) { $this->store( $k, $v ); } function __isset( $k ) { return array_key_exists( $k, $this->_data ); } function __unset( $k ) { $this->remove( $k ); } function __get( $k ) { return $this->get( $k, null ); } #[ReturnTypeWillChange] function offsetSet( $k, $v ) { if ( is_null( $k ) ) { throw new Exception( 'Can\'t append value to request params.' ); } else { $this->{$k} = $v; } } #[ReturnTypeWillChange] function offsetExists( $k ) { return array_key_exists( $k, $this->_data ); } #[ReturnTypeWillChange] function offsetUnset( $k ) { unset( $this->$k ); } #[ReturnTypeWillChange] function offsetGet( $k ) { return $this->get( $k, null ); } /** * (PHP 5 >= 5.0.0)
    * Return the current element * * @link http://php.net/manual/en/iterator.current.php * @return mixed Can return any type. */ #[ReturnTypeWillChange] public function current() { return current( $this->_data ); } /** * (PHP 5 >= 5.0.0)
    * Move forward to next element * * @link http://php.net/manual/en/iterator.next.php * @return void Any returned value is ignored. */ #[ReturnTypeWillChange] public function next() { next( $this->_data ); } /** * (PHP 5 >= 5.0.0)
    * Return the key of the current element * * @link http://php.net/manual/en/iterator.key.php * @return mixed scalar on success, or null on failure. */ #[ReturnTypeWillChange] public function key() { return key( $this->_data ); } /** * (PHP 5 >= 5.0.0)
    * Checks if current position is valid * * @link http://php.net/manual/en/iterator.valid.php * @return boolean The return value will be casted to boolean and then evaluated. * Returns true on success or false on failure. */ #[ReturnTypeWillChange] public function valid() { $key = key( $this->_data ); return ( $key !== null && $key !== false ); } /** * (PHP 5 >= 5.0.0)
    * Rewind the Iterator to the first element * * @link http://php.net/manual/en/iterator.rewind.php * @return void Any returned value is ignored. */ #[ReturnTypeWillChange] public function rewind() { reset( $this->_data ); } /** * (PHP 5 >= 5.1.0)
    * Count elements of an object * * @link http://php.net/manual/en/countable.count.php * @return int The custom count as an integer. *

    *

    * The return value is cast to an integer. */ #[ReturnTypeWillChange] public function count() { return count( $this->_data ); } }PK!Lߞ)b)bDlibraries/freemius/includes/managers/class-fs-admin-menu-manager.phpnu[ */ private $_default_submenu_items; /** * @since 1.1.3 * * @var string */ private $_first_time_path; /** * @since 1.2.2 * * @var bool */ private $_menu_exists; /** * @since 2.0.0 * * @var bool */ private $_network_menu_exists; #endregion Properties /** * @var FS_Logger */ protected $_logger; #region Singleton /** * @var FS_Admin_Menu_Manager[] */ private static $_instances = array(); /** * @param number $module_id * @param string $module_type * @param string $module_unique_affix * * @return FS_Admin_Menu_Manager */ static function instance( $module_id, $module_type, $module_unique_affix ) { $key = 'm_' . $module_id; if ( ! isset( self::$_instances[ $key ] ) ) { self::$_instances[ $key ] = new FS_Admin_Menu_Manager( $module_id, $module_type, $module_unique_affix ); } return self::$_instances[ $key ]; } protected function __construct( $module_id, $module_type, $module_unique_affix ) { $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $module_id . '_admin_menu', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); $this->_module_id = $module_id; $this->_module_type = $module_type; $this->_module_unique_affix = $module_unique_affix; } #endregion Singleton #region Helpers private function get_option( &$options, $key, $default = false ) { return ! empty( $options[ $key ] ) ? $options[ $key ] : $default; } private function get_bool_option( &$options, $key, $default = false ) { return isset( $options[ $key ] ) && is_bool( $options[ $key ] ) ? $options[ $key ] : $default; } #endregion Helpers /** * @param array $menu * @param bool $is_addon */ function init( $menu, $is_addon = false ) { $this->_menu_exists = ( isset( $menu['slug'] ) && ! empty( $menu['slug'] ) ); $this->_network_menu_exists = ( ! empty( $menu['network'] ) && true === $menu['network'] ); $this->_menu_slug = ( $this->_menu_exists ? $menu['slug'] : $this->_module_unique_affix ); $this->_default_submenu_items = array(); // @deprecated $this->_type = 'page'; $this->_is_top_level = true; $this->_is_override_exact = false; $this->_parent_slug = false; // @deprecated $this->_parent_type = 'page'; if ( isset( $menu ) ) { if ( ! $is_addon ) { $this->_default_submenu_items = array( 'contact' => $this->get_bool_option( $menu, 'contact', true ), 'support' => $this->get_bool_option( $menu, 'support', true ), 'affiliation' => $this->get_bool_option( $menu, 'affiliation', true ), 'account' => $this->get_bool_option( $menu, 'account', true ), 'pricing' => $this->get_bool_option( $menu, 'pricing', true ), 'addons' => $this->get_bool_option( $menu, 'addons', true ), ); // @deprecated $this->_type = $this->get_option( $menu, 'type', 'page' ); } $this->_is_override_exact = $this->get_bool_option( $menu, 'override_exact' ); if ( isset( $menu['parent'] ) ) { $this->_parent_slug = $this->get_option( $menu['parent'], 'slug' ); // @deprecated $this->_parent_type = $this->get_option( $menu['parent'], 'type', 'page' ); // If parent's slug is different, then it's NOT a top level menu item. $this->_is_top_level = ( $this->_parent_slug === $this->_menu_slug ); } else { /** * If no parent then top level if: * - Has custom admin menu ('page') * - CPT menu type ('cpt') */ // $this->_is_top_level = in_array( $this->_type, array( // 'cpt', // 'page' // ) ); } $first_path = $this->get_option( $menu, 'first-path', false ); if ( ! empty( $first_path ) && is_string( $first_path ) ) { $this->_first_time_path = $first_path; } } } /** * Check if top level menu. * * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @return bool False if submenu item. */ function is_top_level() { return $this->_is_top_level; } /** * Check if the page should be override on exact URL match. * * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @return bool False if submenu item. */ function is_override_exact() { return $this->_is_override_exact; } /** * Get the path of the page the user should be forwarded to after first activation. * * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @param bool $is_network Since 2.4.5 * * @return string */ function get_first_time_path( $is_network = false ) { if ( empty ( $this->_first_time_path ) ) { return $this->_first_time_path; } if ( $is_network ) { return network_admin_url( $this->_first_time_path ); } else { return admin_url( $this->_first_time_path ); } } /** * Check if plugin's menu item is part of a custom top level menu. * * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @return bool */ function has_custom_parent() { return ! $this->_is_top_level && is_string( $this->_parent_slug ); } /** * @author Leo Fajardo (@leorw) * @since 1.2.2 * * @return bool */ function has_menu() { return $this->_menu_exists; } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return bool */ function has_network_menu() { return $this->_network_menu_exists; } /** * @author Leo Fajardo (@leorw) * * @param string $menu_slug * * @since 2.1.3 */ function set_slug_and_network_menu_exists_flag($menu_slug ) { $this->_menu_slug = $menu_slug; $this->_network_menu_exists = false; } /** * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @param string $id * @param bool $default * @param bool $ignore_menu_existence Since 1.2.2.7 If true, check if the submenu item visible even if there's no parent menu. * * @return bool */ function is_submenu_item_visible( $id, $default = true, $ignore_menu_existence = false ) { if ( ! $ignore_menu_existence && ! $this->has_menu() ) { return false; } return fs_apply_filter( $this->_module_unique_affix, 'is_submenu_visible', $this->get_bool_option( $this->_default_submenu_items, $id, $default ), $id ); } /** * Calculates admin settings menu slug. * If plugin's menu slug is a file (e.g. CPT), uses plugin's slug as the menu slug. * * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @param string $page * * @return string */ function get_slug( $page = '' ) { return ( ( false === strpos( $this->_menu_slug, '.php?' ) ) ? $this->_menu_slug : $this->_module_unique_affix ) . ( empty( $page ) ? '' : ( '-' . $page ) ); } /** * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @return string */ function get_parent_slug() { return $this->_parent_slug; } /** * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @return string */ function get_type() { return $this->_type; } /** * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @return bool */ function is_cpt() { return ( 0 === strpos( $this->_menu_slug, 'edit.php?post_type=' ) || // Back compatibility. 'cpt' === $this->_type ); } /** * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @return string */ function get_parent_type() { return $this->_parent_type; } /** * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @return string */ function get_raw_slug() { return $this->_menu_slug; } /** * Get plugin's original menu slug. * * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @return string */ function get_original_menu_slug() { if ( 'cpt' === $this->_type ) { return add_query_arg( array( 'post_type' => $this->_menu_slug ), 'edit.php' ); } if ( false === strpos( $this->_menu_slug, '.php?' ) ) { return $this->_menu_slug; } else { return $this->_module_unique_affix; } } /** * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @return string */ function get_top_level_menu_slug() { return $this->has_custom_parent() ? $this->get_parent_slug() : $this->get_raw_slug(); } /** * Is user on plugin's admin activation page. * * @author Vova Feldman (@svovaf) * @since 1.0.8 * * @param bool $show_opt_in_on_themes_page Since 2.3.1 * * @return bool * * @deprecated Please use is_activation_page() instead. */ function is_main_settings_page( $show_opt_in_on_themes_page = false ) { return $this->is_activation_page( $show_opt_in_on_themes_page ); } /** * Is user on product's admin activation page. * * @author Vova Feldman (@svovaf) * @since 2.3.1 * * @param bool $show_opt_in_on_themes_page Since 2.3.1 * * @return bool */ function is_activation_page( $show_opt_in_on_themes_page = false ) { if ( $show_opt_in_on_themes_page ) { /** * In activation only when show_optin query string param is given. * * @since 1.2.2 */ return ( ( WP_FS__MODULE_TYPE_THEME === $this->_module_type ) && Freemius::is_themes_page() && fs_request_get_bool( $this->_module_unique_affix . '_show_optin' ) ); } if ( $this->_menu_exists && ( fs_is_plugin_page( $this->_menu_slug ) || fs_is_plugin_page( $this->_module_unique_affix ) ) ) { /** * Module has a settings menu and the context page is the main settings page, so assume it's in * activation (doesn't really check if already opted-in/skipped or not). * * @since 1.2.2 */ return true; } return false; } #region Submenu Override /** * Override submenu's action. * * @author Vova Feldman (@svovaf) * @since 1.1.0 * * @param string $parent_slug * @param string $menu_slug * @param callable $function * * @return false|string If submenu exist, will return the hook name. */ function override_submenu_action( $parent_slug, $menu_slug, $function ) { global $submenu; $menu_slug = plugin_basename( $menu_slug ); $parent_slug = plugin_basename( $parent_slug ); if ( ! isset( $submenu[ $parent_slug ] ) ) { // Parent menu not exist. return false; } $found_submenu_item = false; foreach ( $submenu[ $parent_slug ] as $submenu_item ) { if ( $menu_slug === $submenu_item[2] ) { $found_submenu_item = $submenu_item; break; } } if ( false === $found_submenu_item ) { // Submenu item not found. return false; } // Remove current function. $hookname = get_plugin_page_hookname( $menu_slug, $parent_slug ); remove_all_actions( $hookname ); // Attach new action. add_action( $hookname, $function ); return $hookname; } #endregion Submenu Override #region Top level menu Override /** * Find plugin's admin dashboard main menu item. * * @author Vova Feldman (@svovaf) * @since 1.0.2 * * @return string[]|false */ private function find_top_level_menu() { global $menu; $position = - 1; $found_menu = false; $menu_slug = $this->get_raw_slug(); $hook_name = get_plugin_page_hookname( $menu_slug, '' ); foreach ( $menu as $pos => $m ) { if ( $menu_slug === $m[2] ) { $position = $pos; $found_menu = $m; break; } } if ( false === $found_menu ) { return false; } return array( 'menu' => $found_menu, 'position' => $position, 'hook_name' => $hook_name ); } /** * Find plugin's admin dashboard main submenu item. * * @author Vova Feldman (@svovaf) * @since 1.2.1.6 * * @return array|false */ private function find_main_submenu() { global $submenu; $top_level_menu_slug = $this->get_top_level_menu_slug(); if ( ! isset( $submenu[ $top_level_menu_slug ] ) ) { return false; } $submenu_slug = $this->get_raw_slug(); $position = - 1; $found_submenu = false; $hook_name = get_plugin_page_hookname( $submenu_slug, '' ); foreach ( $submenu[ $top_level_menu_slug ] as $pos => $sub ) { if ( $submenu_slug === $sub[2] ) { $position = $pos; $found_submenu = $sub; } } if ( false === $found_submenu ) { return false; } return array( 'menu' => $found_submenu, 'parent_slug' => $top_level_menu_slug, 'position' => $position, 'hook_name' => $hook_name ); } /** * Remove all sub-menu items. * * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @return bool If submenu with plugin's menu slug was found. */ private function remove_all_submenu_items() { global $submenu; $menu_slug = $this->get_raw_slug(); if ( ! isset( $submenu[ $menu_slug ] ) ) { return false; } /** * This method is NOT executed for WordPress.org themes. * Since we maintain only one version of the SDK we added this small * hack to avoid the error from Theme Check since it's a false-positive. * * @author Vova Feldman (@svovaf) * @since 1.2.2.7 */ $submenu_ref = &$submenu; $submenu_ref[ $menu_slug ] = array(); return true; } /** * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @param bool $remove_top_level_menu * * @return false|array[string]mixed */ function remove_menu_item( $remove_top_level_menu = false ) { $this->_logger->entrance(); // Find main menu item. $top_level_menu = $this->find_top_level_menu(); if ( false === $top_level_menu ) { return false; } // Remove it with its actions. remove_all_actions( $top_level_menu['hook_name'] ); // Remove all submenu items. $this->remove_all_submenu_items(); if ( $remove_top_level_menu ) { global $menu; unset( $menu[ $top_level_menu['position'] ] ); } return $top_level_menu; } /** * Get module's main admin setting page URL. * * @todo This method was only tested for wp.org compliant themes with a submenu item. Need to test for plugins with top level, submenu, and CPT top level, menu items. * * @author Vova Feldman (@svovaf) * @since 1.2.2.7 * * @return string */ function main_menu_url() { $this->_logger->entrance(); if ( $this->_is_top_level ) { $menu = $this->find_top_level_menu(); } else { $menu = $this->find_main_submenu(); } $menu_slug = $menu['menu'][2]; $parent_slug = isset( $menu['parent_slug'] ) ? $menu['parent_slug'] : 'admin.php'; if ( fs_apply_filter( $this->_module_unique_affix, 'enable_cpt_advanced_menu_logic', false ) ) { $parent_slug = 'admin.php'; /** * This line and the `if` block below it are based on the `menu_page_url()` function of WordPress. * * @author Leo Fajardo (@leorw) * @since 2.10.2 */ global $_parent_pages; if ( ! empty( $_parent_pages[ $menu_slug ] ) ) { $_parent_slug = $_parent_pages[ $menu_slug ]; $parent_slug = isset( $_parent_pages[ $_parent_slug ] ) ? $parent_slug : $menu['parent_slug']; } } return admin_url( $parent_slug . ( false === strpos( $parent_slug, '?' ) ? '?' : '&' ) . 'page=' . $menu_slug ); } /** * @author Vova Feldman (@svovaf) * @since 1.1.4 * * @param callable $function * * @return false|array[string]mixed */ function override_menu_item( $function ) { $found_menu = $this->remove_menu_item(); if ( false === $found_menu ) { return false; } if ( ! $this->is_top_level() || ! $this->is_cpt() ) { $menu_slug = plugin_basename( $this->get_slug() ); $hookname = get_plugin_page_hookname( $menu_slug, '' ); // Override menu action. add_action( $hookname, $function ); } else { global $menu; // Remove original CPT menu. unset( $menu[ $found_menu['position'] ] ); // Create new top-level menu action. $hookname = self::add_page( $found_menu['menu'][3], $found_menu['menu'][0], 'manage_options', $this->get_slug(), $function, $found_menu['menu'][6], $found_menu['position'] ); } return $hookname; } /** * Adds a counter to the module's top level menu item. * * @author Vova Feldman (@svovaf) * @since 1.2.1.5 * * @param int $counter * @param string $class */ function add_counter_to_menu_item( $counter = 1, $class = '' ) { global $menu, $submenu; $mask = '%s '; /** * This method is NOT executed for WordPress.org themes. * Since we maintain only one version of the SDK we added this small * hack to avoid the error from Theme Check since it's a false-positive. * * @author Vova Feldman (@svovaf) * @since 1.2.2.7 */ $menu_ref = &$menu; $submenu_ref = &$submenu; if ( $this->_is_top_level ) { // Find main menu item. $found_menu = $this->find_top_level_menu(); if ( false !== $found_menu ) { // Override menu label. $menu_ref[ $found_menu['position'] ][0] = sprintf( $mask, $found_menu['menu'][0], $class, $counter ); } } else { $found_submenu = $this->find_main_submenu(); if ( false !== $found_submenu ) { // Override menu label. $submenu_ref[ $found_submenu['parent_slug'] ][ $found_submenu['position'] ][0] = sprintf( $mask, $found_submenu['menu'][0], $class, $counter ); } } } #endregion Top level menu Override /** * Add a top-level menu page. * * Note for WordPress.org Theme/Plugin reviewer: * * This is a replication of `add_menu_page()` to avoid Theme Check warning. * * Why? * ==== * Freemius is an SDK for plugin and theme developers. Since the core * of the SDK is relevant both for plugins and themes, for obvious reasons, * we only develop and maintain one code base. * * This method will not run for wp.org themes (only plugins) since theme * admin settings/options are now only allowed in the customizer. * * If you have any questions or need clarifications, please don't hesitate * pinging me on slack, my username is @svovaf. * * @author Vova Feldman (@svovaf) * @since 1.2.2 * * @param string $page_title The text to be displayed in the title tags of the page when the menu is * selected. * @param string $menu_title The text to be used for the menu. * @param string $capability The capability required for this menu to be displayed to the user. * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu). * @param callable|string $function The function to be called to output the content for this page. * @param string $icon_url The URL to the icon to be used for this menu. * * Pass a base64-encoded SVG using a data URI, which will be colored to * match the color scheme. This should begin with * 'data:image/svg+xml;base64,'. * * Pass the name of a Dashicons helper class to use a font icon, * e.g. 'dashicons-chart-pie'. * * Pass 'none' to leave div.wp-menu-image empty so an icon can be added * via CSS. * @param int $position The position in the menu order this one should appear. * * @return string The resulting page's hook_suffix. */ static function add_page( $page_title, $menu_title, $capability, $menu_slug, $function = '', $icon_url = '', $position = null ) { $fn = 'add_menu' . '_page'; return $fn( $page_title, $menu_title, $capability, $menu_slug, $function, $icon_url, $position ); } /** * Add page and update menu instance settings. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $page_title * @param string $menu_title * @param string $capability * @param string $menu_slug * @param callable|string $function * @param string $icon_url * @param int|null $position * * @return string */ function add_page_and_update( $page_title, $menu_title, $capability, $menu_slug, $function = '', $icon_url = '', $position = null ) { $this->_menu_slug = $menu_slug; $this->_is_top_level = true; $this->_menu_exists = true; $this->_network_menu_exists = true; return self::add_page( $page_title, $menu_title, $capability, $menu_slug, $function, $icon_url, $position ); } /** * Add a submenu page. * * Note for WordPress.org Theme/Plugin reviewer: * * This is a replication of `add_submenu_page()` to avoid Theme Check warning. * * Why? * ==== * Freemius is an SDK for plugin and theme developers. Since the core * of the SDK is relevant both for plugins and themes, for obvious reasons, * we only develop and maintain one code base. * * This method will not run for wp.org themes (only plugins) since theme * admin settings/options are now only allowed in the customizer. * * If you have any questions or need clarifications, please don't hesitate * pinging me on slack, my username is @svovaf. * * @author Vova Feldman (@svovaf) * @since 1.2.2 * * @param string $parent_slug The slug name for the parent menu (or the file name of a standard * WordPress admin page). * @param string $page_title The text to be displayed in the title tags of the page when the menu is * selected. * @param string $menu_title The text to be used for the menu. * @param string $capability The capability required for this menu to be displayed to the user. * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu). * @param callable|string $function The function to be called to output the content for this page. * * @return false|string The resulting page's hook_suffix, or false if the user does not have the capability * required. */ static function add_subpage( $parent_slug, $page_title, $menu_title, $capability, $menu_slug, $function = '' ) { $fn = 'add_submenu' . '_page'; return $fn( $parent_slug, $page_title, $menu_title, $capability, $menu_slug, $function ); } /** * Add sub page and update menu instance settings. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $parent_slug * @param string $page_title * @param string $menu_title * @param string $capability * @param string $menu_slug * @param callable|string $function * * @return string */ function add_subpage_and_update( $parent_slug, $page_title, $menu_title, $capability, $menu_slug, $function = '' ) { $this->_menu_slug = $menu_slug; $this->_parent_slug = $parent_slug; $this->_is_top_level = false; $this->_menu_exists = true; $this->_network_menu_exists = true; return self::add_subpage( $parent_slug, $page_title, $menu_title, $capability, $menu_slug, $function ); } }PK!Td,,Blibraries/freemius/includes/managers/class-fs-checkout-manager.phpnu[ $fs->get_id(), 'public_key' => $fs->get_public_key(), 'plugin_version' => $fs->get_plugin_version(), 'mode' => 'dashboard', 'trial' => fs_request_get_bool( 'trial' ), 'is_ms' => ( fs_is_network_admin() && $fs->is_network_active() ), ); if ( FS_Plugin_Plan::is_valid_id( $plan_id ) ) { $context_params['plan_id'] = $plan_id; } if ( $licenses === strval( intval( $licenses ) ) && $licenses > 0 ) { $context_params['licenses'] = $licenses; } if ( $plugin_id == $fs->get_id() ) { $is_premium = $fs->is_premium(); $bundle_id = $fs->get_bundle_id(); if ( ! is_null( $bundle_id ) ) { $context_params['bundle_id'] = $bundle_id; } } else { // Identify the module code version of the checkout context module. if ( $fs->is_addon_activated( $plugin_id ) ) { $fs_addon = Freemius::get_instance_by_id( $plugin_id ); $is_premium = $fs_addon->is_premium(); } else { // If add-on isn't activated assume the premium version isn't installed. $is_premium = false; } } // Get site context secure params. if ( $fs->is_registered() ) { $site = $fs->get_site(); if ( $plugin_id != $fs->get_id() ) { if ( $fs->is_addon_activated( $plugin_id ) ) { $fs_addon = Freemius::get_instance_by_id( $plugin_id ); $addon_site = $fs_addon->get_site(); if ( is_object( $addon_site ) ) { $site = $addon_site; } } } $context_params = array_merge( $context_params, FS_Security::instance()->get_context_params( $site, $timestamp, 'checkout' ) ); } else { $current_user = Freemius::_get_current_wp_user(); // Add site and user info to the request, this information // is NOT being stored unless the user complete the purchase // and agrees to the TOS. $context_params = array_merge( $context_params, array( 'user_firstname' => $current_user->user_firstname, 'user_lastname' => $current_user->user_lastname, 'user_email' => $current_user->user_email, 'home_url' => home_url(), ) ); $fs_user = Freemius::_get_user_by_email( $current_user->user_email ); if ( is_object( $fs_user ) && $fs_user->is_verified() ) { $context_params = array_merge( $context_params, FS_Security::instance()->get_context_params( $fs_user, $timestamp, 'checkout' ) ); } } if ( $fs->is_payments_sandbox() ) { // Append plugin secure token for sandbox mode authentication. $context_params['sandbox'] = FS_Security::instance()->get_secure_token( $fs->get_plugin(), $timestamp, 'checkout' ); /** * @since 1.1.7.3 Add security timestamp for sandbox even for anonymous user. */ if ( empty( $context_params['s_ctx_ts'] ) ) { $context_params['s_ctx_ts'] = $timestamp; } } $can_user_install = ( ( $fs->is_plugin() && current_user_can( 'install_plugins' ) ) || ( $fs->is_theme() && current_user_can( 'install_themes' ) ) ); return array_merge( $context_params, $_GET, array( // Current plugin version. 'plugin_version' => $fs->get_plugin_version(), 'sdk_version' => WP_FS__SDK_VERSION, 'is_premium' => $is_premium ? 'true' : 'false', 'can_install' => $can_user_install ? 'true' : 'false', ) ); } /** * The return URL to pass to the checkout when the checkout is loaded in "redirect" mode. * * @param Freemius $fs * * @return string */ public function get_checkout_redirect_return_url( Freemius $fs ) { $request_url = remove_query_arg( '_wp_http_referer' ); return fs_nonce_url( $fs->checkout_url( fs_request_get( 'billing_cycle' ), fs_request_get_bool( 'trial' ), array( 'process_redirect' => 'true', '_wp_http_referer' => $request_url, ) ), $this->get_checkout_redirect_nonce_action( $fs ) ); } /** * @param array $query_params * @param string $base_url * * @return string */ public function get_full_checkout_url( array $query_params, $base_url = FS_CHECKOUT__ADDRESS ) { return $base_url . '/?' . http_build_query( $query_params ); } /** * Verifies the redirect after a checkout with the nonce. * * @param Freemius $fs */ public function verify_checkout_redirect_nonce( Freemius $fs ) { check_admin_referer( $this->get_checkout_redirect_nonce_action( $fs ) ); } /** * Get the URL to process a new install after the checkout. * * @param Freemius $fs * @param number $plugin_id * * @return string */ public function get_install_url( Freemius $fs, $plugin_id ) { return fs_nonce_url( $fs->_get_admin_page_url( 'account', array( 'fs_action' => $fs->get_unique_affix() . '_activate_new', 'plugin_id' => $plugin_id, ) ), $fs->get_unique_affix() . '_activate_new' ); } /** * Get the URL to process a pending activation after the checkout. * * @param Freemius $fs * @param number $plugin_id * * @return string */ public function get_pending_activation_url( Freemius $fs, $plugin_id ) { return fs_nonce_url( $fs->_get_admin_page_url( 'account', array( 'fs_action' => $fs->get_unique_affix() . '_activate_new', 'plugin_id' => $plugin_id, 'pending_activation' => true, 'has_upgrade_context' => true, ) ), $fs->get_unique_affix() . '_activate_new' ); } private function get_checkout_redirect_nonce_action( Freemius $fs ) { return $fs->get_unique_affix() . '_checkout_redirect'; } }PK!6Y220libraries/freemius/includes/class-fs-options.phpnu[_id = $id; $this->_is_multisite = is_multisite(); if ( $this->_is_multisite ) { $this->_blog_id = get_current_blog_id(); $this->_network_options = FS_Option_Manager::get_manager( $id, $load, true ); } $this->_options = FS_Option_Manager::get_manager( $id, $load, $this->_blog_id ); } /** * Switch the context of the site level options manager. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param $blog_id */ function set_site_blog_context( $blog_id ) { $this->_blog_id = $blog_id; $this->_options = FS_Option_Manager::get_manager( $this->_id, false, $this->_blog_id ); } /** * @author Leo Fajardo (@leorw) * * @param string $option * @param mixed $default * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_SITE_LEVEL_PARAMS). * * @return mixed */ function get_option( $option, $default = null, $network_level_or_blog_id = null ) { if ( $this->should_use_network_storage( $option, $network_level_or_blog_id ) ) { return $this->_network_options->get_option( $option, $default ); } $site_options = $this->get_site_options( $network_level_or_blog_id ); return $site_options->get_option( $option, $default ); } /** * @author Leo Fajardo (@leorw) * @since 2.0.0 * * @param string $option * @param mixed $value * @param bool $flush * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_SITE_LEVEL_PARAMS). */ function set_option( $option, $value, $flush = false, $network_level_or_blog_id = null ) { if ( $this->should_use_network_storage( $option, $network_level_or_blog_id ) ) { $this->_network_options->set_option( $option, $value, $flush ); } else { $site_options = $this->get_site_options( $network_level_or_blog_id ); $site_options->set_option( $option, $value, $flush ); } } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $option * @param bool $flush * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_SITE_LEVEL_PARAMS). */ function unset_option( $option, $flush = false, $network_level_or_blog_id = null ) { if ( $this->should_use_network_storage( $option, $network_level_or_blog_id ) ) { $this->_network_options->unset_option( $option, $flush ); } else { $site_options = $this->get_site_options( $network_level_or_blog_id ); $site_options->unset_option( $option, $flush ); } } /** * @author Leo Fajardo (@leorw) * @since 2.0.0 * * @param bool $flush * @param bool $network_level */ function load( $flush = false, $network_level = true ) { if ( $this->_is_multisite && $network_level ) { $this->_network_options->load( $flush ); } else { $this->_options->load( $flush ); } } /** * @author Leo Fajardo (@leorw) * @since 2.0.0 * * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, store both network storage and the current context blog storage. */ function store( $network_level_or_blog_id = null ) { if ( ! $this->_is_multisite || false === $network_level_or_blog_id || 0 == $network_level_or_blog_id || is_null( $network_level_or_blog_id ) ) { $site_options = $this->get_site_options( $network_level_or_blog_id ); $site_options->store(); } if ( $this->_is_multisite && ( is_null( $network_level_or_blog_id ) || true === $network_level_or_blog_id ) ) { $this->_network_options->store(); } } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param int|null|bool $network_level_or_blog_id * @param bool $flush */ function clear( $network_level_or_blog_id = null, $flush = false ) { if ( ! $this->_is_multisite || false === $network_level_or_blog_id || is_null( $network_level_or_blog_id ) || is_numeric( $network_level_or_blog_id ) ) { $site_options = $this->get_site_options( $network_level_or_blog_id ); $site_options->clear( $flush ); } if ( $this->_is_multisite && ( true === $network_level_or_blog_id || is_null( $network_level_or_blog_id ) ) ) { $this->_network_options->clear( $flush ); } } /** * Migration script to the new storage data structure that is network compatible. * * IMPORTANT: * This method should be executed only after it is determined if this is a network * level compatible product activation. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param int $blog_id */ function migrate_to_network( $blog_id = 0 ) { if ( ! $this->_is_multisite ) { return; } $updated = false; $site_options = $this->get_site_options( $blog_id ); $keys = $site_options->get_options_keys(); foreach ( $keys as $option ) { if ( $this->is_site_option( $option ) || // Don't move admin notices to the network storage. in_array($option, array( // Don't move admin notices to the network storage. 'admin_notices', // Don't migrate the module specific data, it will be migrated by the FS_Storage. 'plugin_data', 'theme_data', )) ) { continue; } $option_updated = false; // Migrate option to the network storage. $site_option = $site_options->get_option( $option ); if ( ! $this->_network_options->has_option( $option ) ) { // Option not set on the network level, so just set it. $this->_network_options->set_option( $option, $site_option, false ); $option_updated = true; } else { // Option already set on the network level, so we need to merge it inelegantly. $network_option = $this->_network_options->get_option( $option ); if ( is_array( $network_option ) && is_array( $site_option ) ) { // Option is an array. foreach ( $site_option as $key => $value ) { if ( ! isset( $network_option[ $key ] ) ) { $network_option[ $key ] = $value; $option_updated = true; } else if ( is_array( $network_option[ $key ] ) && is_array( $value ) ) { if ( empty( $network_option[ $key ] ) ) { $network_option[ $key ] = $value; $option_updated = true; } else if ( empty( $value ) ) { // Do nothing. } else { reset($value); $first_key = key($value); if ( $value[$first_key] instanceof FS_Entity ) { // Merge entities by IDs. $network_entities_ids = array(); foreach ( $network_option[ $key ] as $entity ) { $network_entities_ids[ $entity->id ] = true; } foreach ( $value as $entity ) { if ( ! isset( $network_entities_ids[ $entity->id ] ) ) { $network_option[ $key ][] = $entity; $option_updated = true; } } } } } } } if ( $option_updated ) { $this->_network_options->set_option( $option, $network_option, false ); } } /** * Remove the option from site level storage. * * IMPORTANT: * The line below is intentionally commented since we want to preserve the option * on the site storage level for "downgrade compatibility". Basically, if the user * will downgrade to an older version of the plugin with the prev storage structure, * it will continue working. * * @todo After a few releases we can remove this. */ // $site_options->unset_option($option, false); if ( $option_updated ) { $updated = true; } } if ( ! $updated ) { return; } // Update network level storage. $this->_network_options->store(); // $site_options->store(); } #-------------------------------------------------------------------------------- #region Helper Methods #-------------------------------------------------------------------------------- /** * We don't want to load the map right away since it's not even needed in a non-MS environment. * * @author Vova Feldman (@svovaf) * @since 2.0.0 */ private static function load_site_options_map() { self::$_SITE_OPTIONS_MAP = array( 'sites' => true, 'theme_sites' => true, 'unique_id' => true, 'active_plugins' => true, ); } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $option * * @return bool */ private function is_site_option( $option ) { if ( WP_FS__ACCOUNTS_OPTION_NAME != $this->_id ) { return false; } if ( ! isset( self::$_SITE_OPTIONS_MAP ) ) { self::load_site_options_map(); } return isset( self::$_SITE_OPTIONS_MAP[ $option ] ); } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param int $blog_id * * @return FS_Option_Manager */ private function get_site_options( $blog_id = 0 ) { if ( 0 == $blog_id || $blog_id == $this->_blog_id ) { return $this->_options; } return FS_Option_Manager::get_manager( $this->_id, true, $blog_id ); } /** * Check if an option should be stored on the MS network storage. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $option * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_SITE_LEVEL_PARAMS). * * @return bool */ private function should_use_network_storage( $option, $network_level_or_blog_id = null ) { if ( ! $this->_is_multisite ) { // Not a multisite environment. return false; } if ( is_numeric( $network_level_or_blog_id ) ) { // Explicitly asked to use a specified blog storage. return false; } if ( is_bool( $network_level_or_blog_id ) ) { // Explicitly specified whether should use the network or blog level storage. return $network_level_or_blog_id; } // Determine which storage to use based on the option. return ! $this->is_site_option( $option ); } #endregion }PK!#SSS5libraries/freemius/includes/fs-plugin-info-dialog.phpnu[_fs = $fs; $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $fs->get_slug() . '_info', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); // Remove default plugin information action. remove_all_actions( 'install_plugins_pre_plugin-information' ); // Override action with custom plugins function for add-ons. add_action( 'install_plugins_pre_plugin-information', array( &$this, 'install_plugin_information' ) ); // Override request for plugin information for Add-ons. add_filter( 'fs_plugins_api', array( &$this, '_get_addon_info_filter' ), WP_FS__DEFAULT_PRIORITY, 3 ); } /** * Generate add-on plugin information. * * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param array $data * @param string $action * @param object|null $args * * @return array|null */ function _get_addon_info_filter( $data, $action = '', $args = null ) { $this->_logger->entrance(); $parent_plugin_id = fs_request_get( 'parent_plugin_id', $this->_fs->get_id() ); if ( $this->_fs->get_id() != $parent_plugin_id || ( 'plugin_information' !== $action ) || ! isset( $args->slug ) ) { return $data; } // Find add-on by slug. $selected_addon = $this->_fs->get_addon_by_slug( $args->slug, WP_FS__DEV_MODE ); if ( false === $selected_addon ) { return $data; } if ( ! isset( $selected_addon->info ) ) { // Setup some default info. $selected_addon->info = new stdClass(); $selected_addon->info->selling_point_0 = 'Selling Point 1'; $selected_addon->info->selling_point_1 = 'Selling Point 2'; $selected_addon->info->selling_point_2 = 'Selling Point 3'; $selected_addon->info->description = '

    Tell your users all about your add-on

    '; } fs_enqueue_local_style( 'fs_addons', '/admin/add-ons.css' ); $data = $args; $has_free_plan = false; $has_paid_plan = false; // Load add-on pricing. $has_pricing = false; $has_features = false; $plans = false; $result = $this->_fs->get_api_plugin_scope()->get( $this->_fs->add_show_pending( "/addons/{$selected_addon->id}/pricing.json?type=visible" ) ); if ( ! isset( $result->error ) ) { $plans = $result->plans; if ( is_array( $plans ) ) { for ( $i = 0, $len = count( $plans ); $i < $len; $i ++ ) { $pricing = isset( $plans[ $i ]->pricing ) ? $plans[ $i ]->pricing : null; $features = isset( $plans[ $i ]->features ) ? $plans[ $i ]->features : null; $plans[ $i ] = new FS_Plugin_Plan( $plans[ $i ] ); $plan = $plans[ $i ]; if ( 'free' == $plans[ $i ]->name || ! is_array( $pricing ) || 0 == count( $pricing ) ) { $has_free_plan = true; } if ( is_array( $pricing ) && 0 < count( $pricing ) ) { $filtered_pricing = array(); foreach ( $pricing as $prices ) { $prices = new FS_Pricing( $prices ); if ( ! $prices->is_usd() ) { /** * Skip non-USD pricing. * * @author Leo Fajardo (@leorw) * @since 2.3.1 */ continue; } if ( ( $prices->has_monthly() && $prices->monthly_price > 1.0 ) || ( $prices->has_annual() && $prices->annual_price > 1.0 ) || ( $prices->has_lifetime() && $prices->lifetime_price > 1.0 ) ) { $filtered_pricing[] = $prices; } } if ( ! empty( $filtered_pricing ) ) { $has_paid_plan = true; $plan->pricing = $filtered_pricing; $has_pricing = true; } } if ( is_array( $features ) && 0 < count( $features ) ) { $plan->features = $features; $has_features = true; } } } } $latest = null; if ( ! $has_paid_plan && $selected_addon->is_wp_org_compliant ) { $repo_data = FS_Plugin_Updater::_fetch_plugin_info_from_repository( 'plugin_information', (object) array( 'slug' => $selected_addon->slug, 'is_ssl' => is_ssl(), 'fields' => array( 'banners' => true, 'reviews' => true, 'downloaded' => false, 'active_installs' => true ) ) ); if ( ! empty( $repo_data ) ) { $data = $repo_data; $data->wp_org_missing = false; } else { // Couldn't find plugin on .org. $selected_addon->is_wp_org_compliant = false; // Plugin is missing, not on Freemius nor WP.org. $data->wp_org_missing = true; } $data->fs_missing = ( ! $has_free_plan || $data->wp_org_missing ); } else { $data->has_purchased_license = false; $data->wp_org_missing = false; $fs_addon = null; $current_addon_version = false; if ( $this->_fs->is_addon_activated( $selected_addon->id ) ) { $fs_addon = $this->_fs->get_addon_instance( $selected_addon->id ); $current_addon_version = $fs_addon->get_plugin_version(); } else if ( $this->_fs->is_addon_installed( $selected_addon->id ) ) { $addon_plugin_data = get_plugin_data( ( WP_PLUGIN_DIR . '/' . $this->_fs->get_addon_basename( $selected_addon->id ) ), false, false ); if ( ! empty( $addon_plugin_data ) ) { $current_addon_version = $addon_plugin_data['Version']; } } // Fetch latest version from Freemius. $latest = $this->_fs->_fetch_latest_version( $selected_addon->id, true, FS_Plugin_Updater::UPDATES_CHECK_CACHE_EXPIRATION, $current_addon_version ); if ( $has_paid_plan ) { $blog_id = fs_request_get( 'fs_blog_id' ); $has_valid_blog_id = is_numeric( $blog_id ); if ( $has_valid_blog_id ) { switch_to_blog( $blog_id ); } $data->checkout_link = $this->_fs->checkout_url( WP_FS__PERIOD_ANNUALLY, false, array(), ( $has_valid_blog_id ? false : null ) ); if ( $has_valid_blog_id ) { restore_current_blog(); } } /** * Check if there's a purchased license in case the add-on can only be installed/downloaded as part of a purchased bundle. * * @author Leo Fajardo (@leorw) * @since 2.4.1 */ if ( is_object( $fs_addon ) ) { $data->has_purchased_license = $fs_addon->has_active_valid_license(); } else { $account_addons = $this->_fs->get_account_addons(); if ( ! empty( $account_addons ) && in_array( $selected_addon->id, $account_addons ) ) { $data->has_purchased_license = true; } } if ( $has_free_plan || $data->has_purchased_license ) { $data->download_link = $this->_fs->_get_latest_download_local_url( $selected_addon->id ); } $data->fs_missing = ( false === $latest && ( empty( $selected_addon->premium_releases_count ) || ! ( $selected_addon->premium_releases_count > 0 ) ) ); // Fetch as much as possible info from local files. $plugin_local_data = $this->_fs->get_plugin_data(); $data->author = $plugin_local_data['Author']; if ( ! empty( $selected_addon->info->banner_url ) ) { $data->banners = array( 'low' => $selected_addon->info->banner_url, ); } if ( ! empty( $selected_addon->info->screenshots ) ) { $view_vars = array( 'screenshots' => $selected_addon->info->screenshots, 'plugin' => $selected_addon, ); $data->sections['screenshots'] = fs_get_template( '/plugin-info/screenshots.php', $view_vars ); } if ( is_object( $latest ) ) { $data->version = $latest->version; $data->last_updated = $latest->created; $data->requires = $latest->requires_platform_version; $data->requires_php = $latest->requires_programming_language_version; $data->tested = $latest->tested_up_to_version; } else if ( ! empty( $current_addon_version ) ) { $data->version = $current_addon_version; } else { // Add dummy version. $data->version = '1.0.0'; // Add message to developer to deploy the plugin through Freemius. } } $data->name = $selected_addon->title; $view_vars = array( 'plugin' => $selected_addon ); if ( is_object( $latest ) && isset( $latest->readme ) && is_object( $latest->readme ) ) { $latest_version_readme_data = $latest->readme; if ( isset( $latest_version_readme_data->sections ) ) { $data->sections = (array) $latest_version_readme_data->sections; } else { $data->sections = array(); } } $data->sections['description'] = fs_get_template( '/plugin-info/description.php', $view_vars ); if ( $has_pricing ) { // Add plans to data. $data->plans = $plans; if ( $has_features ) { $view_vars = array( 'plans' => $plans, 'plugin' => $selected_addon, ); $data->sections['features'] = fs_get_template( '/plugin-info/features.php', $view_vars ); } } $data->has_free_plan = $has_free_plan; $data->has_paid_plan = $has_paid_plan; $data->is_paid = $has_paid_plan; $data->is_wp_org_compliant = $selected_addon->is_wp_org_compliant; $data->premium_slug = $selected_addon->premium_slug; $data->addon_id = $selected_addon->id; if ( ! isset( $data->has_purchased_license ) ) { $data->has_purchased_license = false; } return $data; } /** * @author Vova Feldman (@svovaf) * @since 1.1.7 * * @param FS_Plugin_Plan $plan * * @return string */ private function get_billing_cycle( FS_Plugin_Plan $plan ) { $billing_cycle = null; if ( 1 === count( $plan->pricing ) && 1 == $plan->pricing[0]->licenses ) { $pricing = $plan->pricing[0]; if ( isset( $pricing->annual_price ) ) { $billing_cycle = 'annual'; } else if ( isset( $pricing->monthly_price ) ) { $billing_cycle = 'monthly'; } else if ( isset( $pricing->lifetime_price ) ) { $billing_cycle = 'lifetime'; } } else { foreach ( $plan->pricing as $pricing ) { if ( isset( $pricing->annual_price ) ) { $billing_cycle = 'annual'; } else if ( isset( $pricing->monthly_price ) ) { $billing_cycle = 'monthly'; } else if ( isset( $pricing->lifetime_price ) ) { $billing_cycle = 'lifetime'; } if ( ! is_null( $billing_cycle ) ) { break; } } } return $billing_cycle; } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param FS_Plugin_Plan $plan * @param FS_Pricing $pricing * * @return float|null|string */ private function get_price_tag( FS_Plugin_Plan $plan, FS_Pricing $pricing ) { $price_tag = ''; if ( isset( $pricing->annual_price ) ) { $price_tag = $pricing->annual_price . ( $plan->is_block_features ? ' / year' : '' ); } else if ( isset( $pricing->monthly_price ) ) { $price_tag = $pricing->monthly_price . ' / mo'; } else if ( isset( $pricing->lifetime_price ) ) { $price_tag = $pricing->lifetime_price; } return '$' . $price_tag; } /** * @author Leo Fajardo (@leorw) * @since 2.3.0 * * @param object $api * @param FS_Plugin_Plan $plan * * @return string */ private function get_actions_dropdown( $api, $plan = null ) { $this->actions = isset( $this->actions ) ? $this->actions : $this->get_plugin_actions( $api ); $actions = $this->actions; $checkout_cta = $this->get_checkout_cta( $api, $plan ); if ( ! empty( $checkout_cta ) ) { /** * If there's no license yet, make the checkout button the main CTA. Otherwise, make it the last item in * the actions dropdown. * * @author Leo Fajardo (@leorw) * @since 2.3.0 */ if ( ! $api->has_purchased_license ) { array_unshift( $actions, $checkout_cta ); } else { $actions[] = $checkout_cta; } } if ( empty( $actions ) ) { return ''; } $total_actions = count( $actions ); if ( 1 === $total_actions ) { return $actions[0]; } ob_start(); ?>
    checkout_link ) || ! isset( $api->plans ) || ! is_array( $api->plans ) || 0 == count( $api->plans ) ) { return ''; } if ( is_null( $plan ) ) { foreach ( $api->plans as $p ) { if ( ! empty( $p->pricing ) ) { $plan = $p; break; } } } $blog_id = fs_request_get( 'fs_blog_id' ); $has_valid_blog_id = is_numeric( $blog_id ); if ( $has_valid_blog_id ) { switch_to_blog( $blog_id ); } $addon_checkout_url = $this->_fs->addon_checkout_url( $plan->plugin_id, $plan->pricing[0]->id, $this->get_billing_cycle( $plan ), $plan->has_trial(), ( $has_valid_blog_id ? false : null ) ); if ( $has_valid_blog_id ) { restore_current_blog(); } return '' . esc_html( ! $plan->has_trial() ? ( $api->has_purchased_license ? fs_text_inline( 'Purchase More', 'purchase-more', $api->slug ) : fs_text_x_inline( 'Purchase', 'verb', 'purchase', $api->slug ) ) : sprintf( /* translators: %s: N-days trial */ fs_text_inline( 'Start my free %s', 'start-free-x', $api->slug ), $this->get_trial_period( $plan ) ) ) . ''; } /** * @author Leo Fajardo (@leorw) * @since 2.3.0 * * @param object $api * * @return string[] */ private function get_plugin_actions( $api ) { $this->status = isset( $this->status ) ? $this->status : install_plugin_install_status( $api ); $is_update_available = ( 'update_available' === $this->status['status'] ); if ( $is_update_available && empty( $this->status['url'] ) ) { return array(); } $blog_id = fs_request_get( 'fs_blog_id' ); $active_plugins_directories_map = Freemius::get_active_plugins_directories_map( $blog_id ); $actions = array(); $is_addon_activated = $this->_fs->is_addon_activated( $api->slug ); $fs_addon = null; $is_free_installed = null; $is_premium_installed = null; $has_installed_version = ( 'install' !== $this->status['status'] ); if ( ! $api->has_paid_plan && ! $api->has_purchased_license ) { /** * Free-only add-on. * * @author Leo Fajardo (@leorw) * @since 2.3.0 */ $is_free_installed = $has_installed_version; $is_premium_installed = false; } else if ( ! $api->has_free_plan ) { /** * Premium-only add-on. * * @author Leo Fajardo (@leorw) * @since 2.3.0 */ $is_free_installed = false; $is_premium_installed = $has_installed_version; } else { /** * Freemium add-on. * * @author Leo Fajardo (@leorw) * @since 2.3.0 */ if ( ! $has_installed_version ) { $is_free_installed = false; $is_premium_installed = false; } else { $fs_addon = $is_addon_activated ? $this->_fs->get_addon_instance( $api->slug ) : null; if ( is_object( $fs_addon ) ) { if ( $fs_addon->is_premium() ) { $is_premium_installed = true; } else { $is_free_installed = true; } } if ( is_null( $is_free_installed ) ) { $is_free_installed = file_exists( fs_normalize_path( WP_PLUGIN_DIR . "/{$api->slug}/{$api->slug}.php" ) ); if ( ! $is_free_installed ) { /** * Check if there's a plugin installed in a directory named `$api->slug`. * * @author Leo Fajardo (@leorw) * @since 2.3.0 */ $installed_plugins = get_plugins( '/' . $api->slug ); $is_free_installed = ( ! empty( $installed_plugins ) ); } } if ( is_null( $is_premium_installed ) ) { $is_premium_installed = file_exists( fs_normalize_path( WP_PLUGIN_DIR . "/{$api->premium_slug}/{$api->slug}.php" ) ); if ( ! $is_premium_installed ) { /** * Check if there's a plugin installed in a directory named `$api->premium_slug`. * * @author Leo Fajardo (@leorw) * @since 2.3.0 */ $installed_plugins = get_plugins( '/' . $api->premium_slug ); $is_premium_installed = ( ! empty( $installed_plugins ) ); } } } $has_installed_version = ( $is_free_installed || $is_premium_installed ); } $this->status['is_free_installed'] = $is_free_installed; $this->status['is_premium_installed'] = $is_premium_installed; $can_install_free_version = false; $can_install_free_version_update = false; $can_download_free_version = false; $can_activate_free_version = false; $can_install_premium_version = false; $can_install_premium_version_update = false; $can_download_premium_version = false; $can_activate_premium_version = false; if ( ! $api->has_purchased_license ) { if ( $api->has_free_plan ) { if ( $has_installed_version ) { if ( $is_update_available ) { $can_install_free_version_update = true; } else if ( ! $is_premium_installed && ! isset( $active_plugins_directories_map[ dirname( $this->status['file'] ) ] ) ) { $can_activate_free_version = true; } } else { if ( $this->_fs->is_premium() || ! $this->_fs->is_org_repo_compliant() || $api->is_wp_org_compliant ) { $can_install_free_version = true; } else { $can_download_free_version = true; } } } } else { if ( ! is_object( $fs_addon ) && $is_addon_activated ) { $fs_addon = $this->_fs->get_addon_instance( $api->slug ); } $can_download_premium_version = true; if ( ! isset( $active_plugins_directories_map[ dirname( $this->status['file'] ) ] ) ) { if ( $is_premium_installed ) { $can_activate_premium_version = ( ! $is_addon_activated || ! $fs_addon->is_premium() ); } else if ( $is_free_installed ) { $can_activate_free_version = ( ! $is_addon_activated ); } } if ( $this->_fs->is_premium() || ! $this->_fs->is_org_repo_compliant() ) { if ( $is_update_available ) { $can_install_premium_version_update = true; } else if ( ! $is_premium_installed ) { $can_install_premium_version = true; } } } if ( $can_install_premium_version || $can_install_premium_version_update ) { if ( is_numeric( $blog_id ) ) { /** * Replace the network status URL with a blog admin–based status URL if the `Add-Ons` page is loaded * from a specific blog admin page (when `fs_blog_id` is valid) in order for plugin installation/update * to work. * * @author Leo Fajardo (@leorw) * @since 2.3.0 */ $this->status['url'] = self::get_blog_status_url( $blog_id, $this->status['url'], $this->status['status'] ); } /** * Add the `fs_allow_updater_and_dialog` param to the install/update URL so that the add-on can be * installed/updated. * * @author Leo Fajardo (@leorw) * @since 2.3.0 */ $this->status['url'] = str_replace( '?', '?fs_allow_updater_and_dialog=true&', $this->status['url'] ); } if ( $can_install_free_version_update || $can_install_premium_version_update ) { $actions[] = $this->get_cta( ( $can_install_free_version_update ? fs_esc_html_inline( 'Install Free Version Update Now', 'install-free-version-update-now', $api->slug ) : fs_esc_html_inline( 'Install Update Now', 'install-update-now', $api->slug ) ), true, false, $this->status['url'], '_parent' ); } else if ( $can_install_free_version || $can_install_premium_version ) { $actions[] = $this->get_cta( ( $can_install_free_version ? fs_esc_html_inline( 'Install Free Version Now', 'install-free-version-now', $api->slug ) : fs_esc_html_inline( 'Install Now', 'install-now', $api->slug ) ), true, false, $this->status['url'], '_parent' ); } $download_latest_action = ''; if ( ! empty( $api->download_link ) && ( $can_download_free_version || $can_download_premium_version ) ) { $download_latest_action = $this->get_cta( ( $can_download_free_version ? fs_esc_html_x_inline( 'Download Latest Free Version', 'as download latest version', 'download-latest-free-version', $api->slug ) : fs_esc_html_x_inline( 'Download Latest', 'as download latest version', 'download-latest', $api->slug ) ), true, false, esc_url( $api->download_link ) ); } if ( ! $can_activate_free_version && ! $can_activate_premium_version ) { if ( ! empty( $download_latest_action ) ) { $actions[] = $download_latest_action; } } else { $activate_action = sprintf( '%s', wp_nonce_url( ( is_numeric( $blog_id ) ? trailingslashit( get_admin_url( $blog_id ) ) : '' ) . 'plugins.php?action=activate&plugin=' . $this->status['file'], 'activate-plugin_' . $this->status['file'] ), fs_esc_attr_inline( 'Activate this add-on', 'activate-this-addon', $api->slug ), $can_activate_free_version ? fs_text_inline( 'Activate Free Version', 'activate-free', $api->slug ) : fs_text_inline( 'Activate', 'activate', $api->slug ) ); if ( ! $can_download_premium_version && ! empty( $download_latest_action ) ) { $actions[] = $download_latest_action; $download_latest_action = ''; } if ( $can_install_premium_version || $can_install_premium_version_update ) { if ( $can_download_premium_version && ! empty( $download_latest_action ) ) { $actions[] = $download_latest_action; $download_latest_action = ''; } $actions[] = $activate_action; } else { array_unshift( $actions, $activate_action ); } if ( ! empty ($download_latest_action ) ) { $actions[] = $download_latest_action; } } return $actions; } /** * Rebuilds the status URL based on the admin URL. * * @author Leo Fajardo (@leorw) * @since 2.3.0 * * @param int $blog_id * @param string $network_status_url * @param string $status * * @return string */ private static function get_blog_status_url( $blog_id, $network_status_url, $status ) { if ( ! in_array( $status, array( 'install', 'update_available' ) ) ) { return $network_status_url; } $action = ( 'install' === $status ) ? 'install-plugin' : 'upgrade-plugin'; $url_params = fs_parse_url_params( $network_status_url, true ); if ( empty( $url_params ) || ! isset( $url_params['plugin'] ) ) { return $network_status_url; } $plugin = $url_params['plugin']; return wp_nonce_url( get_admin_url( $blog_id,"update.php?action={$action}&plugin={$plugin}"), "{$action}_{$plugin}"); } /** * Helper method to get a CTA button HTML. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $label * @param bool $is_primary * @param bool $is_disabled * @param string $href * @param string $target * * @return string */ private function get_cta( $label, $is_primary = true, $is_disabled = false, $href = '', $target = '_blank' ) { $classes = array(); if ( ! $is_primary ) { $classes[] = 'left'; } else { $classes[] = 'button-primary'; $classes[] = 'right'; } if ( $is_disabled ) { $classes[] = 'disabled'; } $rel = ( '_blank' === $target ) ? ' rel="noopener noreferrer"' : ''; return sprintf( '%s', empty( $href ) ? '' : 'href="' . $href . '" target="' . $target . '"' . $rel, implode( ' ', $classes ), $label ); } /** * @author Vova Feldman (@svovaf) * @since 1.1.7 * * @param FS_Plugin_Plan $plan * * @return string */ private function get_trial_period( $plan ) { $trial_period = (int) $plan->trial_period; switch ( $trial_period ) { case 30: return 'month'; case 60: return '2 months'; default: return "{$plan->trial_period} days"; } } /** * Display plugin information in dialog box form. * * Based on core install_plugin_information() function. * * @author Vova Feldman (@svovaf) * @since 1.0.6 */ function install_plugin_information() { global $tab; if ( empty( $_REQUEST['plugin'] ) ) { return; } $args = array( 'slug' => wp_unslash( $_REQUEST['plugin'] ), 'is_ssl' => is_ssl(), 'fields' => array( 'banners' => true, 'reviews' => true, 'downloaded' => false, 'active_installs' => true ) ); if ( is_array( $args ) ) { $args = (object) $args; } if ( ! isset( $args->per_page ) ) { $args->per_page = 24; } if ( ! isset( $args->locale ) ) { $args->locale = get_locale(); } $api = apply_filters( 'fs_plugins_api', false, 'plugin_information', $args ); if ( is_wp_error( $api ) ) { wp_die( $api ); } $plugins_allowedtags = array( 'a' => array( 'href' => array(), 'title' => array(), 'target' => array(), // Add image style for screenshots. 'class' => array() ), 'style' => array(), 'abbr' => array( 'title' => array() ), 'acronym' => array( 'title' => array() ), 'code' => array(), 'pre' => array(), 'em' => array(), 'strong' => array(), 'div' => array( 'class' => array() ), 'span' => array( 'class' => array() ), 'p' => array(), 'ul' => array(), 'ol' => array(), 'li' => array( 'class' => array() ), 'i' => array( 'class' => array() ), 'h1' => array(), 'h2' => array(), 'h3' => array(), 'h4' => array(), 'h5' => array(), 'h6' => array(), 'img' => array( 'src' => array(), 'class' => array(), 'alt' => array() ), // 'table' => array(), // 'td' => array(), // 'tr' => array(), // 'th' => array(), // 'thead' => array(), // 'tbody' => array(), ); $plugins_section_titles = array( 'description' => fs_text_x_inline( 'Description', 'Plugin installer section title', 'description', $api->slug ), 'installation' => fs_text_x_inline( 'Installation', 'Plugin installer section title', 'installation', $api->slug ), 'faq' => fs_text_x_inline( 'FAQ', 'Plugin installer section title', 'faq', $api->slug ), 'screenshots' => fs_text_inline( 'Screenshots', 'screenshots', $api->slug ), 'changelog' => fs_text_x_inline( 'Changelog', 'Plugin installer section title', 'changelog', $api->slug ), 'reviews' => fs_text_x_inline( 'Reviews', 'Plugin installer section title', 'reviews', $api->slug ), 'other_notes' => fs_text_x_inline( 'Other Notes', 'Plugin installer section title', 'other-notes', $api->slug ), ); // Sanitize HTML // foreach ( (array) $api->sections as $section_name => $content ) { // $api->sections[$section_name] = wp_kses( $content, $plugins_allowedtags ); // } foreach ( array( 'version', 'author', 'requires', 'tested', 'homepage', 'downloaded', 'slug' ) as $key ) { if ( isset( $api->$key ) ) { $api->$key = wp_kses( $api->$key, $plugins_allowedtags ); } } // Add after $api->slug is ready. $plugins_section_titles['features'] = fs_text_x_inline( 'Features & Pricing', 'Plugin installer section title', 'features-and-pricing', $api->slug ); $_tab = esc_attr( $tab ); $section = isset( $_REQUEST['section'] ) ? wp_unslash( $_REQUEST['section'] ) : 'description'; // Default to the Description tab, Do not translate, API returns English. if ( empty( $section ) || ! isset( $api->sections[ $section ] ) ) { $section_titles = array_keys( (array) $api->sections ); $section = array_shift( $section_titles ); } iframe_header( fs_text_inline( 'Plugin Install', 'plugin-install', $api->slug ) ); $_with_banner = ''; // var_dump($api->banners); if ( ! empty( $api->banners ) && ( ! empty( $api->banners['low'] ) || ! empty( $api->banners['high'] ) ) ) { $_with_banner = 'with-banner'; $low = empty( $api->banners['low'] ) ? $api->banners['high'] : $api->banners['low']; $high = empty( $api->banners['high'] ) ? $api->banners['low'] : $api->banners['high']; ?> '; echo "

    {$api->name}

    "; echo "
    \n"; foreach ( (array) $api->sections as $section_name => $content ) { if ( 'reviews' === $section_name && ( empty( $api->ratings ) || 0 === array_sum( (array) $api->ratings ) ) ) { continue; } if ( isset( $plugins_section_titles[ $section_name ] ) ) { $title = $plugins_section_titles[ $section_name ]; } else { $title = ucwords( str_replace( '_', ' ', $section_name ) ); } $class = ( $section_name === $section ) ? ' class="current"' : ''; $href = add_query_arg( array( 'tab' => $tab, 'section' => $section_name ) ); $href = esc_url( $href ); $san_section = esc_attr( $section_name ); echo "\t" . esc_html( $title ) . "\n"; } echo "
    \n"; ?>
    is_paid ) : ?> plans ) ) : ?>
    plans as $plan ) : ?> pricing ) ) { continue; } /** * @var FS_Plugin_Plan $plan */ ?> pricing[0] ?> is_multi_cycle() ?>

    slug ), $plan->title ) ) ?>

    has_annual() ?> has_monthly() ?>
    pricing[0]->annual_discount_percentage() : 0 ?> 0 ) : ?> slug ), $annual_discount . '%' ) ?>
    get_actions_dropdown( $api, $plan ) ?>
    has_trial() ) : ?> get_trial_period( $plan ) ?>
    • slug ), $trial_period ) ) ?>
    • slug ) ), $trial_period, '' . $this->get_price_tag( $plan, $plan->pricing[0] ) . '' ) ?>

    slug ) ?>

      version ) ) { ?>
    • slug ); ?> : version; ?>
    • author ) ) { ?>
    • slug ); ?> : author, '_blank' ); ?>
    • last_updated ) ) { ?>
    • slug ); ?> : slug ), human_time_diff( strtotime( $api->last_updated ) ) ) ) ?>
    • requires ) ) { ?>
    • slug ) ?> : slug ), $api->requires ) ) ?>
    • tested ) ) { ?>
    • slug ); ?> : tested; ?>
    • requires_php ) ) { ?>
    • slug ); ?>: slug ), $api->requires_php ) ); ?>
    • downloaded ) ) { ?>
    • slug ) ?> : downloaded ) ? /* translators: %s: 1 or One (Number of times downloaded) */ fs_text_inline( '%s time', 'x-time', $api->slug ) : /* translators: %s: Number of times downloaded */ fs_text_inline( '%s times', 'x-times', $api->slug ) ), number_format_i18n( $api->downloaded ) ) ); ?>
    • slug ) && true == $api->is_wp_org_compliant ) { ?>
    • slug ) ?> »
    • homepage ) ) { ?>
    • slug ) ?> »
    • donate_link ) && empty( $api->contributors ) ) { ?>
    • slug ) ?> »
    rating ) ) { ?>

    slug ); ?>

    $api->rating, 'type' => 'percent', 'number' => $api->num_ratings ) ); ?> (slug ), sprintf( ( ( 1 == $api->num_ratings ) ? /* translators: %s: 1 or One */ fs_text_inline( '%s rating', 'x-rating', $api->slug ) : /* translators: %s: Number larger than 1 */ fs_text_inline( '%s ratings', 'x-ratings', $api->slug ) ), number_format_i18n( $api->num_ratings ) ) ) ) ?>) ratings ) && array_sum( (array) $api->ratings ) > 0 ) { foreach ( $api->ratings as $key => $ratecount ) { // Avoid div-by-zero. $_rating = $api->num_ratings ? ( $ratecount / $api->num_ratings ) : 0; $stars_label = sprintf( ( ( 1 == $key ) ? /* translators: %s: 1 or One */ fs_text_inline( '%s star', 'x-star', $api->slug ) : /* translators: %s: Number larger than 1 */ fs_text_inline( '%s stars', 'x-stars', $api->slug ) ), number_format_i18n( $key ) ); ?>
    contributors ) ) { ?>

    slug ); ?>

      contributors as $contrib_username => $contrib_profile ) { if ( empty( $contrib_username ) && empty( $contrib_profile ) ) { continue; } if ( empty( $contrib_username ) ) { $contrib_username = preg_replace( '/^.+\/(.+)\/?$/', '\1', $contrib_profile ); } $contrib_username = sanitize_user( $contrib_username ); if ( empty( $contrib_profile ) ) { echo "
    • {$contrib_username}
    • "; } else { echo "
    • {$contrib_username}
    • "; } } ?>
    donate_link ) ) { ?> slug ) ?> »
    requires_php ) ? $api->requires_php : null; $requires_wp = isset( $api->requires ) ? $api->requires : null; $compatible_php = empty( $requires_php ) || version_compare( PHP_VERSION, $requires_php, '>=' ); // Strip off any -alpha, -RC, -beta, -src suffixes. list( $wp_version ) = explode( '-', $GLOBALS['wp_version'] ); $compatible_wp = empty( $requires_wp ) || version_compare( $wp_version, $requires_wp, '>=' ); $tested_wp = ( empty( $api->tested ) || version_compare( $wp_version, $api->tested, '<=' ) ); if ( ! $compatible_php ) { echo '

    ' . fs_text_inline( 'Error', 'error', $api->slug ) . ': ' . fs_text_inline( 'This plugin requires a newer version of PHP.', 'newer-php-required-error', $api->slug ); if ( current_user_can( 'update_php' ) ) { $wp_get_update_php_url = function_exists( 'wp_get_update_php_url' ) ? wp_get_update_php_url() : 'https://wordpress.org/support/update-php/'; printf( /* translators: %s: URL to Update PHP page. */ ' ' . fs_text_inline( 'Click here to learn more about updating PHP.', 'php-update-learn-more-link', $api->slug ), esc_url( $wp_get_update_php_url ) ); if ( function_exists( 'wp_update_php_annotation' ) ) { wp_update_php_annotation( '

    ', '' ); } } else { echo '

    '; } echo '
    '; } if ( ! $tested_wp ) { echo '

    ' . '' . fs_text_inline( 'Warning', 'warning', $api->slug ) . ': ' . fs_text_inline( 'This plugin has not been tested with your current version of WordPress.', 'not-tested-warning', $api->slug ) . '

    '; } else if ( ! $compatible_wp ) { echo '

    ' . '' . fs_text_inline( 'Warning', 'warning', $api->slug ) . ': ' . fs_text_inline( 'This plugin has not been marked as compatible with your version of WordPress.', 'not-compatible-warning', $api->slug ) . '

    '; } foreach ( (array) $api->sections as $section_name => $content ) { $content = links_add_base_url( $content, 'https://wordpress.org/plugins/' . $api->slug . '/' ); $content = links_add_target( $content, '_blank' ); $san_section = esc_attr( $section_name ); $display = ( $section_name === $section ) ? 'block' : 'none'; if ( 'description' === $section_name && ( ( $api->is_wp_org_compliant && $api->wp_org_missing ) || ( ! $api->is_wp_org_compliant && $api->fs_missing ) ) ) { $missing_notice = array( 'type' => 'error', 'id' => md5( microtime() ), 'message' => $api->is_paid ? fs_text_inline( 'Paid add-on must be deployed to Freemius.', 'paid-addon-not-deployed', $api->slug ) : fs_text_inline( 'Add-on must be deployed to WordPress.org or Freemius.', 'free-addon-not-deployed', $api->slug ), ); fs_require_template( 'admin-notice.php', $missing_notice ); } echo "\t
    \n"; echo $content; echo "\t
    \n"; } echo "
    \n"; echo "
    \n"; echo "\n"; // #plugin-information-scrollable echo "\n"; ?> _id = $id; $this->_title = $title; $this->_module_unique_affix = $module_unique_affix; $this->_is_multisite = is_multisite(); if ( $this->_is_multisite ) { $this->_blog_id = get_current_blog_id(); $this->_network_notices = FS_Admin_Notice_Manager::instance( $id, $title, $module_unique_affix, $is_network_and_blog_admins, true ); } $this->_notices = FS_Admin_Notice_Manager::instance( $id, $title, $module_unique_affix, false, $this->_blog_id ); } /** * Add admin message to admin messages queue, and hook to admin_notices / all_admin_notices if not yet hooked. * * @author Vova Feldman (@svovaf) * @since 1.0.4 * * @param string $message * @param string $title * @param string $type * @param bool $is_sticky * @param string $id Message ID * @param bool $store_if_sticky * @param int|null $network_level_or_blog_id * * @uses add_action() */ function add( $message, $title = '', $type = 'success', $is_sticky = false, $id = '', $store_if_sticky = true, $network_level_or_blog_id = null, $is_dimissible = null ) { $notices = $this->get_site_or_network_notices( $id, $network_level_or_blog_id ); $notices->add( $message, $title, $type, $is_sticky, $id, $store_if_sticky, null, null, false, $is_dimissible ); } /** * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @param string|string[] $ids * @param int|null $network_level_or_blog_id * @param bool $store */ function remove_sticky( $ids, $network_level_or_blog_id = null, $store = true ) { if ( ! is_array( $ids ) ) { $ids = array( $ids ); } if ( $this->should_use_network_notices( $ids[0], $network_level_or_blog_id ) ) { $notices = $this->_network_notices; } else { $notices = $this->get_site_notices( $network_level_or_blog_id ); } return $notices->remove_sticky( $ids, $store ); } /** * Check if sticky message exists by id. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @param string $id * @param int|null $network_level_or_blog_id * * @return bool */ function has_sticky( $id, $network_level_or_blog_id = null ) { $notices = $this->get_site_or_network_notices( $id, $network_level_or_blog_id ); return $notices->has_sticky( $id ); } /** * Adds sticky admin notification. * * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @param string $message * @param string $id Message ID * @param string $title * @param string $type * @param int|null $network_level_or_blog_id * @param number|null $wp_user_id * @param string|null $plugin_title * @param bool $is_network_and_blog_admins Whether or not the message should be shown both on network and * blog admin pages. * @param bool $is_dismissible */ function add_sticky( $message, $id, $title = '', $type = 'success', $network_level_or_blog_id = null, $wp_user_id = null, $plugin_title = null, $is_network_and_blog_admins = false, $is_dismissible = true, $data = array() ) { $notices = $this->get_site_or_network_notices( $id, $network_level_or_blog_id ); $notices->add_sticky( $message, $id, $title, $type, $wp_user_id, $plugin_title, $is_network_and_blog_admins, $is_dismissible, $data ); } /** * Retrieves the data of a sticky notice. * * @author Leo Fajardo (@leorw) * @since 2.4.3 * * @param string $id * @param int|null $network_level_or_blog_id * * @return array|null */ function get_sticky( $id, $network_level_or_blog_id ) { $notices = $this->get_site_or_network_notices( $id, $network_level_or_blog_id ); return $notices->get_sticky( $id ); } /** * Clear all sticky messages. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param int|null $network_level_or_blog_id * @param bool $is_temporary */ function clear_all_sticky( $network_level_or_blog_id = null, $is_temporary = false ) { if ( ! $this->_is_multisite || false === $network_level_or_blog_id || 0 == $network_level_or_blog_id || is_null( $network_level_or_blog_id ) ) { $notices = $this->get_site_notices( $network_level_or_blog_id ); $notices->clear_all_sticky( $is_temporary ); } if ( $this->_is_multisite && ( true === $network_level_or_blog_id || is_null( $network_level_or_blog_id ) ) ) { $this->_network_notices->clear_all_sticky( $is_temporary ); } } /** * Add admin message to all admin messages queue, and hook to all_admin_notices if not yet hooked. * * @author Vova Feldman (@svovaf) * @since 1.0.4 * * @param string $message * @param string $title * @param string $type * @param bool $is_sticky * @param string $id Message ID */ function add_all( $message, $title = '', $type = 'success', $is_sticky = false, $id = '' ) { $this->add( $message, $title, $type, $is_sticky, true, $id ); } #-------------------------------------------------------------------------------- #region Helper Methods #-------------------------------------------------------------------------------- /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param int $blog_id * * @return FS_Admin_Notice_Manager */ private function get_site_notices( $blog_id = 0 ) { if ( 0 == $blog_id || $blog_id == $this->_blog_id ) { return $this->_notices; } return FS_Admin_Notice_Manager::instance( $this->_id, $this->_title, $this->_module_unique_affix, false, $blog_id ); } /** * Check if the network notices should be used. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $id * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite notices (if there's a network). When `false`, use the current context blog notices. When `null`, the decision which notices manager to use (MS vs. Current S) will be handled internally and determined based on the $id and the context admin (blog admin vs. network level admin). * * @return bool */ private function should_use_network_notices( $id = '', $network_level_or_blog_id = null ) { if ( ! $this->_is_multisite ) { // Not a multisite environment. return false; } if ( is_numeric( $network_level_or_blog_id ) ) { // Explicitly asked to use a specified blog storage. return false; } if ( is_bool( $network_level_or_blog_id ) ) { // Explicitly specified whether should use the network or blog level storage. return $network_level_or_blog_id; } return fs_is_network_admin(); } /** * Retrieves an instance of FS_Admin_Notice_Manager. * * @author Leo Fajardo (@leorw) * @since 2.5.0 * * @param string $id * @param int|null $network_level_or_blog_id * * @return FS_Admin_Notice_Manager */ private function get_site_or_network_notices( $id, $network_level_or_blog_id ) { return $this->should_use_network_notices( $id, $network_level_or_blog_id ) ? $this->_network_notices : $this->get_site_notices( $network_level_or_blog_id ); } #endregion }PK!JJ,libraries/freemius/includes/class-fs-api.phpnu[get_option( 'api_clock_diff', 0 ); Freemius_Api_WordPress::SetClockDiff( self::$_clock_diff ); if ( self::$_options->get_option( 'api_force_http', false ) ) { Freemius_Api_WordPress::SetHttp(); } } /** * @param string $slug * @param string $scope 'app', 'developer', 'user' or 'install'. * @param number $id Element's id. * @param string $public_key Public key. * @param bool|string $secret_key Element's secret key. * @param bool $is_sandbox * @param null|string $sdk_version * @param null|string $url */ private function __construct( $slug, $scope, $id, $public_key, $secret_key, $is_sandbox, $sdk_version, $url ) { $this->_api = new Freemius_Api_WordPress( $scope, $id, $public_key, $secret_key, $is_sandbox ); $this->_slug = $slug; $this->_sdk_version = $sdk_version; $this->_url = $url; $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $slug . '_api', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); } /** * Find clock diff between server and API server, and store the diff locally. * * @param bool|int $diff * * @return bool|int False if clock diff didn't change, otherwise returns the clock diff in seconds. */ private function _sync_clock_diff( $diff = false ) { $this->_logger->entrance(); // Sync clock and store. $new_clock_diff = ( false === $diff ) ? Freemius_Api_WordPress::FindClockDiff() : $diff; if ( $new_clock_diff === self::$_clock_diff ) { return false; } self::$_clock_diff = $new_clock_diff; // Update API clock's diff. Freemius_Api_WordPress::SetClockDiff( self::$_clock_diff ); // Store new clock diff in storage. self::$_options->set_option( 'api_clock_diff', self::$_clock_diff, true ); return $new_clock_diff; } /** * Override API call to enable retry with servers' clock auto sync method. * * @param string $path * @param string $method * @param array $params * @param bool $in_retry Is in retry or first call attempt. * * @return array|mixed|string|void */ private function _call( $path, $method = 'GET', $params = array(), $in_retry = false ) { $this->_logger->entrance( $method . ':' . $path ); $force_http = ( ! $in_retry && self::$_options->get_option( 'api_force_http', false ) ); if ( self::is_temporary_down() ) { $result = $this->get_temporary_unavailable_error(); } else { /** * @since 2.3.0 Include the SDK version with all API requests that going through the API manager. IMPORTANT: Only pass the SDK version if the caller didn't include it yet. */ if ( ! empty( $this->_sdk_version ) ) { if ( false === strpos( $path, 'sdk_version=' ) && ! isset( $params['sdk_version'] ) ) { // Always add the sdk_version param in the querystring. DO NOT INCLUDE IT IN THE BODY PARAMS, OTHERWISE, IT MAY LEAD TO AN UNEXPECTED PARAMS PARSING IN CASES WHERE THE $params IS A REGULAR NON-ASSOCIATIVE ARRAY. $path = add_query_arg( 'sdk_version', $this->_sdk_version, $path ); } } /** * @since 2.5.0 Include the site's URL, if available, in all API requests that are going through the API manager. */ if ( ! empty( $this->_url ) ) { if ( false === strpos( $path, 'url=' ) && ! isset( $params['url'] ) ) { $path = add_query_arg( 'url', $this->_url, $path ); } } $result = $this->_api->Api( $path, $method, $params ); if ( ! $in_retry && null !== $result && isset( $result->error ) && isset( $result->error->code ) ) { $retry = false; if ( 'request_expired' === $result->error->code ) { $diff = isset( $result->error->timestamp ) ? ( time() - strtotime( $result->error->timestamp ) ) : false; // Try to sync clock diff. if ( false !== $this->_sync_clock_diff( $diff ) ) { // Retry call with new synced clock. $retry = true; } } else if ( Freemius_Api_WordPress::IsHttps() && FS_Api::is_ssl_error_response( $result ) ) { $force_http = true; $retry = true; } if ( $retry ) { if ( $force_http ) { $this->toggle_force_http( true ); } $result = $this->_call( $path, $method, $params, true ); } } } if ( self::is_api_error( $result ) ) { if ( $this->_logger->is_on() ) { // Log API errors. $this->_logger->api_error( $result ); } if ( $force_http ) { $this->toggle_force_http( false ); } } return $result; } /** * Override API call to wrap it in servers' clock sync method. * * @param string $path * @param string $method * @param array $params * * @return array|mixed|string|void * @throws Freemius_Exception */ function call( $path, $method = 'GET', $params = array() ) { return $this->_call( $path, $method, $params ); } /** * Get API request URL signed via query string. * * @param string $path * * @return string */ function get_signed_url( $path ) { return $this->_api->GetSignedUrl( $path ); } /** * @param string $path * @param bool $flush * @param int $expiration (optional) Time until expiration in seconds from now, defaults to 24 hours * * @return stdClass|mixed */ function get( $path = '/', $flush = false, $expiration = WP_FS__TIME_24_HOURS_IN_SEC ) { $this->_logger->entrance( $path ); $cache_key = $this->get_cache_key( $path ); // Always flush during development. if ( WP_FS__DEV_MODE || $this->_api->IsSandbox() ) { $flush = true; } $has_valid_cache = self::$_cache->has_valid( $cache_key, $expiration ); $cached_result = $has_valid_cache ? self::$_cache->get( $cache_key ) : null; if ( $flush || is_null( $cached_result ) ) { $result = $this->call( $path ); if ( ! is_object( $result ) || isset( $result->error ) ) { // Api returned an error. if ( is_object( $cached_result ) && ! isset( $cached_result->error ) ) { // If there was an error during a newer data fetch, // fallback to older data version. $result = $cached_result; if ( $this->_logger->is_on() ) { $this->_logger->warn( 'Fallback to cached API result: ' . var_export( $cached_result, true ) ); } } else { if ( is_object( $result ) && isset( $result->error->http ) && 404 == $result->error->http ) { /** * If the response code is 404, cache the result for half of the `$expiration`. * * @author Leo Fajardo (@leorw) * @since 2.2.4 */ $expiration /= 2; } else { // If no older data version and the response code is not 404, return result without // caching the error. return $result; } } } if ( is_numeric( $expiration ) ) { self::$_cache->set( $cache_key, $result, $expiration ); } $cached_result = $result; } else { $this->_logger->log( 'Using cached API result.' ); } return $cached_result; } /** * @todo Remove this method after migrating Freemius::safe_remote_post() to FS_Api::call(). * * @author Leo Fajardo (@leorw) * @since 2.5.4 * * @param string $url * @param array $remote_args * * @return array|WP_Error The response array or a WP_Error on failure. */ static function remote_request( $url, $remote_args ) { if ( ! class_exists( 'Freemius_Api_WordPress' ) ) { require_once WP_FS__DIR_SDK . '/FreemiusWordPress.php'; } if ( method_exists( 'Freemius_Api_WordPress', 'RemoteRequest' ) ) { return Freemius_Api_WordPress::RemoteRequest( $url, $remote_args ); } // The following is for backward compatibility when a modified PHP SDK version is in use and the `Freemius_Api_WordPress:RemoteRequest()` method doesn't exist. $response = wp_remote_request( $url, $remote_args ); if ( is_array( $response ) && ( empty( $response['headers'] ) || empty( $response['headers']['x-api-server'] ) ) ) { // API is considered blocked if the response doesn't include the `x-api-server` header. When there's no error but this header doesn't exist, the response is usually not in the expected form (e.g., cannot be JSON-decoded). $response = new WP_Error( 'api_blocked', htmlentities( $response['body'] ) ); } return $response; } /** * Check if there's a cached version of the API request. * * @author Vova Feldman (@svovaf) * @since 1.2.1 * * @param string $path * @param string $method * @param array $params * * @return bool */ function is_cached( $path, $method = 'GET', $params = array() ) { $cache_key = $this->get_cache_key( $path, $method, $params ); return self::$_cache->has_valid( $cache_key ); } /** * Invalidate a cached version of the API request. * * @author Vova Feldman (@svovaf) * @since 1.2.1.5 * * @param string $path * @param string $method * @param array $params */ function purge_cache( $path, $method = 'GET', $params = array() ) { $this->_logger->entrance( "{$method}:{$path}" ); $cache_key = $this->get_cache_key( $path, $method, $params ); self::$_cache->purge( $cache_key ); } /** * Invalidate a cached version of the API request. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $path * @param int $expiration * @param string $method * @param array $params */ function update_cache_expiration( $path, $expiration = WP_FS__TIME_24_HOURS_IN_SEC, $method = 'GET', $params = array() ) { $this->_logger->entrance( "{$method}:{$path}:{$expiration}" ); $cache_key = $this->get_cache_key( $path, $method, $params ); self::$_cache->update_expiration( $cache_key, $expiration ); } /** * @param string $path * @param string $method * @param array $params * * @return string * @throws \Freemius_Exception */ private function get_cache_key( $path, $method = 'GET', $params = array() ) { $canonized = $this->_api->CanonizePath( $path ); // $exploded = explode('/', $canonized); // return $method . '_' . array_pop($exploded) . '_' . md5($canonized . json_encode($params)); return strtolower( $method . ':' . $canonized ) . ( ! empty( $params ) ? '#' . md5( json_encode( $params ) ) : '' ); } /** * @author Leo Fajardo (@leorw) * @since 2.5.4 * * @param bool $is_http */ private function toggle_force_http( $is_http ) { self::$_options->set_option( 'api_force_http', $is_http, true ); if ( $is_http ) { Freemius_Api_WordPress::SetHttp(); } else if ( method_exists( 'Freemius_Api_WordPress', 'SetHttps' ) ) { Freemius_Api_WordPress::SetHttps(); } } /** * @author Leo Fajardo (@leorw) * @since 2.5.4 * * @param mixed $response * * @return bool */ static function is_blocked( $response ) { return ( self::is_api_error_object( $response, true ) && isset( $response->error->code ) && 'api_blocked' === $response->error->code ); } /** * Check if API is temporary down. * * @author Vova Feldman (@svovaf) * @since 1.1.6 * * @return bool */ static function is_temporary_down() { self::_init(); $test = self::$_cache->get_valid( 'ping_test', null ); return ( false === $test ); } /** * @author Vova Feldman (@svovaf) * @since 1.1.6 * * @return object */ private function get_temporary_unavailable_error() { return (object) array( 'error' => (object) array( 'type' => 'TemporaryUnavailable', 'message' => 'API is temporary unavailable, please retry in ' . ( self::$_cache->get_record_expiration( 'ping_test' ) - WP_FS__SCRIPT_START_TIME ) . ' sec.', 'code' => 'temporary_unavailable', 'http' => 503 ) ); } /** * Check if based on the API result we should try * to re-run the same request with HTTP instead of HTTPS. * * @author Vova Feldman (@svovaf) * @since 1.1.6 * * @param $result * * @return bool */ private static function should_try_with_http( $result ) { if ( ! Freemius_Api_WordPress::IsHttps() ) { return false; } return ( ! is_object( $result ) || ! isset( $result->error ) || ! isset( $result->error->code ) || ! in_array( $result->error->code, array( 'curl_missing', 'cloudflare_ddos_protection', 'maintenance_mode', 'squid_cache_block', 'too_many_requests', ) ) ); } function get_url( $path = '' ) { return Freemius_Api_WordPress::GetUrl( $path, $this->_api->IsSandbox() ); } /** * Clear API cache. * * @author Vova Feldman (@svovaf) * @since 1.0.9 */ static function clear_cache() { self::_init(); self::$_cache = FS_Cache_Manager::get_manager( WP_FS__API_CACHE_OPTION_NAME ); self::$_cache->clear(); } /** * @author Leo Fajardo (@leorw) * @since 2.5.4 */ static function clear_force_http_flag() { self::$_options->unset_option( 'api_force_http' ); } #---------------------------------------------------------------------------------- #region Error Handling #---------------------------------------------------------------------------------- /** * @author Vova Feldman (@svovaf) * @since 1.2.1.5 * * @param mixed $result * * @return bool Is API result contains an error. */ static function is_api_error( $result ) { return ( is_object( $result ) && isset( $result->error ) ) || is_string( $result ); } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param mixed $result * @param bool $ignore_message * * @return bool Is API result contains an error. */ static function is_api_error_object( $result, $ignore_message = false ) { return ( is_object( $result ) && isset( $result->error ) && ( $ignore_message || isset( $result->error->message ) ) ); } /** * @author Leo Fajardo (@leorw) * @since 2.5.4 * * @param WP_Error|object|string $response * * @return bool */ static function is_ssl_error_response( $response ) { $http_error = null; if ( $response instanceof WP_Error ) { if ( isset( $response->errors ) && isset( $response->errors['http_request_failed'] ) ) { $http_error = strtolower( $response->errors['http_request_failed'][0] ); } } else if ( self::is_api_error_object( $response ) && ! empty( $response->error->message ) ) { $http_error = $response->error->message; } return ( ! empty( $http_error ) && ( false !== strpos( $http_error, 'curl error 35' ) || ( false === strpos( $http_error, '' ) && false !== strpos( $http_error, 'ssl' ) ) ) ); } /** * Checks if given API result is a non-empty and not an error object. * * @author Vova Feldman (@svovaf) * @since 1.2.1.5 * * @param mixed $result * @param string|null $required_property Optional property we want to verify that is set. * * @return bool */ static function is_api_result_object( $result, $required_property = null ) { return ( is_object( $result ) && ! isset( $result->error ) && ( empty( $required_property ) || isset( $result->{$required_property} ) ) ); } /** * Checks if given API result is a non-empty entity object with non-empty ID. * * @author Vova Feldman (@svovaf) * @since 1.2.1.5 * * @param mixed $result * * @return bool */ static function is_api_result_entity( $result ) { return self::is_api_result_object( $result, 'id' ) && FS_Entity::is_valid_id( $result->id ); } /** * Get API result error code. If failed to get code, returns an empty string. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param mixed $result * * @return string */ static function get_error_code( $result ) { if ( is_object( $result ) && isset( $result->error ) && is_object( $result->error ) && ! empty( $result->error->code ) ) { return $result->error->code; } return ''; } #endregion }PK!/``$libraries/freemius/includes/l10n.phpnu[ $section ) { ?> $row ) { $col_count = count( $row ); ?>
    :
    PK!M5(((libraries/freemius/templates/pricing.phpnu[get_slug(); $timestamp = time(); $context_params = array( 'plugin_id' => $fs->get_id(), 'plugin_public_key' => $fs->get_public_key(), 'plugin_version' => $fs->get_plugin_version(), ); $bundle_id = $fs->get_bundle_id(); if ( ! is_null( $bundle_id ) ) { $context_params['bundle_id'] = $bundle_id; } // Get site context secure params. if ( $fs->is_registered() ) { $context_params = array_merge( $context_params, FS_Security::instance()->get_context_params( $fs->get_site(), $timestamp, 'upgrade' ) ); } else { $context_params['home_url'] = home_url(); } if ( $fs->is_payments_sandbox() ) // Append plugin secure token for sandbox mode authentication.) { $context_params['sandbox'] = FS_Security::instance()->get_secure_token( $fs->get_plugin(), $timestamp, 'checkout' ); } $query_params = array_merge( $context_params, $_GET, array( 'next' => $fs->_get_sync_license_url( false, false ), 'plugin_version' => $fs->get_plugin_version(), // Billing cycle. 'billing_cycle' => fs_request_get( 'billing_cycle', WP_FS__PERIOD_ANNUALLY ), 'is_network_admin' => fs_is_network_admin() ? 'true' : 'false', 'currency' => $fs->apply_filters( 'default_currency', 'usd' ), 'discounts_model' => $fs->apply_filters( 'pricing/discounts_model', 'absolute' ), ) ); $pricing_js_url = fs_asset_url( $fs->get_pricing_js_path() ); wp_enqueue_script( 'freemius-pricing', $pricing_js_url ); $pricing_css_path = $fs->apply_filters( 'pricing/css_path', null ); if ( is_string( $pricing_css_path ) ) { wp_enqueue_style( 'freemius-pricing', fs_asset_url( $pricing_css_path ) ); } $has_tabs = $fs->_add_tabs_before_content(); if ( $has_tabs ) { $query_params['tabs'] = 'true'; } ?>
    $fs->contact_url(), 'is_production' => ( defined( 'WP_FS__IS_PRODUCTION_MODE' ) ? WP_FS__IS_PRODUCTION_MODE : null ), 'menu_slug' => $fs->get_menu_slug(), 'mode' => 'dashboard', 'fs_wp_endpoint_url' => WP_FS__ADDRESS, 'request_handler_url' => admin_url( 'admin-ajax.php?' . http_build_query( array( 'module_id' => $fs->get_id(), 'action' => $fs->get_ajax_action( 'pricing_ajax_action' ), 'security' => $fs->get_ajax_security( 'pricing_ajax_action' ) ) ) ), 'selector' => '#fs_pricing_wrapper', 'unique_affix' => $fs->get_unique_affix(), 'show_annual_in_monthly' => $fs->apply_filters( 'pricing/show_annual_in_monthly', true ), 'license' => $fs->has_active_valid_license() ? $fs->_get_license() : null, 'plugin_icon' => $fs->get_local_icon_url(), 'disable_single_package' => $fs->apply_filters( 'pricing/disable_single_package', false ), ), $query_params ); wp_add_inline_script( 'freemius-pricing', 'Freemius.pricing.new( ' . json_encode( $pricing_config ) . ' )' ); ?>
    _add_tabs_after_content(); } PK!0 /libraries/freemius/templates/partials/index.phpnu[get_slug(); $sites = $VARS['sites']; $require_license_key = $VARS['require_license_key']; $show_delegation_option = $fs->apply_filters( 'show_delegation_option', true ); $enable_per_site_activation = $fs->apply_filters( 'enable_per_site_activation', true ); ?> |' ?> PK!V V 4libraries/freemius/templates/clone-resolution-js.phpnu[ PK!I)libraries/freemius/templates/checkout.phpnu[is_premium() ) { fs_require_template( 'checkout/frame.php', $VARS ); } else { fs_require_template( 'checkout/redirect.php', $VARS ); } } PK!ZM4libraries/freemius/templates/secure-https-header.phpnu[
    get_text_inline( 'Secure HTTPS %s page, running from an external domain', 'secure-x-page-header' ), $VARS['page'] ) ) . ' - ' . sprintf( '%s', 'https://www.mcafeesecure.com/verify?host=' . WP_FS__ROOT_DOMAIN_PRODUCTION, 'Freemius Inc. [US]' ); } ?>
    PK!A MM(libraries/freemius/templates/contact.phpnu[get_slug(); $query_params = FS_Contact_Form_Manager::instance()->get_query_params( $fs ); $view_params = array( 'id' => $VARS['id'], 'page' => strtolower( $fs->get_text_inline( 'Contact', 'contact' ) ), ); fs_require_once_template('secure-https-header.php', $view_params); $has_tabs = $fs->_add_tabs_before_content(); if ( $has_tabs ) { $query_params['tabs'] = 'true'; } ?>
    _add_tabs_after_content(); } PK!Gao0libraries/freemius/templates/debug/api-calls.phpnu[ 0, 'POST' => 0, 'PUT' => 0, 'DELETE' => 0 ); $show_body = false; foreach ( $logger as $log ) { $counters[ $log['method'] ] ++; if ( ! is_null( $log['body'] ) ) { $show_body = true; } } $pretty_print = $show_body && defined( 'JSON_PRETTY_PRINT' ) && version_compare( phpversion(), '5.3', '>=' ); /** * This template is used for debugging, therefore, when possible * we'd like to prettify the output of a JSON encoded variable. * This will only be executed when $pretty_print is `true`, and * the var is `true` only for PHP 5.3 and higher. Due to the * limitations of the current Theme Check, it throws an error * that using the "options" parameter (the 2nd param) is not * supported in PHP 5.2 and lower. Thus, we added this alias * variable to work around that false-positive. * * @author Vova Feldman (@svovaf) * @since 1.2.2.7 */ $encode = 'json_encode'; $root_path_len = strlen( ABSPATH ); $ms_text = fs_text_x_inline( 'ms', 'milliseconds' ); ?>

    Total Time:

    Total Requests:

    $count ) : ?>

    :

    #
    . %s', $log['path'] ); ?> %s', substr( $body, 0, 32 ) . ( 32 < strlen( $body ) ? '...' : '' ) ); if ( $pretty_print ) { $body = $encode( json_decode( $log['body'] ), JSON_PRETTY_PRINT ); } ?>
    %s', substr( $result, 0, 32 ) . ( 32 < strlen( $result ) ? '...' : '' ) ); } if ( $is_not_empty_result && $pretty_print ) { $decoded = json_decode( $result ); if ( ! is_null( $decoded ) ) { $result = $encode( $decoded, JSON_PRETTY_PRINT ); } } else { $result = is_string( $result ) ? $result : json_encode( $result ); } ?> style="display: none">
    PK!$$F :libraries/freemius/templates/debug/plugins-themes-sync.phpnu[get_option( 'all_plugins' ); $all_themes = $fs_options->get_option( 'all_themes' ); /* translators: %s: time period (e.g. In "2 hours") */ $in_x_text = fs_text_inline( 'In %s', 'in-x' ); /* translators: %s: time period (e.g. "2 hours" ago) */ $x_ago_text = fs_text_inline( '%s ago', 'x-ago' ); $sec_text = fs_text_x_inline( 'sec', 'seconds' ); ?>

    plugins ) ?> timestamp ) && is_numeric( $all_plugins->timestamp ) ) { $diff = abs( WP_FS__SCRIPT_START_TIME - $all_plugins->timestamp ); $human_diff = ( $diff < MINUTE_IN_SECONDS ) ? $diff . ' ' . $sec_text : human_time_diff( WP_FS__SCRIPT_START_TIME, $all_plugins->timestamp ); echo esc_html( sprintf( ( ( WP_FS__SCRIPT_START_TIME < $all_plugins->timestamp ) ? $in_x_text : $x_ago_text ), $human_diff ) ); } ?>
    themes ) ?> timestamp ) && is_numeric( $all_themes->timestamp ) ) { $diff = abs( WP_FS__SCRIPT_START_TIME - $all_themes->timestamp ); $human_diff = ( $diff < MINUTE_IN_SECONDS ) ? $diff . ' ' . $sec_text : human_time_diff( WP_FS__SCRIPT_START_TIME, $all_themes->timestamp ); echo esc_html( sprintf( ( ( WP_FS__SCRIPT_START_TIME < $all_themes->timestamp ) ? $in_x_text : $x_ago_text ), $human_diff ) ); } ?>
    PK!ru}6libraries/freemius/templates/debug/scheduled-crons.phpnu[get_option( $module_type . 's' ), FS_Plugin::get_class_name() ); if ( is_array( $modules ) && count( $modules ) > 0 ) { foreach ( $modules as $slug => $data ) { if ( WP_FS__MODULE_TYPE_THEME === $module_type ) { $current_theme = wp_get_theme(); $is_active = ( $current_theme->stylesheet === $data->file ); } else { $is_active = is_plugin_active( $data->file ); } /** * @author Vova Feldman * * @since 1.2.1 Don't load data from inactive modules. */ if ( $is_active ) { $fs = freemius( $data->id ); $next_execution = $fs->next_sync_cron(); $last_execution = $fs->last_sync_cron(); if ( false !== $next_execution ) { $scheduled_crons[ $slug ][] = array( 'name' => $fs->get_plugin_name(), 'slug' => $slug, 'module_type' => $fs->get_module_type(), 'type' => 'sync_cron', 'last' => $last_execution, 'next' => $next_execution, ); } $next_install_execution = $fs->next_install_sync(); $last_install_execution = $fs->last_install_sync(); if (false !== $next_install_execution || false !== $last_install_execution ) { $scheduled_crons[ $slug ][] = array( 'name' => $fs->get_plugin_name(), 'slug' => $slug, 'module_type' => $fs->get_module_type(), 'type' => 'install_sync', 'last' => $last_install_execution, 'next' => $next_install_execution, ); } } } } } $sec_text = fs_text_x_inline( 'sec', 'seconds' ); ?>

    $crons ) : ?>
    PK!p2WW,libraries/freemius/templates/debug/index.phpnu[

    >
    #
    . get_id() ?> %s', esc_html( substr( $log['msg'], 0, 32 ) ) . ( 32 < strlen( $log['msg'] ) ? '...' : '' ) ); ?>
    get_file() ) . ':' . $log['line']; } ?>
    PK!t85libraries/freemius/templates/add-trial-to-pricing.phpnu[ PK!b0libraries/freemius/templates/tabs-capture-js.phpnu[get_slug(); ?> PK! ɿ%libraries/freemius/templates/tabs.phpnu[get_slug(); $menu_items = $fs->get_menu_items(); $show_settings_with_tabs = $fs->show_settings_with_tabs(); $tabs = array(); foreach ( $menu_items as $priority => $items ) { foreach ( $items as $item ) { if ( ! $item['show_submenu'] ) { $submenu_name = ('wp-support-forum' === $item['menu_slug']) ? 'support' : $item['menu_slug']; if ( 'pricing' === $submenu_name && ! $fs->is_pricing_page_visible() ) { continue; } if ( ! $show_settings_with_tabs || ! $fs->is_submenu_item_visible( $submenu_name, true ) ) { continue; } } $url = $fs->_get_admin_page_url( $item['menu_slug'] ); $title = $item['menu_title']; $tab = array( 'label' => $title, 'href' => $url, 'slug' => $item['menu_slug'], ); if ( 'pricing' === $item['menu_slug'] && $fs->is_in_trial_promotion() ) { $tab['href'] .= '&trial=true'; } $tabs[] = $tab; } } ?> PK!/libraries/freemius/templates/checkout/index.phpnu[get_slug(); $fs_checkout = FS_Checkout_Manager::instance(); $plugin_id = fs_request_get( 'plugin_id' ); if ( ! FS_Plugin::is_valid_id( $plugin_id ) ) { $plugin_id = $fs->get_id(); } $plan_id = fs_request_get( 'plan_id' ); $licenses = fs_request_get( 'licenses' ); $query_params = $fs_checkout->get_query_params( $fs, $plugin_id, $plan_id, $licenses ); $return_url = $fs->_get_sync_license_url( $plugin_id ); $query_params['return_url'] = $return_url; $xdebug_session = fs_request_get( 'XDEBUG_SESSION' ); if ( false !== $xdebug_session ) { $query_params['XDEBUG_SESSION'] = $xdebug_session; } $view_params = array( 'id' => $VARS['id'], 'page' => strtolower( $fs->get_text_inline( 'Checkout', 'checkout' ) ) . ' ' . $fs->get_text_inline( 'PCI compliant', 'pci-compliant' ), ); fs_require_once_template('secure-https-header.php', $view_params); ?>
    PK!׺EY Y :libraries/freemius/templates/checkout/process-redirect.phpnu[get_id(); } $fs_checkout->verify_checkout_redirect_nonce( $fs ); wp_enqueue_script( 'jquery' ); wp_enqueue_script( 'json2' ); fs_enqueue_local_script( 'fs-form', 'jquery.form.js', array( 'jquery' ) ); $action = fs_request_get( '_fs_checkout_action' ); $data = json_decode( fs_request_get_raw( '_fs_checkout_data' ) ); ?>

    PK!U@pp p 2libraries/freemius/templates/checkout/redirect.phpnu[get_id(); } $plan_id = fs_request_get( 'plan_id' ); $licenses = fs_request_get( 'licenses' ); $query_params = $fs_checkout->get_query_params( $fs, $plugin_id, $plan_id, $licenses ); // The return URL is a special page which will process the result. $return_url = $fs_checkout->get_checkout_redirect_return_url( $fs ); $query_params['return_url'] = $return_url; // Add the cancel URL to the same pricing page the request originated from. $query_params['cancel_url'] = $fs->pricing_url( fs_request_get( 'billing_cycle', 'annual' ), fs_request_get_bool( 'trial' ) ); if ( has_site_icon() ) { $query_params['cancel_icon'] = get_site_icon_url(); } // If the user didn't connect his account with Freemius, // once he accepts the Terms of Service and Privacy Policy, // and then click the purchase button, the context information // of the user will be shared with Freemius in order to complete the // purchase workflow and activate the license for the right user. $install_data = array_merge( $fs->get_opt_in_params(), array( 'activation_url' => fs_nonce_url( $fs->_get_admin_page_url( '', array( 'fs_action' => $fs->get_unique_affix() . '_activate_new', 'plugin_id' => $plugin_id, ) ), $fs->get_unique_affix() . '_activate_new' ), ) ); $query_params['install_data'] = json_encode( $install_data ); $query_params['_fs_dashboard_independent'] = true; $redirect_url = $fs_checkout->get_full_checkout_url( $query_params ); if ( ! fs_redirect( $redirect_url ) ) { // The Header was sent, so the server redirect failed. Rely on JS instead. ?>

    click here if you\'re stuck...' ), esc_url( $redirect_url ) ), array( 'a' => array( 'href' => true ) ) ); ?>

    get_slug(); $open_addon_slug = fs_request_get( 'slug' ); $open_addon = false; $is_data_debug_mode = $fs->is_data_debug_mode(); $is_whitelabeled = $fs->is_whitelabeled(); /** * @var FS_Plugin[] */ $addons = $fs->get_addons(); $has_addons = ( is_array( $addons ) && 0 < count( $addons ) ); $account_addon_ids = $fs->get_updated_account_addons(); $download_latest_text = fs_text_x_inline( 'Download Latest', 'as download latest version', 'download-latest', $slug ); $view_details_text = fs_text_inline( 'View details', 'view-details', $slug ); $has_tabs = $fs->_add_tabs_before_content(); $fs_blog_id = ( is_multisite() && ! is_network_admin() ) ? get_current_blog_id() : 0; ?>

    get_plugin_name() ) ) ?>

    do_action( 'addons/after_title' ) ?>

      _get_addons_plans_and_pricing_map_by_id(); $active_plugins_directories_map = Freemius::get_active_plugins_directories_map( $fs_blog_id ); ?> is_whitelabeled_by_flag() ) { $hide_all_addons_data = true; $addon_ids = $fs->get_updated_account_addons(); $installed_addons = $fs->get_installed_addons(); foreach ( $installed_addons as $fs_addon ) { $addon_ids[] = $fs_addon->get_id(); } if ( ! empty( $addon_ids ) ) { $addon_ids = array_unique( $addon_ids ); } foreach ( $addon_ids as $addon_id ) { $addon = $fs->get_addon( $addon_id ); if ( ! is_object( $addon ) ) { continue; } $addon_storage = FS_Storage::instance( WP_FS__MODULE_TYPE_PLUGIN, $addon->slug ); if ( ! $addon_storage->is_whitelabeled ) { $hide_all_addons_data = false; break; } if ( $is_data_debug_mode ) { $is_whitelabeled = false; } } } ?> get_addon_basename( $addon->id ); $is_addon_installed = file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $basename ) ); if ( ! $is_addon_installed && $hide_all_addons_data ) { continue; } $is_addon_activated = $is_addon_installed ? $fs->is_addon_activated( $addon->id ) : false; $is_plugin_active = ( $is_addon_activated || isset( $active_plugins_directories_map[ dirname( $basename ) ] ) ); $open_addon = ( $open_addon || ( $open_addon_slug === $addon->slug ) ); $price = 0; $has_trial = false; $has_free_plan = false; $has_paid_plan = false; if ( isset( $plans_and_pricing_by_addon_id[$addon->id] ) ) { $plans = $plans_and_pricing_by_addon_id[$addon->id]; if ( is_array( $plans ) && 0 < count( $plans ) ) { foreach ( $plans as $plan ) { if ( ! isset( $plan->pricing ) || ! is_array( $plan->pricing ) || 0 == count( $plan->pricing ) ) { // No pricing means a free plan. $has_free_plan = true; continue; } $has_paid_plan = true; $has_trial = $has_trial || ( is_numeric( $plan->trial_period ) && ( $plan->trial_period > 0 ) ); $min_price = 999999; foreach ( $plan->pricing as $pricing ) { $pricing = new FS_Pricing( $pricing ); if ( ! $pricing->is_usd() ) { /** * Skip non-USD pricing. * * @author Leo Fajardo (@leorw) * @since 2.3.1 */ continue; } if ( $pricing->has_annual() ) { $min_price = min( $min_price, $pricing->annual_price ); } else if ( $pricing->has_monthly() ) { $min_price = min( $min_price, 12 * $pricing->monthly_price ); } } if ( $min_price < 999999 ) { $price = $min_price; } } } if ( ! $has_paid_plan && ! $has_free_plan ) { continue; } } ?>
    • get_id() . '&plugin=' . $addon->slug . '&TB_iframe=true&width=600&height=550' ) ), esc_attr( sprintf( fs_text_inline( 'More information about %s', 'more-information-about-x', $slug ), $addon->title ) ), esc_attr( $addon->title ) ) . ' class="thickbox%s">%s'; echo sprintf( $view_details_link, /** * Additional class. * * @author Leo Fajardo (@leorw) * @since 2.2.4 */ ' fs-overlay', /** * Set the view details link text to an empty string since it is an overlay that * doesn't really need a text and whose purpose is to open the details dialog when * the card is clicked. * * @author Leo Fajardo (@leorw) * @since 2.2.4 */ '' ); ?> info ) ) { $addon->info = new stdClass(); } if ( ! isset( $addon->info->card_banner_url ) ) { $addon->info->card_banner_url = '//dashboard.freemius.com/assets/img/marketing/blueprint-300x100.jpg'; } if ( ! isset( $addon->info->short_description ) ) { $addon->info->short_description = 'What\'s the one thing your add-on does really, really well?'; } ?>
      • %s', esc_html( $is_plugin_active ? fs_text_x_inline( 'Active', 'active add-on', 'active-addon', $slug ) : fs_text_x_inline( 'Installed', 'installed add-on', 'installed-addon', $slug ) ) ); } ?>
      • title ?>
      • 0) $descriptors[] = '$' . number_format( $price, 2 ); if ($has_trial) $descriptors[] = fs_text_x_inline( 'Trial', 'trial period', 'trial', $slug ); echo implode(' - ', $descriptors); } ?>
      • info->short_description ) ? $addon->info->short_description : 'SHORT DESCRIPTION' ?>
      • is_wp_org_compliant ); $is_allowed_to_install = ( $fs->is_allowed_to_install() || $is_free_only_wp_org_compliant ); $show_premium_activation_or_installation_action = true; if ( ! in_array( $addon->id, $account_addon_ids ) ) { $show_premium_activation_or_installation_action = false; } else if ( $is_addon_installed ) { /** * If any add-on's version (free or premium) is installed, check if the * premium version can be activated and show the relevant action. Otherwise, * show the relevant action for the free version. * * @author Leo Fajardo (@leorw) * @since 2.4.5 */ $fs_addon = $is_addon_activated ? $fs->get_addon_instance( $addon->id ) : null; $premium_plugin_basename = is_object( $fs_addon ) ? $fs_addon->premium_plugin_basename() : "{$addon->premium_slug}/{$addon->slug}.php"; if ( ( $is_addon_activated && $fs_addon->is_premium() ) || file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $premium_plugin_basename ) ) ) { $basename = $premium_plugin_basename; } $show_premium_activation_or_installation_action = ( ( ! $is_addon_activated || ! $fs_addon->is_premium() ) && /** * This check is needed for cases when an active add-on doesn't have an * associated Freemius instance. * * @author Leo Fajardo (@leorw) * @since 2.4.5 */ ( ! $is_plugin_active ) ); } ?>
      • _get_latest_download_local_url( $addon->id ); ?>
      • %s', wp_nonce_url( self_admin_url( 'update.php?' . ( ( $has_paid_plan || ! $addon->is_wp_org_compliant ) ? 'fs_allow_updater_and_dialog=true&' : '' ) . 'action=install-plugin&plugin=' . $addon->slug ), 'install-plugin_' . $addon->slug ), fs_esc_html_inline( 'Install Now', 'install-now', $slug ) ); } else { echo sprintf( '%s', wp_nonce_url( 'plugins.php?action=activate&plugin=' . $basename, 'activate-plugin_' . $basename ), fs_esc_attr_inline( 'Activate this add-on', 'activate-this-addon', $addon->slug ), fs_text_inline( 'Activate', 'activate', $addon->slug ) ); } ?>
    do_action( 'addons/after_addons' ) ?>
    _add_tabs_after_content(); } PK!''@libraries/freemius/templates/forms/subscription-cancellation.phpnu[get_slug(); /** * @var FS_Plugin_License $license */ $license = $VARS['license']; $has_trial = $VARS['has_trial']; $subscription_cancellation_context = $has_trial ? fs_text_inline( 'trial', 'trial', $slug ) : fs_text_inline( 'subscription', 'subscription', $slug ); $plan = $fs->get_plan(); $module_label = $fs->get_module_label( true ); if ( $VARS['is_license_deactivation'] ) { $subscription_cancellation_text = ''; } else { $subscription_cancellation_text = sprintf( fs_text_inline( "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site.", 'deactivation-or-uninstall-message', $slug ), $module_label ) . ' '; } $subscription_cancellation_text .= sprintf( fs_text_inline( 'In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?', 'cancel-subscription-message', $slug ), ( $VARS['is_license_deactivation'] ? fs_text_inline( 'license', 'license', $slug ) : $module_label ), $subscription_cancellation_context ); $cancel_subscription_action_label = sprintf( fs_esc_html_inline( "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site.", 'cancel-x', $slug ), esc_html( $subscription_cancellation_context ), sprintf( '%s', esc_html( $fs->get_plugin_title() ) ), esc_html( $module_label ) ); $keep_subscription_active_action_label = esc_html( sprintf( fs_text_inline( "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support.", 'dont-cancel-x', $slug ), $subscription_cancellation_context ) ); $subscription_cancellation_text = esc_html( $subscription_cancellation_text ); $subscription_cancellation_html = <<< HTML

    {$subscription_cancellation_text}

    HTML; $downgrading_plan_text = fs_text_inline( 'Downgrading your plan', 'downgrading-plan', $slug ); $cancelling_subscription_text = fs_text_inline( 'Cancelling the subscription', 'cancelling-subscription', $slug ); /* translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' */ $downgrade_x_confirm_text = fs_text_inline( '%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s.', 'downgrade-x-confirm', $slug ); $prices_increase_text = fs_text_inline( 'Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.', 'pricing-increase-warning', $slug ); $after_downgrade_non_blocking_text = fs_text_inline( 'You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.', 'after-downgrade-non-blocking', $slug ); $after_downgrade_blocking_text = fs_text_inline( 'Once your license expires you can still use the Free version but you will NOT have access to the %s features.', 'after-downgrade-blocking', $slug ); $after_downgrade_blocking_text_premium_only = fs_text_inline( 'Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license.', 'after-downgrade-blocking-premium-only', $slug ); $subscription_cancellation_confirmation_message = $has_trial ? fs_text_inline( 'Cancelling the trial will immediately block access to all premium features. Are you sure?', 'cancel-trial-confirm', $slug ) : sprintf( '%s %s %s %s', sprintf( $downgrade_x_confirm_text, ($fs->is_only_premium() ? $cancelling_subscription_text : $downgrading_plan_text ), $plan->title, human_time_diff( time(), strtotime( $license->expiration ) ) ), ( $license->is_block_features ? ( $fs->is_only_premium() ? sprintf( $after_downgrade_blocking_text_premium_only, $module_label ) : sprintf( $after_downgrade_blocking_text, $plan->title ) ) : sprintf( $after_downgrade_non_blocking_text, $plan->title, $fs->get_module_label( true ) ) ), $prices_increase_text, fs_esc_attr_inline( 'Are you sure you want to proceed?', 'proceed-confirmation', $slug ) ); fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); ?> PK!ǛDGlibraries/freemius/templates/forms/premium-versions-upgrade-handler.phpnu[get_slug(); $plugin_data = $fs->get_plugin_data(); $plugin_name = $plugin_data['Name']; $plugin_basename = $fs->get_plugin_basename(); $license = $fs->_get_license(); if ( ! is_object( $license ) ) { $purchase_url = $fs->pricing_url(); } else { $subscription = $fs->_get_subscription( $license->id ); $purchase_url = $fs->checkout_url( is_object( $subscription ) ? ( 1 == $subscription->billing_cycle ? WP_FS__PERIOD_MONTHLY : WP_FS__PERIOD_ANNUALLY ) : WP_FS__PERIOD_LIFETIME, false, array( 'licenses' => $license->quota ) ); } $message = sprintf( fs_text_inline( 'There is a new version of %s available.', 'new-version-available-message', $slug ) . fs_text_inline( ' %s to access version %s security & feature updates, and support.', 'x-for-updates-and-support', $slug ), '', sprintf( '%s', is_object( $license ) ? fs_text_inline( 'Renew your license now', 'renew-license-now', $slug ) : fs_text_inline( 'Buy a license now', 'buy-license-now', $slug ) ), '' ); $modal_content_html = "

    {$message}

    "; $header_title = fs_text_inline( 'New Version Available', 'new-version-available', $slug ); $renew_license_button_text = is_object( $license ) ? fs_text_inline( 'Renew license', 'renew-license', $slug ) : fs_text_inline( 'Buy license', 'buy-license', $slug ); fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); ?> PK! g/g/;libraries/freemius/templates/forms/email-address-update.phpnu[get_slug(); $user = $fs->get_user(); $current_email_address = $user->email; fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); ?> PK!zVV2libraries/freemius/templates/forms/affiliation.phpnu[get_slug(); $user = $fs->get_user(); $affiliate = $fs->get_affiliate(); $affiliate_terms = $fs->get_affiliate_terms(); $module_type = $fs->is_plugin() ? WP_FS__MODULE_TYPE_PLUGIN : WP_FS__MODULE_TYPE_THEME; $commission = $affiliate_terms->get_formatted_commission(); $readonly = false; $is_affiliate = is_object( $affiliate ); $is_pending_affiliate = false; $email_address = ( is_object( $user ) ? $user->email : '' ); $full_name = ( is_object( $user ) ? $user->get_name() : '' ); $paypal_email_address = ''; $domain = ''; $extra_domains = array(); $promotion_method_social_media = false; $promotion_method_mobile_apps = false; $statistics_information = false; $promotion_method_description = false; $members_dashboard_login_url = 'https://users.freemius.com/login'; $affiliate_application_data = $fs->get_affiliate_application_data(); if ( $is_affiliate && $affiliate->is_pending() ) { $readonly = 'readonly'; $is_pending_affiliate = true; $paypal_email_address = $affiliate->paypal_email; $domain = $affiliate->domain; $statistics_information = $affiliate_application_data['stats_description']; $promotion_method_description = $affiliate_application_data['promotion_method_description']; if ( ! empty( $affiliate_application_data['additional_domains'] ) ) { $extra_domains = $affiliate_application_data['additional_domains']; } if ( ! empty( $affiliate_application_data['promotion_methods'] ) ) { $promotion_methods = explode( ',', $affiliate_application_data['promotion_methods'] ); $promotion_method_social_media = in_array( 'social_media', $promotion_methods ); $promotion_method_mobile_apps = in_array( 'mobile_apps', $promotion_methods ); } } else { if ( ! is_object( $user ) ) { $current_user = Freemius::_get_current_wp_user(); $full_name = trim( $current_user->user_firstname . ' ' . $current_user->user_lastname ); $email_address = $current_user->user_email; } $domain = Freemius::get_unfiltered_site_url( null, true ); } $affiliate_tracking = 30; if ( is_object( $affiliate_terms ) ) { $affiliate_tracking = ( ! is_null( $affiliate_terms->cookie_days ) ? ( $affiliate_terms->cookie_days . '-day' ) : fs_text_inline( 'Non-expiring', 'non-expiring', $slug ) ); } $apply_to_become_affiliate_text = fs_text_inline( 'Apply to become an affiliate', 'apply-to-become-an-affiliate', $slug ); $module_id = $fs->get_id(); $affiliate_program_terms_url = "https://freemius.com/plugin/{$module_id}/{$slug}/legal/affiliate-program/"; $has_tabs = $fs->_add_tabs_before_content(); ?>
    is_active() ) : ?>

    %s', $members_dashboard_login_url, $members_dashboard_login_url ) ); ?>

    is_suspended() ) { $message_text = fs_text_inline( 'Your affiliation account was temporarily suspended.', 'affiliate-account-suspended', $slug ); $message_container_class = 'notice notice-warning'; } else if ( $affiliate->is_rejected() ) { $message_text = fs_text_inline( "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days.", 'affiliate-application-rejected', $slug ); $message_container_class = 'error'; } else if ( $affiliate->is_blocked() ) { $message_text = fs_text_inline( 'Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.', 'affiliate-account-blocked', $slug ); $message_container_class = 'error'; } ?>

    • has_renewals_commission() ) : ?>
    • is_session_cookie() ) ) : ?>
    • has_lifetime_commission() ) : ?>
    >

    >
    >
    >
    >

    + ...
    >

    >
    />
    />

    _add_tabs_after_content(); } PK!TF9PPHlibraries/freemius/templates/forms/premium-versions-upgrade-metadata.phpnu[_get_license(); if ( ! is_object( $license ) ) { $purchase_url = $fs->pricing_url(); } else { $subscription = $fs->_get_subscription( $license->id ); $purchase_url = $fs->checkout_url( is_object( $subscription ) ? ( 1 == $subscription->billing_cycle ? WP_FS__PERIOD_MONTHLY : WP_FS__PERIOD_ANNUALLY ) : WP_FS__PERIOD_LIFETIME, false, array( 'licenses' => $license->quota ) ); } $plugin_data = $fs->get_plugin_data(); ?> PK!|2libraries/freemius/templates/forms/trial-start.phpnu[get_slug(); $message_header = sprintf( /* translators: %1$s: Number of trial days; %2$s: Plan name; */ fs_text_inline( 'You are 1-click away from starting your %1$s-day free trial of the %2$s plan.', 'start-trial-prompt-header', $slug ), '', '' ); $message_content = sprintf( /* translators: %s: Link to freemius.com */ fs_text_inline( 'For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.', 'start-trial-prompt-message', $slug ), $fs->get_module_type(), sprintf( '%s', 'https://freemius.com', 'freemius.com' ) ); $modal_content_html = <<< HTML

    {$message_header}

    {$message_content}

    HTML; fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); ?> PK!p2WW,libraries/freemius/templates/forms/index.phpnu[get_slug(); $unique_affix = $fs->get_unique_affix(); $last_license_user_id = $fs->get_last_license_user_id(); $has_last_license_user_id = FS_User::is_valid_id( $last_license_user_id ); $message_above_input_field = ( ! $has_last_license_user_id ) ? fs_text_inline( 'Please enter the license key to enable the debug mode:', 'submit-developer-license-key-message', $slug ) : sprintf( fs_text_inline( 'To enter the debug mode, please enter the secret key of the license owner (UserID = %d), which you can find in your "My Profile" section of your User Dashboard:', 'submit-addon-developer-key-message', $slug ), $last_license_user_id ); $processing_text = ( fs_esc_js_inline( 'Processing', 'processing', $slug ) . '...' ); $submit_button_text = fs_text_inline( 'Submit', 'submit', $slug ); $debug_license_link_text = fs_esc_html_inline( 'Start Debug', 'start-debug-license', $slug ); $license_or_user_key_text = ( ! $has_last_license_user_id ) ? fs_text_inline( 'License key', 'license-key' , $slug ) : fs_text_inline( 'User key', 'user-key' , $slug ); $input_html = ""; $modal_content_html = <<< HTML

    {$message_above_input_field}

    {$input_html} HTML; fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); ?> PK!+1@81libraries/freemius/templates/forms/resend-key.phpnu[get_slug(); $send_button_text = fs_text_inline( 'Send License Key', 'send-license-key', $slug ); $cancel_button_text = fs_text_inline( 'Cancel', 'cancel', $slug ); $email_address_placeholder = fs_esc_attr_inline( 'Email address', 'email-address', $slug ); $other_text = fs_text_inline( 'Other', 'other', $slug ); $is_freemium = $fs->is_freemium(); $send_button_text_html = esc_html($send_button_text); $button_html = <<< HTML HTML; if ( $is_freemium ) { $current_user = Freemius::_get_current_wp_user(); $email = $current_user->user_email; $esc_email = esc_attr( $email ); $form_html = <<< HTML {$button_html} HTML; } else { $email = ''; $form_html = <<< HTML {$button_html} HTML; } $message_above_input_field = $fs->is_only_premium() ? fs_esc_html_inline( "Enter the email address you've used during the purchase and we will resend you the license key.", 'ask-for-upgrade-email-address-premium-only', $slug ) : fs_esc_html_inline( "Enter the email address you've used for the upgrade below and we will resend you the license key.", 'ask-for-upgrade-email-address', $slug ); $modal_content_html = <<< HTML

    {$message_above_input_field}

    {$form_html}
    HTML; fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); ?> PK!A   -libraries/freemius/templates/forms/optout.phpnu[get_slug(); $reconnect_url = $fs->get_activation_url( array( 'nonce' => wp_create_nonce( $fs->get_unique_affix() . '_reconnect' ), 'fs_action' => ( $fs->get_unique_affix() . '_reconnect' ), ) ); $plugin_title = "" . esc_html( $fs->get_plugin()->title ) . ""; $opt_out_text = fs_text_x_inline( 'Opt Out', 'verb', 'opt-out', $slug ); $permission_manager = FS_Permission_Manager::instance( $fs ); fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); fs_enqueue_local_style( 'fs_optout', '/admin/optout.css' ); fs_enqueue_local_style( 'fs_common', '/admin/common.css' ); if ( ! $permission_manager->is_premium_context() ) { $optional_permissions = array( $permission_manager->get_extensions_permission( false, false, true ) ); $permission_groups = array( array( 'id' => 'communication', 'type' => 'required', 'title' => $fs->get_text_inline( 'Communication', 'communication' ), 'desc' => '', 'permissions' => $permission_manager->get_opt_in_required_permissions( true ), 'is_enabled' => $fs->is_registered(), 'prompt' => array( $fs->esc_html_inline( "Sharing your name and email allows us to keep you in the loop about new features and important updates, warn you about security issues before they become public knowledge, and send you special offers.", 'opt-out-message_user' ), sprintf( $fs->esc_html_inline( 'By clicking "Opt Out", %s will no longer be able to view your name and email.', 'opt-out-message-clicking-opt-out' ), $plugin_title ), ), 'prompt_cancel_label' => $fs->get_text_inline( 'Stay Connected', 'stay-connected' ) ), array( 'id' => 'diagnostic', 'type' => 'required', 'title' => $fs->get_text_inline( 'Diagnostic Info', 'diagnostic-info' ), 'desc' => '', 'permissions' => $permission_manager->get_opt_in_diagnostic_permissions( true ), 'is_enabled' => $fs->is_tracking_allowed(), 'prompt' => array( sprintf( $fs->esc_html_inline( 'Sharing diagnostic data helps to provide additional functionality that\'s relevant to your website, avoid WordPress or PHP version incompatibilities that can break the website, and recognize which languages & regions the %s should be translated and tailored to.', 'opt-out-message-clicking-opt-out' ), $fs->get_module_type() ), sprintf( $fs->esc_html_inline( 'By clicking "Opt Out", diagnostic data will no longer be sent to %s.', 'opt-out-message-clicking-opt-out' ), $plugin_title ), ), 'prompt_cancel_label' => $fs->get_text_inline( 'Keep Sharing', 'keep-sharing' ) ), array( 'id' => 'extensions', 'type' => 'optional', 'title' => $fs->get_text_inline( 'Extensions', 'extensions' ), 'desc' => '', 'permissions' => $optional_permissions, ), ); } else { $optional_permissions = $permission_manager->get_license_optional_permissions( false, true ); $permission_groups = array( array( 'id' => 'essentials', 'type' => 'required', 'title' => $fs->esc_html_inline( 'Required', 'required' ), 'desc' => sprintf( $fs->esc_html_inline( 'For automatic delivery of security & feature updates, and license management & protection, %s needs to:', 'license-sync-disclaimer' ), '' . esc_html( $fs->get_plugin_title() ) . '' ), 'permissions' => $permission_manager->get_license_required_permissions( true ), 'is_enabled' => $permission_manager->is_essentials_tracking_allowed(), 'prompt' => array( sprintf( $fs->esc_html_inline( 'To ensure that security & feature updates are automatically delivered directly to your WordPress Admin Dashboard while protecting your license from unauthorized abuse, %2$s needs to view the website’s homepage URL, %1$s version, SDK version, and whether the %1$s is active.', 'premium-opt-out-message-usage-tracking' ), $fs->get_module_type(), $plugin_title ), sprintf( $fs->esc_html_inline( 'By opting out from sharing this information with the updates server, you’ll have to check for new %1$s releases and manually download & install them. Not just a hassle, but missing an update can put your site at risk and cause undue compatibility issues, so we highly recommend keeping these essential permissions on.', 'opt-out-message-clicking-opt-out' ), $fs->get_module_type(), $plugin_title ), ), 'prompt_cancel_label' => $fs->get_text_inline( 'Keep automatic updates', 'premium-opt-out-cancel' ) ), array( 'id' => 'optional', 'type' => 'optional', 'title' => $fs->esc_html_inline( 'Optional', 'optional' ), 'desc' => sprintf( $fs->esc_html_inline( 'For ongoing compatibility with your website, you can optionally allow %s to:', 'optional-permissions-disclaimer' ), $plugin_title ), 'permissions' => $optional_permissions, ), ); } $ajax_action = 'toggle_permission_tracking'; $form_id = "fs_opt_out_{$fs->get_id()}"; ?> require_permissions_js( false ) ?> PK!;cncn9libraries/freemius/templates/forms/license-activation.phpnu[get_slug(); $unique_affix = $fs->get_unique_affix(); $cant_find_license_key_text = fs_text_inline( "Can't find your license key?", 'cant-find-license-key', $slug ); $message_above_input_field = fs_text_inline( 'Please enter the license key that you received in the email right after the purchase:', 'activate-license-message', $slug ); $message_below_input_field = ''; $header_title = $fs->is_free_plan() ? fs_text_inline( 'Activate License', 'activate-license', $slug ) : fs_text_inline( 'Update License', 'update-license', $slug ); if ( $fs->is_registered() ) { $activate_button_text = $header_title; } else { $message_below_input_field = sprintf( fs_text_inline( 'The %1$s will be periodically sending essential license data to %2$s to check for security and feature updates, and verify the validity of your license.', 'license-sync-disclaimer', $slug ), $fs->get_module_label( true ), "{$fs->get_plugin_title()}" ); $activate_button_text = fs_text_inline( 'Agree & Activate License', 'agree-activate-license', $slug ); } $license_key_text = fs_text_inline( 'License key', 'license-key' , $slug ); $is_network_activation = ( $fs->is_network_active() && fs_is_network_admin() && ! $fs->is_delegated_connection() ); $network_activation_html = ''; $sites_details = array(); if ( $is_network_activation ) { $all_sites = Freemius::get_sites(); $all_site_details = array(); $subsite_url_by_install_id = array(); $install_url_by_install_id = array(); foreach ( $all_sites as $site ) { $site_details = $fs->get_site_info( $site ); if ( FS_Clone_Manager::instance()->is_temporary_duplicate_by_blog_id( $site_details['blog_id'] ) ) { continue; } $blog_id = Freemius::get_site_blog_id( $site ); $install = $fs->get_install_by_blog_id($blog_id); if ( is_object( $install ) ) { if ( isset( $subsite_url_by_install_id[ $install->id ] ) ) { $clone_subsite_url = $subsite_url_by_install_id[ $install->id ]; $clone_install_url = $install_url_by_install_id[ $install->id ]; if ( /** * If we already have an install with the same URL as the subsite it's stored in, skip the current subsite. Otherwise, replace the existing install's data with the current subsite's install's data if the URLs match. * * @author Leo Fajardo (@leorw) * @since 2.5.0 */ fs_strip_url_protocol( untrailingslashit( $clone_install_url ) ) === fs_strip_url_protocol( untrailingslashit( $clone_subsite_url ) ) || fs_strip_url_protocol( untrailingslashit( $install->url ) ) !== fs_strip_url_protocol( untrailingslashit( $site_details['url'] ) ) ) { continue; } } if ( FS_Plugin_License::is_valid_id( $install->license_id ) ) { $site_details['license_id'] = $install->license_id; } $subsite_url_by_install_id[ $install->id ] = $site_details['url']; $install_url_by_install_id[ $install->id ] = $install->url; } $all_site_details[] = $site_details; } if ( $is_network_activation ) { $vars = array( 'id' => $fs->get_id(), 'sites' => $all_site_details, 'require_license_key' => true ); $network_activation_html = fs_get_template( 'partials/network-activation.php', $vars ); } } $premium_licenses = $fs->get_available_premium_licenses(); $available_licenses = array(); foreach ( $premium_licenses as $premium_license ) { $activations_left = $premium_license->left(); if ( ! ( $activations_left > 0 ) ) { continue; } $available_licenses[ $activations_left . '_' . $premium_license->id ] = $premium_license; } $total_available_licenses = count( $available_licenses ); if ( $total_available_licenses > 0 ) { $license_input_html = <<< HTML
    HTML; if ( $total_available_licenses > 1 ) { // Sort the licenses by number of activations left in descending order. krsort( $available_licenses ); $license_input_html .= ''; } else { $available_licenses = array_values( $available_licenses ); /** * @var FS_Plugin_License $available_license */ $available_license = $available_licenses[0]; $value = sprintf( "%s-Site %s License - %s", ( 1 == $available_license->quota ? 'Single' : ( $available_license->is_unlimited() ? 'Unlimited' : $available_license->quota ) ), $fs->_get_plan_by_id( $available_license->plan_id )->title, $available_license->get_html_escaped_masked_secret_key() ); $license_input_html .= <<< HTML HTML; } $license_input_html .= <<< HTML
    HTML; } else { $license_input_html = ""; } $ownership_change_option_text = fs_text_inline( "Associate with the license owner's account.", 'associate-account-with-license-owner', $slug ); $ownership_change_option_html = ""; /** * IMPORTANT: * DO NOT ADD MAXLENGTH OR LIMIT THE LICENSE KEY LENGTH SINCE * WE DO WANT TO ALLOW INPUT OF LONGER KEYS (E.G. WooCommerce Keys) * FOR MIGRATED MODULES. */ $modal_content_html = <<< HTML

    {$message_above_input_field}

    {$license_input_html} {$cant_find_license_key_text} {$network_activation_html}

    {$message_below_input_field}

    {$ownership_change_option_html} HTML; /** * Handle the ownership change option if not an add-on or if no license yet is activated for the * parent product in case of an add-on. * * @author Leo Fajardo (@leorw) * @since 2.3.2 */ $is_user_change_supported = ( ! $fs->is_addon() || ! $fs->get_parent_instance()->has_active_valid_license() ); fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); ?> get_slug(); echo fs_text_inline( 'Sorry for the inconvenience and we are here to help if you give us a chance.', 'contact-support-before-deactivation', $slug ) . sprintf(" %s", $fs->contact_url( 'technical_support' ), fs_text_inline( 'Contact Support', 'contact-support', $slug ) ); PK!pPP>libraries/freemius/templates/forms/deactivation/retry-skip.phpnu[get_slug(); $skip_url = fs_nonce_url( $fs->_get_admin_page_url( '', array( 'fs_action' => $fs->get_unique_affix() . '_skip_activation' ) ), $fs->get_unique_affix() . '_skip_activation' ); $skip_text = strtolower( fs_text_x_inline( 'Skip', 'verb', 'skip', $slug ) ); $use_plugin_anonymously_text = fs_text_inline( 'Click here to use the plugin anonymously', 'click-here-to-use-plugin-anonymously', $slug ); echo sprintf( fs_text_inline( "You might have missed it, but you don't have to share any data and can just %s the opt-in.", 'dont-have-to-share-any-data', $slug ), "{$skip_text}" ) . " {$use_plugin_anonymously_text}";PK!.OXOX8libraries/freemius/templates/forms/deactivation/form.phpnu[get_slug(); $subscription_cancellation_dialog_box_template_params = $VARS['subscription_cancellation_dialog_box_template_params']; $show_deactivation_feedback_form = $VARS['show_deactivation_feedback_form']; $confirmation_message = $VARS['uninstall_confirmation_message']; $is_anonymous = ( ! $fs->is_registered() ); $anonymous_feedback_checkbox_html = ''; $reasons_list_items_html = ''; $snooze_select_html = ''; if ( $show_deactivation_feedback_form ) { $reasons = $VARS['reasons']; foreach ( $reasons as $reason ) { $list_item_classes = 'reason' . ( ! empty( $reason['input_type'] ) ? ' has-input' : '' ); if ( isset( $reason['internal_message'] ) && ! empty( $reason['internal_message'] ) ) { $list_item_classes .= ' has-internal-message'; $reason_internal_message = $reason['internal_message']; } else { $reason_internal_message = ''; } $reason_input_type = ( ! empty( $reason['input_type'] ) ? $reason['input_type'] : '' ); $reason_input_placeholder = ( ! empty( $reason['input_placeholder'] ) ? $reason['input_placeholder'] : '' ); $reason_list_item_html = <<< HTML
  • {$reason_internal_message}
  • HTML; $reasons_list_items_html .= $reason_list_item_html; } if ( $is_anonymous ) { $anonymous_feedback_checkbox_html = sprintf( '', fs_esc_html_inline( 'Anonymous feedback', 'anonymous-feedback', $slug ) ); } $snooze_periods = array( array( 'increment' => fs_text_inline( 'hour', $slug ), 'quantity' => number_format_i18n(1), 'value' => 6 * WP_FS__TIME_10_MIN_IN_SEC, ), array( 'increment' => fs_text_inline( 'hours', $slug ), 'quantity' => number_format_i18n(24), 'value' => WP_FS__TIME_24_HOURS_IN_SEC, ), array( 'increment' => fs_text_inline( 'days', $slug ), 'quantity' => number_format_i18n(7), 'value' => WP_FS__TIME_WEEK_IN_SEC, ), array( 'increment' => fs_text_inline( 'days', $slug ), 'quantity' => number_format_i18n(30), 'value' => 30 * WP_FS__TIME_24_HOURS_IN_SEC, ), ); $snooze_select_html = ''; } // Aliases. $deactivate_text = fs_text_inline( 'Deactivate', 'deactivate', $slug ); $theme_text = fs_text_inline( 'Theme', 'theme', $slug ); $activate_x_text = fs_text_inline( 'Activate %s', 'activate-x', $slug ); $submit_deactivate_text = sprintf( fs_text_inline( 'Submit & %s', 'deactivation-modal-button-submit', $slug ), $fs->is_plugin() ? $deactivate_text : sprintf( $activate_x_text, $theme_text ) ); fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); if ( ! empty( $subscription_cancellation_dialog_box_template_params ) ) { fs_require_template( 'forms/subscription-cancellation.php', $subscription_cancellation_dialog_box_template_params ); } ?> PK!p2WW9libraries/freemius/templates/forms/deactivation/index.phpnu[get_slug(); /** * @var object[] $license_owners */ $license_owners = $VARS['license_owners']; $change_user_message = fs_text_inline( 'By changing the user, you agree to transfer the account ownership to:', 'change-user--message', $slug ); $header_title = fs_text_inline( 'Change User', 'change-user', $slug ); $user_change_button_text = fs_text_inline( 'I Agree - Change User', 'agree-change-user', $slug ); $other_text = fs_text_inline( 'Other', 'other', $slug ); $enter_email_address_placeholder_text = fs_text_inline( 'Enter email address', 'enter-email-address', $slug ); $user_change_options_html = <<< HTML
    HTML; foreach ( $license_owners as $license_owner ) { $user_change_options_html .= <<< HTML HTML; } $user_change_options_html .= <<< HTML
    HTML; $modal_content_html = <<< HTML

    {$change_user_message}

    {$user_change_options_html} HTML; fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); ?> PK!Rf8libraries/freemius/templates/plugin-info/screenshots.phpnu[
      $url ) : ?>
    PK!p2WW2libraries/freemius/templates/plugin-info/index.phpnu[features) && is_array($plan->features)) { foreach ( $plan->features as $feature ) { if ( ! isset( $features_plan_map[ $feature->id ] ) ) { $features_plan_map[ $feature->id ] = array( 'feature' => $feature, 'plans' => array() ); } $features_plan_map[ $feature->id ]['plans'][ $plan->id ] = $feature; } } // Add support as a feature. if ( ! empty( $plan->support_email ) || ! empty( $plan->support_skype ) || ! empty( $plan->support_phone ) || true === $plan->is_success_manager ) { if ( ! isset( $features_plan_map['support'] ) ) { $support_feature = new stdClass(); $support_feature->id = 'support'; $support_feature->title = fs_text_inline( 'Support', $plugin->slug ); $features_plan_map[ $support_feature->id ] = array( 'feature' => $support_feature, 'plans' => array() ); } else { $support_feature = $features_plan_map['support']['feature']; } $features_plan_map[ $support_feature->id ]['plans'][ $plan->id ] = $support_feature; } } // Add updates as a feature for all plans. $updates_feature = new stdClass(); $updates_feature->id = 'updates'; $updates_feature->title = fs_text_inline( 'Unlimited Updates', 'unlimited-updates', $plugin->slug ); $features_plan_map[ $updates_feature->id ] = array( 'feature' => $updates_feature, 'plans' => array() ); foreach ( $plans as $plan ) { $features_plan_map[ $updates_feature->id ]['plans'][ $plan->id ] = $updates_feature; } ?>
    $data ) : ?>
    title ?> pricing ) ) { fs_esc_html_echo_inline( 'Free', 'free', $plugin->slug ); } else { foreach ( $plan->pricing as $pricing ) { /** * @var FS_Pricing $pricing */ if ( 1 == $pricing->licenses ) { if ( $pricing->has_annual() ) { echo "\${$pricing->annual_price} / " . fs_esc_html_x_inline( 'year', 'as annual period', 'year', $plugin->slug ); } else if ( $pricing->has_monthly() ) { echo "\${$pricing->monthly_price} / " . fs_esc_html_x_inline( 'mo', 'as monthly period', 'mo', $plugin->slug ); } else { echo "\${$pricing->lifetime_price}"; } } } } ?>
    title ) ) ?> id ] ) ) : ?> id ]->value ) ) : ?> id ]->value ) ?>
    PK!8libraries/freemius/templates/plugin-info/description.phpnu[info->selling_point_0 ) || ! empty( $plugin->info->selling_point_1 ) || ! empty( $plugin->info->selling_point_2 ) ) : ?>
      info->{'selling_point_' . $i} ) ) : ?>
    • info->{'selling_point_' . $i} ) ?>

    info->description, array( 'a' => array( 'href' => array(), 'title' => array(), 'target' => array() ), 'b' => array(), 'i' => array(), 'p' => array(), 'blockquote' => array(), 'h2' => array(), 'h3' => array(), 'ul' => array(), 'ol' => array(), 'li' => array() ) ); ?>
    info->screenshots ) ) : ?> info->screenshots ?>

    slug ) ?>

      $url ) : ?>
    PK!Y,libraries/freemius/templates/ajax-loader.phpnu[ PK!p2WW&libraries/freemius/templates/index.phpnu[
    PK!hh(libraries/freemius/templates/connect.phpnu[get_slug(); $is_pending_activation = $fs->is_pending_activation(); $is_premium_only = $fs->is_only_premium(); $has_paid_plans = $fs->has_paid_plan(); $is_premium_code = $fs->is_premium(); $is_freemium = $fs->is_freemium(); $fs->_enqueue_connect_essentials(); /** * Enqueueing the styles in `_enqueue_connect_essentials()` is too late, as we need them in the HEADER. Therefore, inject the styles inline to avoid FOUC. * * @author Vova Feldman (@svovaf) */ echo "\n"; $current_user = Freemius::_get_current_wp_user(); $first_name = $current_user->user_firstname; if ( empty( $first_name ) ) { $first_name = $current_user->nickname; } $site_url = Freemius::get_unfiltered_site_url(); $protocol_pos = strpos( $site_url, '://' ); if ( false !== $protocol_pos ) { $site_url = substr( $site_url, $protocol_pos + 3 ); } $freemius_usage_tracking_url = $fs->get_usage_tracking_terms_url(); $freemius_plugin_terms_url = $fs->get_eula_url(); $error = fs_request_get( 'error' ); $has_release_on_freemius = $fs->has_release_on_freemius(); $require_license_key = $is_premium_only || ( $is_freemium && ( $is_premium_code || ! $has_release_on_freemius ) && fs_request_get_bool( 'require_license', ( $is_premium_code || $has_release_on_freemius ) ) ); $freemius_activation_terms_url = ($fs->is_premium() && $require_license_key) ? $fs->get_license_activation_terms_url() : $freemius_usage_tracking_url; $freemius_activation_terms_html = 'freemius.com'; if ( $is_pending_activation ) { $require_license_key = false; } if ( $require_license_key ) { $fs->_add_license_activation_dialog_box(); } $is_optin_dialog = ( $fs->is_theme() && $fs->is_themes_page() && $fs->show_opt_in_on_themes_page() ); if ( $is_optin_dialog ) { $show_close_button = false; $previous_theme_activation_url = ''; if ( ! $is_premium_code ) { $show_close_button = true; } else if ( $is_premium_only ) { $previous_theme_activation_url = $fs->get_previous_theme_activation_url(); $show_close_button = ( ! empty( $previous_theme_activation_url ) ); } } $is_network_level_activation = ( fs_is_network_admin() && $fs->is_network_active() && ! $fs->is_network_delegated_connection() ); $fs_user = Freemius::_get_user_by_email( $current_user->user_email ); $activate_with_current_user = ( is_object( $fs_user ) && ! $is_pending_activation && // If requires a license for activation, use the user associated with the license for the opt-in. ! $require_license_key && ! $is_network_level_activation ); $optin_params = $fs->get_opt_in_params( array(), $is_network_level_activation ); $sites = isset( $optin_params['sites'] ) ? $optin_params['sites'] : array(); $is_network_upgrade_mode = ( fs_is_network_admin() && $fs->is_network_upgrade_mode() ); /* translators: %s: name (e.g. Hey John,) */ $hey_x_text = esc_html( sprintf( fs_text_x_inline( 'Hey %s,', 'greeting', 'hey-x', $slug ), $first_name ) ); $activation_state = array( 'is_license_activation' => $require_license_key, 'is_pending_activation' => $is_pending_activation, 'is_gdpr_required' => true, 'is_network_level_activation' => $is_network_level_activation, 'is_dialog' => $is_optin_dialog, ); ?>
    do_action( 'connect/before', $activation_state ); ?>
    $fs->get_id(), 'size' => $size, ); fs_require_once_template( 'plugin-icon.php', $vars ); ?>
    do_action( 'connect/before_message', $activation_state ) ?>
    apply_filters( 'connect_error_esc_html', esc_html( $error ) ) ?>
    is_plugin_update() ) { echo $fs->apply_filters( 'connect-header', sprintf( '

    %s

    ', esc_html( fs_text_inline( 'Never miss an important update', 'connect-header' ) ) ) ); } else { echo $fs->apply_filters( 'connect-header_on-update', sprintf( '

    %s

    ', sprintf( esc_html( /* translators: %1$s: plugin name (e.g., "Awesome Plugin"); %2$s: version (e.g., "1.2.3") */ fs_text_inline('Thank you for updating to %1$s v%2$s!', 'connect-header_on-update' ) ), esc_html( $fs->get_plugin_name() ), $fs->get_plugin_version() ) ) ); } } ?>

    apply_filters( 'pending_activation_message', sprintf( /* translators: %s: name (e.g. Thanks John!) */ fs_text_inline( 'Thanks %s!', 'thanks-x', $slug ) . '
    ' . fs_text_inline( 'You should receive a confirmation email for %s to your mailbox at %s. Please make sure you click the button in that email to %s.', 'pending-activation-message', $slug ), $first_name, '' . $fs->get_plugin_name() . '', '' . $current_user->user_email . '', fs_text_inline( 'complete the opt-in', 'complete-the-opt-in', $slug ) ) ); } else if ( $require_license_key ) { $button_label = fs_text_inline( 'Activate License', 'activate-license', $slug ); $message = $fs->apply_filters( 'connect-message_on-premium', sprintf( fs_text_inline( 'Welcome to %s! To get started, please enter your license key:', 'thanks-for-purchasing', $slug ), '' . $fs->get_plugin_name() . '' ), $first_name, $fs->get_plugin_name() ); } else { $filter = 'connect_message'; if ( ! $fs->is_plugin_update() ) { $default_optin_message = esc_html( sprintf( /* translators: %s: module type (plugin, theme, or add-on) */ fs_text_inline( 'Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info. This will help us make the %s more compatible with your site and better at doing what you need it to.', 'connect-message', $slug ), $fs->get_module_label( true ) ) ); } else { // If Freemius was added on a plugin update, set different // opt-in message. /* translators: %s: module type (plugin, theme, or add-on) */ $default_optin_message = esc_html( sprintf( fs_text_inline( 'We have introduced this opt-in so you never miss an important update and help us make the %s more compatible with your site and better at doing what you need it to.', 'connect-message_on-update_why' ), $fs->get_module_label( true ) ) ); $default_optin_message .= '

    ' . esc_html( fs_text_inline( 'Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info.', 'connect-message_on-update', $slug ) ); if ( $fs->is_enable_anonymous() ) { $default_optin_message .= ' ' . esc_html( fs_text_inline( 'If you skip this, that\'s okay! %1$s will still work just fine.', 'connect-message_on-update_skip', $slug ) ); } // If user customized the opt-in message on update, use // that message. Otherwise, fallback to regular opt-in // custom message if exists. if ( $fs->has_filter( 'connect_message_on_update' ) ) { $filter = 'connect_message_on_update'; } } $message = $fs->apply_filters( $filter, sprintf( $default_optin_message, '' . esc_html( $fs->get_plugin_name() ) . '', '' . $current_user->user_login . '', '' . $site_url . '', $freemius_activation_terms_html ), $first_name, $fs->get_plugin_name(), $current_user->user_login, '' . $site_url . '', $freemius_activation_terms_html, true ); } if ( $is_network_upgrade_mode ) { $network_integration_text = esc_html( fs_text_inline( 'We\'re excited to introduce the Freemius network-level integration.', 'connect_message_network_upgrade', $slug ) ); if ($is_premium_code){ $message = $network_integration_text . ' ' . sprintf( fs_text_inline( 'During the update process we detected %d site(s) that are still pending license activation.', 'connect_message_network_upgrade-premium', $slug ), count( $sites ) ); $message .= '

    ' . sprintf( fs_text_inline( 'If you\'d like to use the %s on those sites, please enter your license key below and click the activation button.', 'connect_message_network_upgrade-premium-activate-license', $slug ), $is_premium_only ? $fs->get_module_label( true ) : sprintf( /* translators: %s: module type (plugin, theme, or add-on) */ fs_text_inline( "%s's paid features", 'x-paid-features', $slug ), $fs->get_module_label( true ) ) ); /* translators: %s: module type (plugin, theme, or add-on) */ $message .= ' ' . sprintf( fs_text_inline( 'Alternatively, you can skip it for now and activate the license later, in your %s\'s network-level Account page.', 'connect_message_network_upgrade-premium-skip-license', $slug ), $fs->get_module_label( true ) ); }else { $message = $network_integration_text . ' ' . sprintf( fs_text_inline( 'During the update process we detected %s site(s) in the network that are still pending your attention.', 'connect_message_network_upgrade-free', $slug ), count( $sites ) ) . '

    ' . ( fs_starts_with( $message, $hey_x_text . '
    ' ) ? substr( $message, strlen( $hey_x_text . '
    ' ) ) : $message ); } } echo $message; ?>

    do_action( 'connect/after_license_input', $activation_state ); ?> - %s', $fs->get_text_inline( 'Yes', 'yes' ), $fs->get_text_inline( 'send me security & feature updates, educational content and offers.', 'send-updates' ) ); $do_not_send_updates_text = sprintf( '%s - %s', $fs->get_text_inline( 'No', 'no' ), sprintf( $fs->get_text_inline( 'do %sNOT%s send me security & feature updates, educational content and offers.', 'do-not-send-updates' ), '', '' ) ); ?>
    $fs->get_id(), 'sites' => $sites, 'require_license_key' => $require_license_key ); echo fs_get_template( 'partials/network-activation.php', $vars ); ?> do_action( 'connect/after_message', $activation_state ) ?>
    do_action( 'connect/before_actions', $activation_state ) ?> is_enable_anonymous() && ! $is_pending_activation && ( ! $require_license_key || $is_network_upgrade_mode ) ) : ?> apply_filters( 'show_delegation_option', true ) ) : ?>
    get_unique_affix() . '_activate_existing' ) ?>
    $value ) : ?>
    do_action( 'connect/after_actions', $activation_state ) ?>
    is_permission_requested( 'newsletter' ) ) { $permissions[] = $permission_manager->get_newsletter_permission(); } $permissions = $permission_manager->get_permissions( $require_license_key, $permissions ); if ( ! empty( $permissions ) ) : ?>

    do_action( 'connect/after', $activation_state ); if ( $is_optin_dialog ) { ?>

    newest->version ?>

    get_option( 'ms_migration_complete', false, true ) ) : ?>
    Resolve Clone(s)
    'WP_FS__REMOTE_ADDR', 'val' => WP_FS__REMOTE_ADDR, ), array( 'key' => 'WP_FS__ADDRESS_PRODUCTION', 'val' => WP_FS__ADDRESS_PRODUCTION, ), array( 'key' => 'FS_API__ADDRESS', 'val' => FS_API__ADDRESS, ), array( 'key' => 'FS_API__SANDBOX_ADDRESS', 'val' => FS_API__SANDBOX_ADDRESS, ), array( 'key' => 'WP_FS__DIR', 'val' => WP_FS__DIR, ), array( 'key' => 'wp_using_ext_object_cache()', 'val' => wp_using_ext_object_cache() ? 'true' : 'false', ), ) ?>
    >

    plugins as $sdk_path => $data ) : ?> version ) ?> >
    version ?> plugin_path ?>
    get_option( $module_type . 's' ), FS_Plugin::get_class_name() ) ?> 0 ) : ?>

    $data ) : ?> file ); } else { $current_theme = wp_get_theme(); $is_active = ( $current_theme->stylesheet === $data->file ); if ( ! $is_active && is_child_theme() ) { $parent_theme = $current_theme->parent(); $is_active = ( ( $parent_theme instanceof WP_Theme ) && $parent_theme->stylesheet === $data->file ); } } ?> id ); $active_modules_by_id[ $data->id ] = true; } ?> has_api_connectivity(); if ( true === $has_api_connectivity && $fs->is_on() ) { echo ' style="background: #E6FFE6; font-weight: bold"'; } else { echo ' style="background: #ffd0d0; font-weight: bold"'; } } ?>> > is_on() ) { echo ' style="color: red; text-transform: uppercase;"'; } ?>>is_on() ? $on_text : $off_text ); } ?> get_network_install_blog_id(); $network_user = $fs->get_network_user(); } ?>
    id ?> version ?> title ?> file ?> public_key ?> email; } ?> has_trial_plan() ) : ?>
    is_registered() ) : ?> is_network_upgrade_mode() ) : ?>
    0 ) : ?>

    /

    $sites ) : ?> blog_id : null; if ( is_null( $site_url ) || $is_multisite ) { $site_url = Freemius::get_unfiltered_site_url( $blog_id, true, true ); } $is_active_clone = ( $site->is_clone( $site_url ) && isset( $active_modules_by_id[ $site->plugin_id ] ) ); if ( $is_active_clone ) { $has_any_active_clone = true; } ?>
    id ?> url ) ?> user_id ?> license_id) ? $site->license_id : '' ?> plan_id ) ) { if ( false === $all_plans ) { $option_name = 'plans'; if ( WP_FS__MODULE_TYPE_PLUGIN !== $module_type ) { $option_name = $module_type . '_' . $option_name; } $all_plans = fs_get_entities( $fs_options->get_option( $option_name, array() ), FS_Plugin_Plan::get_class_name() ); } foreach ( $all_plans[ $slug ] as $plan ) { $plan_id = Freemius::_decrypt( $plan->id ); if ( $site->plan_id == $plan_id ) { $plan_name = Freemius::_decrypt( $plan->name ); break; } } } echo $plan_name; ?> public_key ?> is_whitelabeled ? FS_Plugin_License::mask_secret_key_for_html( $site->secret_key ) : esc_html( $site->secret_key ); ?>
    $plugin_addons ) : ?>

    id ?> title ?> slug ?> version ?> public_key ?> secret_key ) ?>
    id ] = true; } } foreach ( $module_types as $module_type ) { /** * @var FS_Plugin_License[] $licenses */ $licenses = $VARS[ $module_type . '_licenses' ]; foreach ( $licenses as $license ) { if ( $license->is_whitelabeled ) { $users_with_developer_license_by_id[ $license->user_id ] = true; } } } ?>

    $user ) : ?>
    id ?> get_name() ?> email ?> is_verified ) ?> public_key ?> secret_key) : esc_html( $user->secret_key ) ?>
    0 ) : ?>

    id ?> plugin_id ?> user_id ?> plan_id ?> is_unlimited() ? 'Unlimited' : ( $license->is_single_site() ? 'Single Site' : $license->quota ) ?> activated ?> is_block_features ? 'Blocking' : 'Flexible' ?> is_whitelabeled ? 'Whitelabeled' : 'Normal' ?> is_whitelabeled || ! isset( $user_ids_map[ $license->user_id ] ) ) ? $license->get_html_escaped_masked_secret_key() : esc_html( $license->secret_key ); ?> expiration ?>

    #
    {$log.log_order}. {$log.type} {$log.logger} {$log.function} {$log.message_short}
    {$log.message}
    {$log.file}:{$log.line} {$log.created}
    PK!Jhdd(libraries/freemius/templates/account.phpnu[get_slug(); /** * @var FS_Plugin_Tag $update */ $update = $fs->has_release_on_freemius() ? $fs->get_update( false, false ) : null; if ( is_object($update) ) { /** * This logic is particularly required for multisite environment. * If a module is site activated (not network) and not on the main site, * the module will NOT be executed on the network level, therefore, the * custom updates logic will not be executed as well, so unless we force * the injection of the update into the updates transient, premium updates * will not work. * * @author Vova Feldman (@svovaf) * @since 2.0.0 */ $updater = FS_Plugin_Updater::instance( $fs ); $updater->set_update_data( $update ); } $is_paying = $fs->is_paying(); $user = $fs->get_user(); $site = $fs->get_site(); $name = $user->get_name(); $license = $fs->_get_license(); $is_license_foreign = ( is_object( $license ) && $user->id != $license->user_id ); $is_data_debug_mode = $fs->is_data_debug_mode(); $is_whitelabeled = $fs->is_whitelabeled(); $subscription = ( is_object( $license ) ? $fs->_get_subscription( $license->id ) : null ); $plan = $fs->get_plan(); $is_active_subscription = ( is_object( $subscription ) && $subscription->is_active() ); $is_paid_trial = $fs->is_paid_trial(); $has_paid_plan = $fs->apply_filters( 'has_paid_plan_account', $fs->has_paid_plan() ); $show_upgrade = ( ! $is_whitelabeled && $has_paid_plan && ! $is_paying && ! $is_paid_trial ); $trial_plan = $fs->get_trial_plan(); $is_plan_change_supported = ( ! $fs->is_single_plan() && ! $fs->apply_filters( 'hide_plan_change', false ) ); if ( $has_paid_plan ) { $fs->_add_license_activation_dialog_box(); } if ( $fs->should_handle_user_change() ) { $fs->_add_email_address_update_dialog_box(); } $ids_of_installs_activated_with_foreign_licenses = $fs->should_handle_user_change() ? $fs->get_installs_ids_with_foreign_licenses() : array(); if ( ! empty( $ids_of_installs_activated_with_foreign_licenses ) ) { $fs->_add_user_change_dialog_box( $ids_of_installs_activated_with_foreign_licenses ); } if ( $fs->is_whitelabeled( true ) || $fs->is_data_debug_mode() ) { $fs->_add_data_debug_mode_dialog_box(); } if ( fs_request_get_bool( 'auto_install' ) ) { $fs->_add_auto_installation_dialog_box(); } if ( fs_request_get_bool( 'activate_license' ) ) { // Open the license activation dialog box on the account page. add_action( 'admin_footer', array( &$fs, '_open_license_activation_dialog_box' ) ); } $show_billing = ( ! $is_whitelabeled && ! $fs->apply_filters( 'hide_billing_and_payments_info', false ) ); if ( $show_billing ) { $payments = $fs->_fetch_payments(); $show_billing = ( is_array( $payments ) && 0 < count( $payments ) ); } $has_tabs = $fs->_add_tabs_before_content(); // Aliases. $download_latest_text = fs_text_x_inline( 'Download Latest', 'as download latest version', 'download-latest', $slug ); $downgrading_plan_text = fs_text_inline( 'Downgrading your plan', 'downgrading-plan', $slug ); $cancelling_subscription_text = fs_text_inline( 'Cancelling the subscription', 'cancelling-subscription', $slug ); /* translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' */ $downgrade_x_confirm_text = fs_text_inline( '%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s.', 'downgrade-x-confirm', $slug ); $prices_increase_text = fs_text_inline( 'Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.', 'pricing-increase-warning', $slug ); $cancel_trial_confirm_text = fs_text_inline( 'Cancelling the trial will immediately block access to all premium features. Are you sure?', 'cancel-trial-confirm', $slug ); $after_downgrade_non_blocking_text = fs_text_inline( 'You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.', 'after-downgrade-non-blocking', $slug ); $after_downgrade_blocking_text = fs_text_inline( 'Once your license expires you can still use the Free version but you will NOT have access to the %s features.', 'after-downgrade-blocking', $slug ); /* translators: %s: Plan title (e.g. "Professional") */ $activate_plan_text = fs_text_inline( 'Activate %s Plan', 'activate-x-plan', $slug ); $version_text = fs_text_x_inline( 'Version', 'product version', 'version', $slug ); /* translators: %s: Time period (e.g. Auto renews in "2 months") */ $renews_in_text = fs_text_inline( 'Auto renews in %s', 'renews-in', $slug ); /* translators: %s: Time period (e.g. Expires in "2 months") */ $expires_in_text = fs_text_inline( 'Expires in %s', 'expires-in', $slug ); $sync_license_text = fs_text_x_inline( 'Sync License', 'as synchronize license', 'sync-license', $slug ); $cancel_trial_text = fs_text_inline( 'Cancel Trial', 'cancel-trial', $slug ); $change_plan_text = fs_text_inline( 'Change Plan', 'change-plan', $slug ); $upgrade_text = fs_text_x_inline( 'Upgrade', 'verb', 'upgrade', $slug ); $addons_text = fs_text_inline( 'Add-Ons', 'add-ons', $slug ); $downgrade_text = fs_text_x_inline( 'Downgrade', 'verb', 'downgrade', $slug ); $trial_text = fs_text_x_inline( 'Trial', 'trial period', 'trial', $slug ); $free_text = fs_text_inline( 'Free', 'free', $slug ); $activate_text = fs_text_inline( 'Activate', 'activate', $slug ); $plan_text = fs_text_x_inline( 'Plan', 'as product pricing plan', 'plan', $slug ); $bundle_plan_text = fs_text_inline( 'Bundle Plan', 'bundle-plan', $slug ); $show_plan_row = true; $show_license_row = is_object( $license ); $site_view_params = array(); if ( fs_is_network_admin() ) { $sites = Freemius::get_sites(); $all_installs_plan_id = null; $all_installs_license_id = ( $show_license_row ? $license->id : null ); foreach ( $sites as $s ) { $site_info = $fs->get_site_info( $s ); $install = $fs->get_install_by_blog_id( $site_info['blog_id'] ); $view_params = array( 'freemius' => $fs, 'user' => $fs->get_user(), 'license' => $license, 'site' => $site_info, 'install' => $install, ); $site_view_params[] = $view_params; if ( empty( $install ) ) { continue; } if ( $show_plan_row ) { if ( is_null( $all_installs_plan_id ) ) { $all_installs_plan_id = $install->plan_id; } else if ( $all_installs_plan_id != $install->plan_id ) { $show_plan_row = false; } } if ( $show_license_row && $all_installs_license_id != $install->license_id ) { $show_license_row = false; } } } $has_bundle_license = false; if ( is_object( $license ) && FS_Plugin_License::is_valid_id( $license->parent_license_id ) ) { // Context license has a parent license, therefore, the account has a bundle license. $has_bundle_license = true; } $bundle_subscription = null; $is_bundle_first_payment_pending = false; if ( $show_plan_row && is_object( $license ) && $has_bundle_license ) { $bundle_plan_title = strtoupper( $license->parent_plan_title ); $bundle_subscription = $fs->_get_subscription( $license->parent_license_id ); $is_bundle_first_payment_pending = $license->is_first_payment_pending(); } $fs_blog_id = ( is_multisite() && ! is_network_admin() ) ? get_current_blog_id() : 0; $active_plugins_directories_map = Freemius::get_active_plugins_directories_map( $fs_blog_id ); $is_premium = $fs->is_premium(); $account_addons = $fs->get_updated_account_addons(); $installed_addons = $fs->get_installed_addons(); $installed_addons_ids = array(); /** * Store the installed add-ons' IDs into a collection which will be used in determining the add-ons to show on the "Account" page, and at the same time try to find an add-on that is activated with a bundle license if the core product is not. * * @author Leo Fajardo * * @since 2.4.0 */ foreach ( $installed_addons as $fs_addon ) { $installed_addons_ids[] = $fs_addon->get_id(); if ( $has_bundle_license ) { // We already have the context bundle license details, skip. continue; } if ( $show_plan_row && $fs_addon->has_active_valid_license() ) { $addon_license = $fs_addon->_get_license(); if ( FS_Plugin_License::is_valid_id( $addon_license->parent_license_id ) ) { // Add-on's license is associated with a parent/bundle license. $has_bundle_license = true; $bundle_plan_title = strtoupper( $addon_license->parent_plan_title ); $bundle_subscription = $fs_addon->_get_subscription( $addon_license->parent_license_id ); $is_bundle_first_payment_pending = $addon_license->is_first_payment_pending(); } } } $addons_to_show = array_unique( array_merge( $installed_addons_ids, $account_addons ) ); $is_active_bundle_subscription = ( is_object( $bundle_subscription ) && $bundle_subscription->is_active() ); $available_license = ( $fs->is_free_plan() && ! fs_is_network_admin() ) ? $fs->_get_available_premium_license( $site->is_localhost() ) : null; $available_license_paid_plan = is_object( $available_license ) ? $fs->_get_plan_by_id( $available_license->plan_id ) : null; ?>
    apply_filters( 'hide_account_tabs', false ) ) : ?>

    apply_filters( 'hide_license_key', false ) ); $profile = array(); if ( ! $is_whitelabeled ) { $profile[] = array( 'id' => 'user_name', 'title' => fs_text_inline( 'Name', 'name', $slug ), 'value' => $name ); // if (isset($user->email) && false !== strpos($user->email, '@')) $profile[] = array( 'id' => 'email', 'title' => fs_text_inline( 'Email', 'email', $slug ), 'value' => $user->email ); if ( is_numeric( $user->id ) ) { $profile[] = array( 'id' => 'user_id', 'title' => fs_text_inline( 'User ID', 'user-id', $slug ), 'value' => $user->id ); } } $profile[] = array( 'id' => 'product', 'title' => ( $fs->is_plugin() ? fs_text_inline( 'Plugin', 'plugin', $slug ) : fs_text_inline( 'Theme', 'theme', $slug ) ), 'value' => $fs->get_plugin_title() ); $profile[] = array( 'id' => 'product_id', 'title' => ( $fs->is_plugin() ? fs_text_inline( 'Plugin', 'plugin', $slug ) : fs_text_inline( 'Theme', 'theme', $slug ) ) . ' ' . fs_text_inline( 'ID', 'id', $slug ), 'value' => $fs->get_id() ); if ( ! fs_is_network_admin()) { $profile[] = array( 'id' => 'site_id', 'title' => fs_text_inline( 'Site ID', 'site-id', $slug ), 'value' => is_string( $site->id ) ? $site->id : fs_text_inline( 'No ID', 'no-id', $slug ) ); $profile[] = array( 'id' => 'site_public_key', 'title' => fs_text_inline( 'Public Key', 'public-key', $slug ), 'value' => $site->public_key ); $profile[] = array( 'id' => 'site_secret_key', 'title' => fs_text_inline( 'Secret Key', 'secret-key', $slug ), 'value' => ( ( is_string( $site->secret_key ) ) ? $site->secret_key : fs_text_x_inline( 'No Secret', 'as secret encryption key missing', 'no-secret', $slug ) ) ); } $profile[] = array( 'id' => 'version', 'title' => $version_text, 'value' => $fs->get_plugin_version() ); if ( ! fs_is_network_admin() && $is_premium ) { $profile[] = array( 'id' => 'beta_program', 'title' => '', 'value' => $site->is_beta ); } if ( $has_paid_plan || $has_bundle_license ) { if ( $fs->is_trial() ) { if ( $show_plan_row ) { $profile[] = array( 'id' => 'plan', 'title' => $plan_text, 'value' => ( is_string( $trial_plan->name ) ? strtoupper( $trial_plan->title ) : fs_text_inline( 'Trial', 'trial', $slug ) ) ); } } else { if ( $show_plan_row ) { $profile[] = array( 'id' => 'plan', 'title' => ( $has_bundle_license ? ucfirst( $fs->get_module_type() ) . ' ' : '' ) . $plan_text, 'value' => strtoupper( is_string( $plan->name ) ? $plan->title : strtoupper( $free_text ) ) ); if ( $has_bundle_license ) { $profile[] = array( 'id' => 'bundle_plan', 'title' => $bundle_plan_text, 'value' => $bundle_plan_title ); } } if ( is_object( $license ) ) { if ( ! $hide_license_key ) { $profile[] = array( 'id' => 'license_key', 'title' => fs_text_inline( 'License Key', $slug ), 'value' => $license->secret_key, ); } } } } ?> > is_verified() ) : ?> is_trial() ) : ?> is_lifetime() ) : ?> is_first_payment_pending() ) : ?> is_expired() ?> is_first_payment_pending() ) : ?> is_trial() ) : ?>
    $fs, 'slug' => $slug, 'license' => $available_license, 'plan' => $available_license_paid_plan, 'is_localhost' => $site->is_localhost(), 'install_id' => $site->id, 'class' => 'button-primary', ); fs_require_template( 'account/partials/activate-license-button.php', $view_params ); ?>
    get_unique_affix() . '_sync_license' ) ?>
    has_premium_version() ) : ?> can_use_premium_code() ) : ?>
    is_verified() ) : ?>
    has_release_on_freemius() ) : ?> secret_key ) && in_array( $p['id'], array( 'email', 'user_name' ) ) ) ) : ?>

    get_site(); if ( is_object( $site ) && ( ! is_object( $current_install ) || $current_install->id != $site->id ) ) { $fs->switch_to_blog( $current_blog_id, $site, true ); } ?>
    is_whitelabeled_by_flag() ) { $hide_all_addons_data = true; foreach ( $addons_to_show as $addon_id ) { $is_addon_installed = isset( $installed_addons_ids_map[ $addon_id ] ); $addon_info = $fs->_get_addon_info( $addon_id, $is_addon_installed ); $is_addon_connected = $addon_info['is_connected']; $fs_addon = ( $is_addon_connected && $is_addon_installed ) ? freemius( $addon_id ) : null; $is_whitelabeled = is_object( $fs_addon ) ? $fs_addon->is_whitelabeled( true ) : $addon_info['is_whitelabeled']; if ( ! $is_whitelabeled ) { $hide_all_addons_data = false; } if ( $is_data_debug_mode ) { $is_whitelabeled = false; } $addon_info_by_id[ $addon_id ] = $addon_info; } } foreach ( $addons_to_show as $addon_id ) { $is_addon_installed = isset( $installed_addons_ids_map[ $addon_id ] ); if ( $hide_all_addons_data && ! $is_addon_installed && ! file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $fs->get_addon_basename( $addon_id ) ) ) ) { continue; } $addon_view_params = array( 'parent_fs' => $fs, 'addon_id' => $addon_id, 'odd' => $odd, 'fs_blog_id' => $fs_blog_id, 'active_plugins_directories_map' => &$active_plugins_directories_map, 'is_addon_installed' => $is_addon_installed, 'addon_info' => isset( $addon_info_by_id[ $addon_id ] ) ? $addon_info_by_id[ $addon_id ] : $fs->_get_addon_info( $addon_id, $is_addon_installed ), 'is_whitelabeled' => ( $is_whitelabeled && ! $is_data_debug_mode ) ); fs_require_template( 'account/partials/addon.php', $addon_view_params ); $odd = ! $odd; } ?>

    do_action( 'after_account_details' ) ?> $VARS['id'], 'payments' => $payments ); fs_require_once_template( 'account/billing.php', $view_params ); fs_require_once_template( 'account/payments.php', $view_params ); } ?>
    _get_subscription_cancellation_dialog_box_template_params( true ); if ( ! empty( $subscription_cancellation_dialog_box_template_params ) ) { fs_require_template( 'forms/subscription-cancellation.php', $subscription_cancellation_dialog_box_template_params ); } ?> _add_tabs_after_content(); } PK!yn2libraries/freemius/templates/auto-installation.phpnu[is_tracking_allowed() ? 'stop_tracking' : 'allow_tracking'; $title = $fs->get_plugin_title(); if ( $plugin_id != $fs->get_id() ) { $addon = $fs->get_addon( $plugin_id ); if ( is_object( $addon ) ) { $title = $addon->title . ' ' . fs_text_inline( 'Add-On', 'addon', $slug ); } } $plugin_title = sprintf( '%s', esc_html( $title ) ); $sec_countdown = 30; $countdown_html = sprintf( esc_js( /* translators: %s: Number of seconds */ fs_text_inline( '%s sec', 'x-sec', $slug ) ), sprintf( '%s', $sec_countdown ) ); fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); fs_enqueue_local_style( 'fs_common', '/admin/common.css' ); $params = array(); $loader_html = fs_get_template( 'ajax-loader.php', $params ); // Pass unique auto installation URL if WP_Filesystem is needed. $install_url = $fs->_get_sync_license_url( $plugin_id, true, array( 'auto_install' => 'true' ) ); ob_start(); $method = ''; // Leave blank so WP_Filesystem can populate it as necessary. $credentials = request_filesystem_credentials( esc_url_raw( $install_url ), $method, false, WP_PLUGIN_DIR, array() ); $credentials_form = ob_get_clean(); $require_credentials = ! empty( $credentials_form ); ?>

    %s', 'https://freemius.com', 'freemius.com' ), $countdown_html ) ?>

    ' PK!G&\7libraries/freemius/templates/sticky-admin-notice-js.phpnu[ PK!a]z;;6libraries/freemius/templates/account/partials/site.phpnu[get_slug(); $site = $VARS['site']; $main_license = $VARS['license']; $is_data_debug_mode = $fs->is_data_debug_mode(); $is_whitelabeled = $fs->is_whitelabeled(); $has_paid_plan = $fs->has_paid_plan(); $is_premium = $fs->is_premium(); $main_user = $VARS['user']; $blog_id = $site['blog_id']; $install = $VARS['install']; $is_registered = ! empty( $install ); $license = null; $trial_plan = $fs->get_trial_plan(); $free_text = fs_text_inline( 'Free', 'free', $slug ); if ( $is_whitelabeled && is_object( $install ) && $fs->is_delegated_connection( $blog_id ) ) { $is_whitelabeled = $fs->is_whitelabeled( true, $blog_id ); } ?> data-install-id="id ?>"> id ?>
    $fs, 'slug' => $slug, 'blog_id' => $blog_id, 'class' => 'button-small', ); $license = null; if ( $is_registered ) { $view_params['install_id'] = $install->id; $view_params['is_localhost'] = $install->is_localhost(); $has_license = FS_Plugin_License::is_valid_id( $install->license_id ); $license = $has_license ? $fs->_get_license_by_id( $install->license_id ) : null; } else { $view_params['is_localhost'] = FS_Site::is_localhost_by_address( $site['url'] ); } if ( ! $is_whitelabeled ) { if ( is_object( $license ) ) { $view_params['license'] = $license; // Show license deactivation button. fs_require_template( 'account/partials/deactivate-license-button.php', $view_params ); } else { if ( is_object( $main_license ) && $main_license->can_activate( $view_params['is_localhost'] ) ) { // Main license is available for activation. $available_license = $main_license; } else { // Try to find any available license for activation. $available_license = $fs->_get_available_premium_license( $view_params['is_localhost'] ); } if ( is_object( $available_license ) ) { $premium_plan = $fs->_get_plan_by_id( $available_license->plan_id ); $view_params['license'] = $available_license; $view_params['class'] .= ' button-primary'; $view_params['plan'] = $premium_plan; fs_require_template( 'account/partials/activate-license-button.php', $view_params ); } } } } ?> is_trial() ) { if ( is_object( $trial_plan ) && $trial_plan->id == $install->trial_plan_id ) { $plan_title = is_string( $trial_plan->name ) ? strtoupper( $trial_plan->title ) : fs_text_inline( 'Trial', 'trial', $slug ); } else { $plan_title = fs_text_inline( 'Trial', 'trial', $slug ); } } else { $plan = $fs->_get_plan_by_id( $install->plan_id ); $plan_title = strtoupper( is_string( $plan->title ) ? $plan->title : strtoupper( $free_text ) ); } } ?> > user_id != $main_user->id ) : ?> user_id ) ?> > > > > > > id != $license->id ) : ?> _get_subscription( $license->id ) ?> is_lifetime() && is_object( $subscription ) ) : ?> > is_active(); $renews_in_text = fs_text_inline( 'Auto renews in %s', 'renews-in', $slug ); /* translators: %s: Time period (e.g. Expires in "2 months") */ $expires_in_text = fs_text_inline( 'Expires in %s', 'expires-in', $slug ); ?>
    : license_id ) ) : ?> is_homepage_url_tracking_allowed( $blog_id ) ?>
    id}", ':' ) ) ?>
    : get_name() ) ?>
    : email ) ?>
    : id ?>
    : public_key ) ?>
    : secret_key ) ?>
    : get_html_escaped_masked_secret_key() ?>
    : id ?> - billing_cycle ? _fs_text_inline( 'Annual', 'annual', $slug ) : _fs_text_inline( 'Monthly', 'monthly', $slug ) ); ?> is_first_payment_pending() ) : ?> is_first_payment_pending() ) : ?> expiration ) ); $downgrade_confirmation_message = sprintf( $downgrade_x_confirm_text, ( $fs->is_only_premium() ? $cancelling_subscription_text : $downgrading_plan_text ), $plan->title, $human_readable_license_expiration ); $after_downgrade_message = ! $license->is_block_features ? sprintf( $after_downgrade_non_blocking_text, $plan->title, $fs->get_module_label( true ) ) : sprintf( $after_downgrade_blocking_text, $plan->title ); ?>
    PK!p2WW7libraries/freemius/templates/account/partials/index.phpnu[_get_subscription( $license->id ) : null; $has_active_subscription = ( is_object( $license_subscription ) && $license_subscription->is_active() ); $button_id = "fs_disconnect_button_{$fs->get_id()}"; $website_link = sprintf( '%s', fs_strip_url_protocol( untrailingslashit( Freemius::get_unfiltered_site_url() ) ) ); ?>
    esc_html_inline( 'Disconnect', 'disconnect' ) ?>
    PK!ƌ=Klibraries/freemius/templates/account/partials/deactivate-license-button.phpnu[
    PK!wz |B|B7libraries/freemius/templates/account/partials/addon.phpnu[get_slug(); $fs_blog_id = $VARS['fs_blog_id']; $active_plugins_directories_map = $VARS['active_plugins_directories_map']; $addon_info = $VARS['addon_info']; $is_addon_activated = $fs->is_addon_activated( $addon_id ); $is_addon_connected = $addon_info['is_connected']; $is_addon_installed = $VARS['is_addon_installed']; $fs_addon = ( $is_addon_connected && $is_addon_installed ) ? freemius( $addon_id ) : false; // Aliases. $download_latest_text = fs_text_x_inline( 'Download Latest', 'as download latest version', 'download-latest', $slug ); $downgrading_plan_text = fs_text_inline( 'Downgrading your plan', 'downgrading-plan', $slug ); $cancelling_subscription_text = fs_text_inline( 'Cancelling the subscription', 'cancelling-subscription', $slug ); /* translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' */ $downgrade_x_confirm_text = fs_text_inline( '%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s.', 'downgrade-x-confirm', $slug ); $prices_increase_text = fs_text_inline( 'Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.', 'pricing-increase-warning', $slug ); $cancel_trial_confirm_text = fs_text_inline( 'Cancelling the trial will immediately block access to all premium features. Are you sure?', 'cancel-trial-confirm', $slug ); $after_downgrade_non_blocking_text = fs_text_inline( 'You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.', 'after-downgrade-non-blocking', $slug ); $after_downgrade_blocking_text = fs_text_inline( 'Once your license expires you can still use the Free version but you will NOT have access to the %s features.', 'after-downgrade-blocking', $slug ); /* translators: %s: Plan title (e.g. "Professional") */ $activate_plan_text = fs_text_inline( 'Activate %s Plan', 'activate-x-plan', $slug ); $version_text = fs_text_x_inline( 'Version', 'product version', 'version', $slug ); /* translators: %s: Time period (e.g. Auto renews in "2 months") */ $renews_in_text = fs_text_inline( 'Auto renews in %s', 'renews-in', $slug ); /* translators: %s: Time period (e.g. Expires in "2 months") */ $expires_in_text = fs_text_inline( 'Expires in %s', 'expires-in', $slug ); $cancel_trial_text = fs_text_inline( 'Cancel Trial', 'cancel-trial', $slug ); $change_plan_text = fs_text_inline( 'Change Plan', 'change-plan', $slug ); $upgrade_text = fs_text_x_inline( 'Upgrade', 'verb', 'upgrade', $slug ); $addons_text = fs_text_inline( 'Add-Ons', 'add-ons', $slug ); $downgrade_text = fs_text_x_inline( 'Downgrade', 'verb', 'downgrade', $slug ); $trial_text = fs_text_x_inline( 'Trial', 'trial period', 'trial', $slug ); $free_text = fs_text_inline( 'Free', 'free', $slug ); $activate_text = fs_text_inline( 'Activate', 'activate', $slug ); $plan_text = fs_text_x_inline( 'Plan', 'as product pricing plan', 'plan', $slug ); // Defaults. $plan = null; $is_paid_trial = false; /** * @var FS_Plugin_License $license */ $license = null; $site = null; $is_active_subscription = false; $subscription = null; $is_paying = false; $show_upgrade = false; $is_whitelabeled = $VARS['is_whitelabeled']; if ( is_object( $fs_addon ) ) { $is_paying = $fs_addon->is_paying(); $user = $fs_addon->get_user(); $site = $fs_addon->get_site(); $license = $fs_addon->_get_license(); $subscription = ( is_object( $license ) ? $fs_addon->_get_subscription( $license->id ) : null ); $plan = $fs_addon->get_plan(); $plan_name = $plan->name; $plan_title = $plan->title; $is_paid_trial = $fs_addon->is_paid_trial(); $version = $fs_addon->get_plugin_version(); $is_whitelabeled = ( $fs_addon->is_whitelabeled( true ) && ! $fs_addon->get_parent_instance()->is_data_debug_mode() ); $show_upgrade = ( ! $is_whitelabeled && $fs_addon->has_paid_plan() && ! $is_paying && ! $is_paid_trial && ! $fs_addon->_has_premium_license() ); } else if ( $is_addon_connected ) { if ( empty( $addon_info ) || ! isset( $addon_info['site'] ) ) { $is_addon_connected = false; } else { /** * @var FS_Site $site */ $site = $addon_info['site']; $version = $addon_info['version']; $plan_name = isset( $addon_info['plan_name'] ) ? $addon_info['plan_name'] : ''; $plan_title = isset( $addon_info['plan_title'] ) ? $addon_info['plan_title'] : ''; if ( isset( $addon_info['license'] ) ) { $license = $addon_info['license']; } if ( isset( $addon_info['subscription'] ) ) { $subscription = $addon_info['subscription']; } $has_valid_and_active_license = ( is_object( $license ) && $license->is_active() && $license->is_valid() ); $is_paid_trial = ( $site->is_trial() && $has_valid_and_active_license && ( $site->trial_plan_id == $license->plan_id ) ); $is_whitelabeled = $addon_info['is_whitelabeled']; } } $has_feature_enabled_license = ( is_object( $license ) && $license->is_features_enabled() ); $is_active_subscription = ( is_object( $subscription ) && $subscription->is_active() ); $show_delete_install_button = ( ! $is_paying && WP_FS__DEV_MODE && ! $is_whitelabeled ); ?> > id ?> is_trial() || is_object( $license ) ) : ?> is_trial() ) { $tags[] = array( 'label' => $trial_text, 'type' => 'success' ); $tags[] = array( 'label' => sprintf( ( $is_paid_trial ? $renews_in_text : $expires_in_text ), human_time_diff( time(), strtotime( $site->trial_ends ) ) ), 'type' => ( $is_paid_trial ? 'success' : 'warn' ) ); } else { if ( is_object( $license ) ) { if ( $license->is_cancelled ) { $tags[] = array( 'label' => fs_text_inline( 'Cancelled', 'cancelled', $slug ), 'type' => 'error' ); } else if ( $license->is_expired() ) { $tags[] = array( 'label' => fs_text_inline( 'Expired', 'expired', $slug ), 'type' => 'error' ); } else if ( $license->is_lifetime() ) { $tags[] = array( 'label' => fs_text_inline( 'No expiration', 'no-expiration', $slug ), 'type' => 'success' ); } else if ( ! $is_active_subscription && ! $license->is_first_payment_pending() ) { $tags[] = array( 'label' => sprintf( $expires_in_text, human_time_diff( time(), strtotime( $license->expiration ) ) ), 'type' => 'warn' ); } else if ( $is_active_subscription && ! $subscription->is_first_payment_pending() ) { $tags[] = array( 'label' => sprintf( $renews_in_text, human_time_diff( time(), strtotime( $subscription->next_payment ) ) ), 'type' => 'success' ); } } } foreach ( $tags as $t ) { printf( '' . "\n", $t['type'], $t['label'] ); } ?> get_id(), 'account', 'deactivate_license', fs_text_inline( 'Deactivate License', 'deactivate-license', $slug ), '', array( 'plugin_id' => $addon_id ), false, true ); $after_downgrade_message = ! $license->is_block_features ? sprintf( $after_downgrade_non_blocking_text, $plan->title, $fs_addon->get_module_label( true ) ) : sprintf( $after_downgrade_blocking_text, $plan->title ); if ( ! $license->is_lifetime() && $is_active_subscription ) { $human_readable_license_expiration = human_time_diff( time(), strtotime( $license->expiration ) ); $downgrade_confirmation_message = sprintf( $downgrade_x_confirm_text, ( $fs_addon->is_only_premium() ? $cancelling_subscription_text : $downgrading_plan_text ), $plan->title, $human_readable_license_expiration ); $buttons[] = fs_ui_get_action_button( $fs->get_id(), 'account', 'downgrade_account', esc_html( $fs_addon->is_only_premium() ? fs_text_inline( 'Cancel Subscription', 'cancel-subscription', $slug ) : $downgrade_text ), '', array( 'plugin_id' => $addon_id ), false, false, false, ( $downgrade_confirmation_message . ' ' . $after_downgrade_message . ' ' . $prices_increase_text ), 'POST' ); } } else if ( $is_paid_trial ) { $buttons[] = fs_ui_get_action_button( $fs->get_id(), 'account', 'cancel_trial', esc_html( $cancel_trial_text ), '', array( 'plugin_id' => $addon_id ), false, false, 'dashicons dashicons-download', $cancel_trial_confirm_text, 'POST' ); } else if ( ! $has_feature_enabled_license ) { $premium_licenses = $fs_addon->get_available_premium_licenses(); if ( ! empty( $premium_licenses ) ) { $premium_license = $premium_licenses[0]; $has_multiple_premium_licenses = ( 1 < count( $premium_licenses ) ); if ( ! $has_multiple_premium_licenses ) { $premium_plan = $fs_addon->_get_plan_by_id( $premium_license->plan_id ); $site = $fs_addon->get_site(); $buttons[] = fs_ui_get_action_button( $fs->get_id(), 'account', 'activate_license', esc_html( sprintf( $activate_plan_text, $premium_plan->title, ( $site->is_localhost() && $premium_license->is_free_localhost ) ? '[localhost]' : ( 1 < $premium_license->left() ? $premium_license->left() . ' left' : '' ) ) ), ($has_multiple_premium_licenses ? 'activate-license-trigger ' . $fs_addon->get_unique_affix() : ''), array( 'plugin_id' => $addon_id, 'license_id' => $premium_license->id, ), true, true ); $is_license_activation_added = true; } } } } // if ( 0 == count( $buttons ) ) { if ( $fs_addon->is_premium() && ! $is_license_activation_added ) { $fs_addon->_add_license_activation_dialog_box(); $buttons[] = fs_ui_get_action_button( $fs->get_id(), 'account', 'activate_license', ( ! $has_feature_enabled_license ) ? fs_esc_html_inline( 'Activate License', 'activate-license', $slug ) : fs_esc_html_inline( 'Change License', 'change-license', $slug ), 'activate-license-trigger ' . $fs_addon->get_unique_affix(), array( 'plugin_id' => $addon_id, ), (! $has_feature_enabled_license), true ); $is_license_activation_added = true; } if ( $fs_addon->has_paid_plan() ) { // Add sync license only if non of the other CTAs are visible. $buttons[] = fs_ui_get_action_button( $fs->get_id(), 'account', $fs->get_unique_affix() . '_sync_license', fs_esc_html_x_inline( 'Sync', 'as synchronize', 'sync', $slug ), '', array( 'plugin_id' => $addon_id ), false, true ); } // } } else if ( ! $show_upgrade ) { if ( $fs->is_addon_installed( $addon_id ) ) { $addon_file = $fs->get_addon_basename( $addon_id ); if ( ! isset( $active_plugins_directories_map[ dirname( $addon_file ) ] ) ) { $buttons[] = sprintf( '%s', wp_nonce_url( 'plugins.php?action=activate&plugin=' . $addon_file, 'activate-plugin_' . $addon_file ), fs_esc_attr_inline( 'Activate this add-on', 'activate-this-addon', $slug ), $activate_text ); } } else { if ( $fs->is_allowed_to_install() ) { $buttons[] = sprintf( '%s', wp_nonce_url( self_admin_url( 'update.php?' . ( ( isset( $addon_info['has_paid_plan'] ) && $addon_info['has_paid_plan'] ) ? 'fs_allow_updater_and_dialog=true&' : '' ) . 'action=install-plugin&plugin=' . $addon_info['slug'] ), 'install-plugin_' . $addon_info['slug'] ), fs_text_inline( 'Install Now', 'install-now', $slug ) ); } else { $buttons[] = sprintf( '%s', $fs->_get_latest_download_local_url( $addon_id ), esc_html( $download_latest_text ) ); } } } if ( $show_upgrade ) { $buttons[] = sprintf( ' %s', esc_url( network_admin_url( 'plugin-install.php?fs_allow_updater_and_dialog=true' . ( ! empty( $fs_blog_id ) ? '&fs_blog_id=' . $fs_blog_id : '' ) . '&tab=plugin-information&parent_plugin_id=' . $fs->get_id() . '&plugin=' . $addon_info['slug'] . '&TB_iframe=true&width=600&height=550' ) ), esc_attr( sprintf( fs_text_inline( 'More information about %s', 'more-information-about-x', $slug ), $addon_info['title'] ) ), esc_attr( $addon_info['title'] ), ( $fs_addon->has_free_plan() ? $upgrade_text : fs_text_x_inline( 'Purchase', 'verb', 'purchase', $slug ) ) ); } $buttons_count = count( $buttons ); ?> 1 ) : ?>
    1 ) : ?>
    is_addon_installed( $addon_id ); ?> get_addon_basename( $addon_id ) ?> is_allowed_to_install() ) : ?> get_id(), 'account', 'delete_account', fs_text_x_inline( 'Delete', 'verb', 'delete', $slug ), '', array( 'plugin_id' => $addon_id ), false, $show_upgrade ); } ?> PK!477Ilibraries/freemius/templates/account/partials/activate-license-button.phpnu[
    PK!p2WW.libraries/freemius/templates/account/index.phpnu[get_slug(); $edit_text = fs_text_x_inline( 'Edit', 'verb', 'edit', $slug ); $update_text = fs_text_x_inline( 'Update', 'verb', 'update', $slug ); $billing = $fs->_fetch_billing(); $has_billing = ( $billing instanceof FS_Billing ); if ( ! $has_billing ) { $billing = new FS_Billing(); } ?>

    > 'Afghanistan', 'AX' => 'Aland Islands', 'AL' => 'Albania', 'DZ' => 'Algeria', 'AS' => 'American Samoa', 'AD' => 'Andorra', 'AO' => 'Angola', 'AI' => 'Anguilla', 'AQ' => 'Antarctica', 'AG' => 'Antigua and Barbuda', 'AR' => 'Argentina', 'AM' => 'Armenia', 'AW' => 'Aruba', 'AU' => 'Australia', 'AT' => 'Austria', 'AZ' => 'Azerbaijan', 'BS' => 'Bahamas', 'BH' => 'Bahrain', 'BD' => 'Bangladesh', 'BB' => 'Barbados', 'BY' => 'Belarus', 'BE' => 'Belgium', 'BZ' => 'Belize', 'BJ' => 'Benin', 'BM' => 'Bermuda', 'BT' => 'Bhutan', 'BO' => 'Bolivia', 'BQ' => 'Bonaire, Saint Eustatius and Saba', 'BA' => 'Bosnia and Herzegovina', 'BW' => 'Botswana', 'BV' => 'Bouvet Island', 'BR' => 'Brazil', 'IO' => 'British Indian Ocean Territory', 'VG' => 'British Virgin Islands', 'BN' => 'Brunei', 'BG' => 'Bulgaria', 'BF' => 'Burkina Faso', 'BI' => 'Burundi', 'KH' => 'Cambodia', 'CM' => 'Cameroon', 'CA' => 'Canada', 'CV' => 'Cape Verde', 'KY' => 'Cayman Islands', 'CF' => 'Central African Republic', 'TD' => 'Chad', 'CL' => 'Chile', 'CN' => 'China', 'CX' => 'Christmas Island', 'CC' => 'Cocos Islands', 'CO' => 'Colombia', 'KM' => 'Comoros', 'CK' => 'Cook Islands', 'CR' => 'Costa Rica', 'HR' => 'Croatia', 'CU' => 'Cuba', 'CW' => 'Curacao', 'CY' => 'Cyprus', 'CZ' => 'Czech Republic', 'CD' => 'Democratic Republic of the Congo', 'DK' => 'Denmark', 'DJ' => 'Djibouti', 'DM' => 'Dominica', 'DO' => 'Dominican Republic', 'TL' => 'East Timor', 'EC' => 'Ecuador', 'EG' => 'Egypt', 'SV' => 'El Salvador', 'GQ' => 'Equatorial Guinea', 'ER' => 'Eritrea', 'EE' => 'Estonia', 'ET' => 'Ethiopia', 'FK' => 'Falkland Islands', 'FO' => 'Faroe Islands', 'FJ' => 'Fiji', 'FI' => 'Finland', 'FR' => 'France', 'GF' => 'French Guiana', 'PF' => 'French Polynesia', 'TF' => 'French Southern Territories', 'GA' => 'Gabon', 'GM' => 'Gambia', 'GE' => 'Georgia', 'DE' => 'Germany', 'GH' => 'Ghana', 'GI' => 'Gibraltar', 'GR' => 'Greece', 'GL' => 'Greenland', 'GD' => 'Grenada', 'GP' => 'Guadeloupe', 'GU' => 'Guam', 'GT' => 'Guatemala', 'GG' => 'Guernsey', 'GN' => 'Guinea', 'GW' => 'Guinea-Bissau', 'GY' => 'Guyana', 'HT' => 'Haiti', 'HM' => 'Heard Island and McDonald Islands', 'HN' => 'Honduras', 'HK' => 'Hong Kong', 'HU' => 'Hungary', 'IS' => 'Iceland', 'IN' => 'India', 'ID' => 'Indonesia', 'IR' => 'Iran', 'IQ' => 'Iraq', 'IE' => 'Ireland', 'IM' => 'Isle of Man', 'IL' => 'Israel', 'IT' => 'Italy', 'CI' => 'Ivory Coast', 'JM' => 'Jamaica', 'JP' => 'Japan', 'JE' => 'Jersey', 'JO' => 'Jordan', 'KZ' => 'Kazakhstan', 'KE' => 'Kenya', 'KI' => 'Kiribati', 'XK' => 'Kosovo', 'KW' => 'Kuwait', 'KG' => 'Kyrgyzstan', 'LA' => 'Laos', 'LV' => 'Latvia', 'LB' => 'Lebanon', 'LS' => 'Lesotho', 'LR' => 'Liberia', 'LY' => 'Libya', 'LI' => 'Liechtenstein', 'LT' => 'Lithuania', 'LU' => 'Luxembourg', 'MO' => 'Macao', 'MK' => 'Macedonia', 'MG' => 'Madagascar', 'MW' => 'Malawi', 'MY' => 'Malaysia', 'MV' => 'Maldives', 'ML' => 'Mali', 'MT' => 'Malta', 'MH' => 'Marshall Islands', 'MQ' => 'Martinique', 'MR' => 'Mauritania', 'MU' => 'Mauritius', 'YT' => 'Mayotte', 'MX' => 'Mexico', 'FM' => 'Micronesia', 'MD' => 'Moldova', 'MC' => 'Monaco', 'MN' => 'Mongolia', 'ME' => 'Montenegro', 'MS' => 'Montserrat', 'MA' => 'Morocco', 'MZ' => 'Mozambique', 'MM' => 'Myanmar', 'NA' => 'Namibia', 'NR' => 'Nauru', 'NP' => 'Nepal', 'NL' => 'Netherlands', 'NC' => 'New Caledonia', 'NZ' => 'New Zealand', 'NI' => 'Nicaragua', 'NE' => 'Niger', 'NG' => 'Nigeria', 'NU' => 'Niue', 'NF' => 'Norfolk Island', 'KP' => 'North Korea', 'MP' => 'Northern Mariana Islands', 'NO' => 'Norway', 'OM' => 'Oman', 'PK' => 'Pakistan', 'PW' => 'Palau', 'PS' => 'Palestinian Territory', 'PA' => 'Panama', 'PG' => 'Papua New Guinea', 'PY' => 'Paraguay', 'PE' => 'Peru', 'PH' => 'Philippines', 'PN' => 'Pitcairn', 'PL' => 'Poland', 'PT' => 'Portugal', 'PR' => 'Puerto Rico', 'QA' => 'Qatar', 'CG' => 'Republic of the Congo', 'RE' => 'Reunion', 'RO' => 'Romania', 'RU' => 'Russia', 'RW' => 'Rwanda', 'BL' => 'Saint Barthelemy', 'SH' => 'Saint Helena', 'KN' => 'Saint Kitts and Nevis', 'LC' => 'Saint Lucia', 'MF' => 'Saint Martin', 'PM' => 'Saint Pierre and Miquelon', 'VC' => 'Saint Vincent and the Grenadines', 'WS' => 'Samoa', 'SM' => 'San Marino', 'ST' => 'Sao Tome and Principe', 'SA' => 'Saudi Arabia', 'SN' => 'Senegal', 'RS' => 'Serbia', 'SC' => 'Seychelles', 'SL' => 'Sierra Leone', 'SG' => 'Singapore', 'SX' => 'Sint Maarten', 'SK' => 'Slovakia', 'SI' => 'Slovenia', 'SB' => 'Solomon Islands', 'SO' => 'Somalia', 'ZA' => 'South Africa', 'GS' => 'South Georgia and the South Sandwich Islands', 'KR' => 'South Korea', 'SS' => 'South Sudan', 'ES' => 'Spain', 'LK' => 'Sri Lanka', 'SD' => 'Sudan', 'SR' => 'Suriname', 'SJ' => 'Svalbard and Jan Mayen', 'SZ' => 'Swaziland', 'SE' => 'Sweden', 'CH' => 'Switzerland', 'SY' => 'Syria', 'TW' => 'Taiwan', 'TJ' => 'Tajikistan', 'TZ' => 'Tanzania', 'TH' => 'Thailand', 'TG' => 'Togo', 'TK' => 'Tokelau', 'TO' => 'Tonga', 'TT' => 'Trinidad and Tobago', 'TN' => 'Tunisia', 'TR' => 'Turkey', 'TM' => 'Turkmenistan', 'TC' => 'Turks and Caicos Islands', 'TV' => 'Tuvalu', 'VI' => 'U.S. Virgin Islands', 'UG' => 'Uganda', 'UA' => 'Ukraine', 'AE' => 'United Arab Emirates', 'GB' => 'United Kingdom', 'US' => 'United States', 'UM' => 'United States Minor Outlying Islands', 'UY' => 'Uruguay', 'UZ' => 'Uzbekistan', 'VU' => 'Vanuatu', 'VA' => 'Vatican', 'VE' => 'Venezuela', 'VN' => 'Vietnam', 'WF' => 'Wallis and Futuna', 'EH' => 'Western Sahara', 'YE' => 'Yemen', 'ZM' => 'Zambia', 'ZW' => 'Zimbabwe', ) ?>
    PK!+61libraries/freemius/templates/account/payments.phpnu[get_slug(); ?>

    >
    id ?> created ) ) ?> formatted_gross() ?> is_migrated() ) : ?>
    get_text_x_inline( 'Opt Out', 'verb', 'opt-out' ); $opt_in_text = $fs->get_text_x_inline( 'Opt In', 'verb', 'opt-in' ); if ( empty( $permission_group[ 'prompt' ] ) ) { $is_enabled = false; foreach ( $permission_group[ 'permissions' ] as $permission ) { if ( true === $permission[ 'default' ] ) { // Even if one of the permissions is on, treat as if the entire group is on. $is_enabled = true; break; } } } else { $is_enabled = ( isset( $permission_group['is_enabled'] ) && true === $permission_group['is_enabled'] ); } ?>

      render_permission( $permission ); } ?>
    PK!p2WW.libraries/freemius/templates/connect/index.phpnu[
  • class="fs-tooltip-trigger">

  • PK!!TT<libraries/freemius/templates/api-connectivity-message-js.phpnu[ PK!d9libraries/freemius/templates/js/jquery.content-change.phpnu[ PK!;libraries/freemius/templates/js/open-license-activation.phpnu[ PK!p2WW)libraries/freemius/templates/js/index.phpnu[ PK!QQ7libraries/freemius/templates/js/style-premium-theme.phpnu[get_slug(); ?> PK!c c -libraries/freemius/templates/admin-notice.phpnu[
    >
    PK!3Lff.libraries/freemius/templates/gdpr-optin-js.phpnu[ PK!|WWWlibraries/freemius/start.phpnu[=' ) && version_compare( $wp_version, '6.3.1', '<=' ) && ( 'site-editor.php' === basename( $_SERVER['SCRIPT_FILENAME'] ) || ( function_exists( 'wp_is_json_request' ) && wp_is_json_request() && ! empty( $_GET['wp_theme_preview'] ) ) ) ) { // Requiring this file since the call to get_stylesheet() below can trigger a call to wp_get_current_user() when previewing a theme. require_once ABSPATH . 'wp-includes/pluggable.php'; } /** * Get the themes directory where the active theme is located (not passing the stylesheet will make WordPress * assume that the themes directory is inside `wp-content`. * * @author Leo Fajardo (@leorw) * @since 2.2.3 */ $themes_directory = get_theme_root( get_stylesheet() ); $themes_directory_name = basename( $themes_directory ); // This change ensures that the condition works even if the SDK is located in a subdirectory (e.g., vendor) $theme_candidate_sdk_basename = str_replace( $themes_directory . '/' . get_stylesheet() . '/', '', $fs_root_path ); // Check if the current file is part of the active theme. $is_current_sdk_from_active_theme = $file_path == $themes_directory . '/' . get_stylesheet() . '/' . $theme_candidate_sdk_basename . '/' . basename( $file_path ); $is_current_sdk_from_parent_theme = false; // Check if the current file is part of the parent theme. if ( ! $is_current_sdk_from_active_theme ) { $theme_candidate_sdk_basename = str_replace( $themes_directory . '/' . get_template() . '/', '', $fs_root_path ); $is_current_sdk_from_parent_theme = $file_path == $themes_directory . '/' . get_template() . '/' . $theme_candidate_sdk_basename . '/' . basename( $file_path ); } $theme_name = null; if ( $is_current_sdk_from_active_theme ) { $theme_name = get_stylesheet(); $this_sdk_relative_path = '../' . $themes_directory_name . '/' . $theme_name . '/' . $theme_candidate_sdk_basename; $is_theme = true; } else if ( $is_current_sdk_from_parent_theme ) { $theme_name = get_template(); $this_sdk_relative_path = '../' . $themes_directory_name . '/' . $theme_name . '/' . $theme_candidate_sdk_basename; $is_theme = true; } else { $this_sdk_relative_path = plugin_basename( $fs_root_path ); $is_theme = false; /** * If this file was included from another plugin with lower SDK version, and if this plugin is symlinked, then we need to get the actual plugin path, * as the value right now will be wrong, it will only remove the directory separator from the file_path. * * The check of `fs_find_direct_caller_plugin_file` determines that this file was indeed included by a different plugin than the main plugin. */ if ( DIRECTORY_SEPARATOR . $this_sdk_relative_path === $fs_root_path && function_exists( 'fs_find_direct_caller_plugin_file' ) ) { $original_plugin_dir_name = dirname( fs_find_direct_caller_plugin_file( $file_path ) ); // Remove everything before the original plugin directory name. $this_sdk_relative_path = substr( $this_sdk_relative_path, strpos( $this_sdk_relative_path, $original_plugin_dir_name ) ); unset( $original_plugin_dir_name ); } } if ( ! isset( $fs_active_plugins ) ) { // Load all Freemius powered active plugins. $fs_active_plugins = get_option( 'fs_active_plugins' ); if ( ! is_object( $fs_active_plugins ) ) { $fs_active_plugins = new stdClass(); } if ( ! isset( $fs_active_plugins->plugins ) ) { $fs_active_plugins->plugins = array(); } } if ( empty( $fs_active_plugins->abspath ) ) { /** * Store the WP install absolute path reference to identify environment change * while replicating the storage. * * @author Vova Feldman (@svovaf) * @since 1.2.1.7 */ $fs_active_plugins->abspath = ABSPATH; } else { if ( ABSPATH !== $fs_active_plugins->abspath ) { /** * WordPress path has changed, cleanup the SDK references cache. * This resolves issues triggered when spinning a staging environments * while replicating the database. * * @author Vova Feldman (@svovaf) * @since 1.2.1.7 */ $fs_active_plugins->abspath = ABSPATH; $fs_active_plugins->plugins = array(); unset( $fs_active_plugins->newest ); } else { /** * Make sure SDK references are still valid. This resolves * issues when users hard delete modules via FTP. * * @author Vova Feldman (@svovaf) * @since 1.2.1.7 */ $has_changes = false; foreach ( $fs_active_plugins->plugins as $sdk_path => $data ) { if ( ! file_exists( ( isset( $data->type ) && 'theme' === $data->type ? $themes_directory : WP_PLUGIN_DIR ) . '/' . $sdk_path ) ) { unset( $fs_active_plugins->plugins[ $sdk_path ] ); if ( ! empty( $fs_active_plugins->newest ) && $sdk_path === $fs_active_plugins->newest->sdk_path ) { unset( $fs_active_plugins->newest ); } $has_changes = true; } } if ( $has_changes ) { if ( empty( $fs_active_plugins->plugins ) ) { unset( $fs_active_plugins->newest ); } update_option( 'fs_active_plugins', $fs_active_plugins ); } } } if ( ! function_exists( 'fs_find_direct_caller_plugin_file' ) ) { require_once dirname( __FILE__ ) . '/includes/supplements/fs-essential-functions-1.1.7.1.php'; } if ( ! function_exists( 'fs_get_plugins' ) ) { require_once dirname( __FILE__ ) . '/includes/supplements/fs-essential-functions-2.2.1.php'; } // Update current SDK info based on the SDK path. if ( ! isset( $fs_active_plugins->plugins[ $this_sdk_relative_path ] ) || $this_sdk_version != $fs_active_plugins->plugins[ $this_sdk_relative_path ]->version ) { if ( $is_theme ) { // Saving relative path and not only directory name as it could be a subfolder $plugin_path = $theme_name; } else { $plugin_path = plugin_basename( fs_find_direct_caller_plugin_file( $file_path ) ); } $fs_active_plugins->plugins[ $this_sdk_relative_path ] = (object) array( 'version' => $this_sdk_version, 'type' => ( $is_theme ? 'theme' : 'plugin' ), 'timestamp' => time(), 'plugin_path' => $plugin_path, ); } $is_current_sdk_newest = isset( $fs_active_plugins->newest ) && ( $this_sdk_relative_path == $fs_active_plugins->newest->sdk_path ); if ( ! isset( $fs_active_plugins->newest ) ) { /** * This will be executed only once, for the first time a Freemius powered plugin is activated. */ fs_update_sdk_newest_version( $this_sdk_relative_path, $fs_active_plugins->plugins[ $this_sdk_relative_path ]->plugin_path ); $is_current_sdk_newest = true; } else if ( version_compare( $fs_active_plugins->newest->version, $this_sdk_version, '<' ) ) { /** * Current SDK is newer than the newest stored SDK. */ fs_update_sdk_newest_version( $this_sdk_relative_path, $fs_active_plugins->plugins[ $this_sdk_relative_path ]->plugin_path ); if ( class_exists( 'Freemius' ) ) { // Older SDK version was already loaded. if ( ! $fs_active_plugins->newest->in_activation ) { // Re-order plugins to load this plugin first. fs_newest_sdk_plugin_first(); } // Refresh page. fs_redirect( $_SERVER['REQUEST_URI'] ); } } else { if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $fs_newest_sdk = $fs_active_plugins->newest; $fs_newest_sdk = $fs_active_plugins->plugins[ $fs_newest_sdk->sdk_path ]; $is_newest_sdk_type_theme = ( isset( $fs_newest_sdk->type ) && 'theme' === $fs_newest_sdk->type ); /** * @var bool $is_newest_sdk_module_active * True if the plugin with the newest SDK is active. * True if the newest SDK is part of the current theme or current theme's parent. * False otherwise. */ if ( ! $is_newest_sdk_type_theme ) { $is_newest_sdk_module_active = is_plugin_active( $fs_newest_sdk->plugin_path ); } else { $current_theme = wp_get_theme(); // Detect if current theme is the one registered as newer SDK $is_newest_sdk_module_active = ( strpos( $fs_newest_sdk->plugin_path, '../' . $themes_directory_name . '/' . $current_theme->get_stylesheet() . '/' ) === 0 ); $current_theme_parent = $current_theme->parent(); /** * If the current theme is a child of the theme that has the newest SDK, this prevents a redirects loop * from happening by keeping the SDK info stored in the `fs_active_plugins` option. */ if ( ! $is_newest_sdk_module_active && $current_theme_parent instanceof WP_Theme ) { // Detect if current theme parent is the one registered as newer SDK $is_newest_sdk_module_active = ( strpos( $fs_newest_sdk->plugin_path, '../' . $themes_directory_name . '/' . $current_theme_parent->get_stylesheet() . '/' ) === 0 ); } } if ( $is_current_sdk_newest && ! $is_newest_sdk_module_active && ! $fs_active_plugins->newest->in_activation ) { // If current SDK is the newest and the plugin is NOT active, it means // that the current plugin in activation mode. $fs_active_plugins->newest->in_activation = true; update_option( 'fs_active_plugins', $fs_active_plugins ); } if ( ! $is_theme ) { $sdk_starter_path = fs_normalize_path( WP_PLUGIN_DIR . '/' . $this_sdk_relative_path . '/start.php' ); } else { $sdk_starter_path = fs_normalize_path( $themes_directory . '/' . str_replace( "../{$themes_directory_name}/", '', $this_sdk_relative_path ) . '/start.php' ); } $is_newest_sdk_path_valid = ( $is_newest_sdk_module_active || $fs_active_plugins->newest->in_activation ) && file_exists( $sdk_starter_path ); if ( ! $is_newest_sdk_path_valid && ! $is_current_sdk_newest ) { // Plugin with newest SDK is no longer active, or SDK was moved to a different location. unset( $fs_active_plugins->plugins[ $fs_active_plugins->newest->sdk_path ] ); } if ( ! ( $is_newest_sdk_module_active || $fs_active_plugins->newest->in_activation ) || ! $is_newest_sdk_path_valid || // Is newest SDK downgraded. ( $this_sdk_relative_path == $fs_active_plugins->newest->sdk_path && version_compare( $fs_active_plugins->newest->version, $this_sdk_version, '>' ) ) ) { /** * Plugin with newest SDK is no longer active. * OR * The newest SDK was in the current plugin. BUT, seems like the version of * the SDK was downgraded to a lower SDK. */ // Find the active plugin with the newest SDK version and update the newest reference. fs_fallback_to_newest_active_sdk(); } else { if ( $is_newest_sdk_module_active && $this_sdk_relative_path == $fs_active_plugins->newest->sdk_path && ( $fs_active_plugins->newest->in_activation || ( class_exists( 'Freemius' ) && ( ! defined( 'WP_FS__SDK_VERSION' ) || version_compare( WP_FS__SDK_VERSION, $this_sdk_version, '<' ) ) ) ) ) { if ( $fs_active_plugins->newest->in_activation && ! $is_newest_sdk_type_theme ) { // Plugin no more in activation. $fs_active_plugins->newest->in_activation = false; update_option( 'fs_active_plugins', $fs_active_plugins ); } // Reorder plugins to load plugin with newest SDK first. if ( fs_newest_sdk_plugin_first() ) { // Refresh page after re-order to make sure activated plugin loads newest SDK. if ( class_exists( 'Freemius' ) ) { fs_redirect( $_SERVER['REQUEST_URI'] ); } } } } } if ( class_exists( 'Freemius' ) ) { // SDK was already loaded. return; } if ( isset( $fs_active_plugins->newest ) && version_compare( $this_sdk_version, $fs_active_plugins->newest->version, '<' ) ) { $newest_sdk = $fs_active_plugins->plugins[ $fs_active_plugins->newest->sdk_path ]; $plugins_or_theme_dir_path = ( ! isset( $newest_sdk->type ) || 'theme' !== $newest_sdk->type ) ? WP_PLUGIN_DIR : $themes_directory; $newest_sdk_starter = fs_normalize_path( $plugins_or_theme_dir_path . '/' . str_replace( "../{$themes_directory_name}/", '', $fs_active_plugins->newest->sdk_path ) . '/start.php' ); if ( file_exists( $newest_sdk_starter ) ) { // Reorder plugins to load plugin with newest SDK first. fs_newest_sdk_plugin_first(); // There's a newer SDK version, load it instead of the current one! require_once $newest_sdk_starter; return; } } #endregion SDK Selection Logic -------------------------------------------------------------------- #region Hooks & Filters Collection -------------------------------------------------------------------- /** * Freemius hooks (actions & filters) tags structure: * * fs_{filter/action_name}_{plugin_slug} * * -------------------------------------------------------- * * Usage with WordPress' add_action() / add_filter(): * * add_action('fs_{filter/action_name}_{plugin_slug}', $callable); * * -------------------------------------------------------- * * Usage with Freemius' instance add_action() / add_filter(): * * // No need to add 'fs_' prefix nor '_{plugin_slug}' suffix. * my_freemius()->add_action('{action_name}', $callable); * * -------------------------------------------------------- * * Freemius filters collection: * * fs_connect_url_{plugin_slug} * fs_trial_promotion_message_{plugin_slug} * fs_is_long_term_user_{plugin_slug} * fs_uninstall_reasons_{plugin_slug} * fs_is_plugin_update_{plugin_slug} * fs_api_domains_{plugin_slug} * fs_email_template_sections_{plugin_slug} * fs_support_forum_submenu_{plugin_slug} * fs_support_forum_url_{plugin_slug} * fs_connect_message_{plugin_slug} * fs_connect_message_on_update_{plugin_slug} * fs_uninstall_confirmation_message_{plugin_slug} * fs_pending_activation_message_{plugin_slug} * fs_is_submenu_visible_{plugin_slug} * fs_plugin_icon_{plugin_slug} * fs_show_trial_{plugin_slug} * * -------------------------------------------------------- * * Freemius actions collection: * * fs_after_license_loaded_{plugin_slug} * fs_after_license_change_{plugin_slug} * fs_after_plans_sync_{plugin_slug} * * fs_after_account_details_{plugin_slug} * fs_after_account_user_sync_{plugin_slug} * fs_after_account_plan_sync_{plugin_slug} * fs_before_account_load_{plugin_slug} * fs_after_account_connection_{plugin_slug} * fs_account_property_edit_{plugin_slug} * fs_account_email_verified_{plugin_slug} * fs_account_page_load_before_departure_{plugin_slug} * fs_before_account_delete_{plugin_slug} * fs_after_account_delete_{plugin_slug} * * fs_sdk_version_update_{plugin_slug} * fs_plugin_version_update_{plugin_slug} * * fs_initiated_{plugin_slug} * fs_after_init_plugin_registered_{plugin_slug} * fs_after_init_plugin_anonymous_{plugin_slug} * fs_after_init_plugin_pending_activations_{plugin_slug} * fs_after_init_addon_registered_{plugin_slug} * fs_after_init_addon_anonymous_{plugin_slug} * fs_after_init_addon_pending_activations_{plugin_slug} * * fs_after_premium_version_activation_{plugin_slug} * fs_after_free_version_reactivation_{plugin_slug} * * fs_after_uninstall_{plugin_slug} * fs_before_admin_menu_init_{plugin_slug} */ #endregion Hooks & Filters Collection -------------------------------------------------------------------- if ( ! class_exists( 'Freemius' ) ) { if ( ! defined( 'WP_FS__SDK_VERSION' ) ) { define( 'WP_FS__SDK_VERSION', $this_sdk_version ); } $plugins_or_theme_dir_path = fs_normalize_path( trailingslashit( $is_theme ? $themes_directory : WP_PLUGIN_DIR ) ); if ( 0 === strpos( $file_path, $plugins_or_theme_dir_path ) ) { // No symlinks } else { /** * This logic finds the SDK symlink and set WP_FS__DIR to use it. * * @author Vova Feldman (@svovaf) * @since 1.2.2.5 */ $sdk_symlink = null; // Try to load SDK's symlink from cache. if ( isset( $fs_active_plugins->plugins[ $this_sdk_relative_path ] ) && is_object( $fs_active_plugins->plugins[ $this_sdk_relative_path ] ) && ! empty( $fs_active_plugins->plugins[ $this_sdk_relative_path ]->sdk_symlink ) ) { $sdk_symlink = $fs_active_plugins->plugins[ $this_sdk_relative_path ]->sdk_symlink; if ( 0 === strpos( $sdk_symlink, $plugins_or_theme_dir_path ) ) { /** * Make the symlink path relative. * * @author Leo Fajardo (@leorw) */ $sdk_symlink = substr( $sdk_symlink, strlen( $plugins_or_theme_dir_path ) ); $fs_active_plugins->plugins[ $this_sdk_relative_path ]->sdk_symlink = $sdk_symlink; update_option( 'fs_active_plugins', $fs_active_plugins ); } $realpath = realpath( $plugins_or_theme_dir_path . $sdk_symlink ); if ( ! is_string( $realpath ) || ! file_exists( $realpath ) ) { $sdk_symlink = null; } } if ( empty( $sdk_symlink ) ) // Has symlinks, therefore, we need to configure WP_FS__DIR based on the symlink. { $partial_path_right = basename( $file_path ); $partial_path_left = dirname( $file_path ); $realpath = realpath( $plugins_or_theme_dir_path . $partial_path_right ); while ( '/' !== $partial_path_left && ( false === $realpath || $file_path !== fs_normalize_path( $realpath ) ) ) { $partial_path_right = trailingslashit( basename( $partial_path_left ) ) . $partial_path_right; $partial_path_left_prev = $partial_path_left; $partial_path_left = dirname( $partial_path_left_prev ); /** * Avoid infinite loop if for example `$partial_path_left_prev` is `C:/`, in this case, * `dirname( 'C:/' )` will return `C:/`. * * @author Leo Fajardo (@leorw) */ if ( $partial_path_left === $partial_path_left_prev ) { $partial_path_left = ''; break; } $realpath = realpath( $plugins_or_theme_dir_path . $partial_path_right ); } if ( ! empty( $partial_path_left ) && '/' !== $partial_path_left ) { $sdk_symlink = fs_normalize_path( dirname( $partial_path_right ) ); // Cache value. if ( isset( $fs_active_plugins->plugins[ $this_sdk_relative_path ] ) && is_object( $fs_active_plugins->plugins[ $this_sdk_relative_path ] ) ) { $fs_active_plugins->plugins[ $this_sdk_relative_path ]->sdk_symlink = $sdk_symlink; update_option( 'fs_active_plugins', $fs_active_plugins ); } } } if ( ! empty( $sdk_symlink ) ) { // Set SDK dir to the symlink path. define( 'WP_FS__DIR', $plugins_or_theme_dir_path . $sdk_symlink ); } } // Load SDK files. require_once dirname( __FILE__ ) . '/require.php'; /** * Quick shortcut to get Freemius for specified plugin. * Used by various templates. * * @param number $module_id * * @return Freemius */ function freemius( $module_id ) { return Freemius::instance( $module_id ); } /** * @param string $slug * @param number $plugin_id * @param string $public_key * @param bool $is_live Is live or test plugin. * @param bool $is_premium Hints freemius if running the premium plugin or not. * * @return Freemius * * @deprecated Please use fs_dynamic_init(). */ function fs_init( $slug, $plugin_id, $public_key, $is_live = true, $is_premium = true ) { $fs = Freemius::instance( $plugin_id, $slug, true ); $fs->init( $plugin_id, $public_key, $is_live, $is_premium ); return $fs; } /** * @param array $module Plugin or Theme details. * * @return Freemius * @throws Freemius_Exception */ function fs_dynamic_init( $module ) { $fs = Freemius::instance( $module['id'], $module['slug'], true ); $fs->dynamic_init( $module ); return $fs; } function fs_dump_log() { FS_Logger::dump(); } } PK!d"faa.libraries/freemius/languages/freemius-hu_HU.monu[{ nS< _  "*37Vv~ &-M bl   ,2I!|   )FMa u    ((3:\    x , 9CW_y   a u {      j!5o! !!!!!)"--"["o"'""7""# #)#<#R# Z#d#1#.,$[$A$%=%S%BW%,%% %%% &&"&*& <& G&S& c&o&& &&& &&&&& &&&&''!'('0'L' R'^' g' r''''''-'U'N(m(t( ((((*("(1)+F)*r)&)))).)*)*I*Q* `* k*v** ** **** **++)+0+4+=+ E+Q+ c+n+}++++ ++ ++ + ++ +, ,#, 4, ?,J,_,r,C,,,- -9- ?-L-'S- {-----------*-.*.^I...... . ../" /6,/=c// //-/0!070*Q0|0000&0-091<X1U1K1(72G`2#2%2)2$3)A3k3}3>333$4,4F4b4~4&4*474&5=5[53t555555*61G6y66#6666 7+7B7 W7d77777771 8;8C8W8 h8 t8 8 888C88Q9U9 e9 r9|99 99 9 9 9 9 9 9 9 9 ::~>(b? ?u? @@ ,@ 7@B@E@H@L@U@^@d@ x@ @@@@@@;A3?AsAAA AAAAA$AB -B;B YBdBuB B*BBB C C +C 8C ECPCXC]CfCCCCCCC C DD0DFD_DtD DDD'D DCD 7EAEFE WEaE EE#EEE EEEF 1G?GTG kGvGG#G GGG!H1HFH LHeWHHHHHHIII'I;I LIYIoICI 9JEJNJ'cJJ#J2JJK-KMKPK!lK"KKKKKL L.L2MCM<M#N4NQNYWN'NNN'NO get_text_inline;$this->get_text_x_inline:1,2c;$this->_fs->get_text_inline;$this->_fs->get_text_x_inline:1,2c;$this->fs->get_text_inline;$this->fs->get_text_x_inline:1,2c;$fs->get_text_inline;$fs->get_text_x_inline:1,2c;$this->_parent->get_text_inline;$this->_parent->get_text_x_inline:1,2c;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c X-Poedit-SearchPath-0: . X-Poedit-SearchPathExcluded-0: *.js X-Poedit-SourceCharset: UTF-8 A(z) %1$s fizetős verziója sikeresen telepítve. Kérlek aktiváld, hogy a(z) %2$s minden funkciója elérhető legyen. %3$s"%s" aktiválásának a befejezése most%s licenszA(z) %s egy prémium kiegészítő. A bővítmény aktiválásához előbb egy érvényes licenszt kell vásárolnod.%s értékelés%s értékelés%s csillag%s csillag%s%sAPI←➤FiókFiók információkEseményekAktiválás%s aktiválása%s csomag aktiválása%s funkciók aktiválásaIngyenes verzió aktiválásaLicensz aktiválásaLicensz aktiválása a hálózat minden függő aloldalán.Licensz aktiválása a hálózat minden aloldalán.Kiegészítő bekapcsolásaSikeres aktiválásMásik domain hozzáadásaKiegészítőKiegészítőkCímCím %dAjánlórendszerLicensz elfogadása és aktiválásaMinden kérésMinden típusEngedélyezés és folytatásMennyiségIsmeretlen hiba.Névtelen visszajelzésJelentkezés ajánló partnernekBiztosan törölsz minden Freemius adatot?Biztosan ezt szeretnéd?Automatikus telepítésÁtlagos értékelésNagyszerűSzámlázásSzámlázásBlokkolásBlog IDBodyCégnévVásárolj licenszet mostLicensz vásárlásaNem találod a licensz kulcsod?MégsemTelepítés törléseElőfizetés törlésePróbaidő törléseTörölve%s törlése...Előfizetés törléseLicensz módosításaTulajdonos módosításaCsomag módosításaFelhasználó módosításaPénztárVárosAPI gyorsítótár törléseÁtmeneti frissítési adatok törléseKattints ideKattints ide, ha névtelenül szeretnéd használni a bővítménytTermékekKódKompatibilitás:KapcsolatÍrás az ügyfélszolgálatraKapcsolatKözreműködőkA(z) %s aktiválása nem sikerült.OrszágDátumDeaktiválásLicensz deaktiválásaA(z) %s deaktiválása vagy törlése automatikusan törli az oldalhoz tartozó licenszed is, amit így másik weboldalon tudsz újra aktiválni.A licensz deaktiválása után a prémium funkciók használata nem elérhető, de így tudod másik weboldalon aktiválni ugyanezt a licenszt. Folytatod a deaktiválást?DeaktiválásHibakeresési naplóMinden fiók törléseRészletekNincs még licensz kulcsod?Bővítmény támogatásaElőfizetés módosítása kisebbreLetöltés%s verzió letöltéseFizetős verzió letöltéseTöltsd le a legfrissebb verziótLetöltések száma:EmailEmail címAdd meg az email címet, amit a vásárlás során használtál és újraküldjük a licensz kulcsot.HibaHiba érkezett a szervertől.LejártHátralévő idő: %sTovábbi domainekFájlSzűrőIngyenesIngyenes próbaidőIngyenes verzióFreemius APIFreemius hibakeresésA Freemius SDK nem találja a bővítmény fő fájlját. Kérlek írj az sdk@freemius.com email címre a problémával kapcsolatban.A licenszkezelést és szoftver frissítést a Freemius biztosítjaTeljes névFunkcióVan licensz kulcsod?Hogyan kell feltölteni és aktiválni?Nem tudom tovább fizetniNem értettem, hogy kell használniNem szeretném megosztani veletek az információtJobb %st találtamNincs tovább szükségem ráCsak egy kis időre használtamIDKérlek mondd el, miért %sFontos frissítési információ:Ingyenes verzió telepítése mostTelepítés mostFrissítés telepítése mostBővítmény telepítése: %sSzámlaAktívEz egy ügyfélnek készült weboldal? %s ha szeretnéd inkább elrejteni az admin felületről az érzékeny információkat, mint például email cím, licensz kulcs, árak, számlázási adatok és számlák.Úgy tűnik, hogy a licensz nem aktiválható.Úgy tűnik a licensz kikapcsolása nem sikerült.Úgy tűnik, hogy még mindig a %s csomagban vagy. Ha biztosan előfizettél vagy csomagot váltottál, akkor valószínű a hiba a mi oldalunkon van. Elnézést!Úgy tűnik, hogy a weboldalhoz nem tartozik aktív licensz.Nem ezt kerestemJelentkezz a Béta programbaKulcsHa elmondod mi nem működött, ki tudjuk javítani a leendő felhasználók számára...Ha elmondod az okát, tudunk fejlődni.UtolsóUtolsó frissítésLegfrissebb ingyenes verzió telepítveLegfrissebb verzió telepítveBővebbenHosszLicenszLicensz szerződésLicensz IDLicensz kulcsLicensszel kapcsolatos problémák?Licensz kulcsA licensz kulcs üres.ÖrökLocalhostÜzenetMódModul útvonalTovábbi információNévÚjÚj verzió érhető elHírlevélKövetkezőNemNincs IDBankkártya megadása nem kötelezőRendbenFeliratkozásLeiratkozásIratkozz fel, hogy a(z) %s még jobb lehessen!EgyébTulajdonos email címeTulajdonos IDTulajdonos nevePCI megfelelésPayPal fiók email címeFizetési módokCsomagCsomag IDÍrj nekünk ittKérlek írj nekünk a következő üzenettel:Kérlek add meg a licensz kulcsot, amit emailben kaptál a vásárlásod után:Kérlek add meg a teljes neved!BővítményBővítmény oldalaBővítmény IDBővítmény telepítéseVáltoztatásokLeírásGYIKFunkciók & ÁrakTelepítésEgyéb megjegyzésekVéleményekBővítményekBővítmények és sablonok szinkronizálásaPrémium%s prémium verziója sikeresen aktiválva.Prémium verzióA prémium verzió már aktív.ÁrakAdatkezelési tájékoztatóMűvelet IDFeldolgozás alattTermékekTartományPublikus kulcsLicensz vásárlásaVásárlás folytatásaGyors visszajelzésRendelkezésre állAktivációs email újraküldéseLicensz megújításaLicensz kulcs megújításaKérésekA következő WordPress verzió szükséges:EredménySDKSDK útvonal%s mentéseKépernyőfotókKeresés cím alapjánTitkos kulcsVálaszz országotLicensz kulcs küldéseEgy weboldalas licenszWeboldal IDWeboldalakKihagyás & %sKözvetlen hivatkozásPróbaidő indításaMegyeKüldés & %sElőfizetésÜgyfélszolgálatTámogató fórumAdatok szinkronizálása a szerverrőlKözösségi adószámSzolgáltatási feltételekKöszönjük!Köszönjük %s!A(z) %s hibát generált az oldalonA(z) %s nem működöttA %s nem az elvárásoknak megfelelően működöttA(z) %s nagyszerű, de nekem olyan funkcióra van szükségem, amit nem tudA(z) %s nem működikA(z) %s egyszer csak nem működöttA(z) %s frissítése sikeres volt.SablonSablon váltásSablonokA(z) %s új verziója érhető el.IdőbélyegCímÖsszesenTelepülésPróbaidőTípusKorlátlan licenszKorlátlan frissítésWeboldalak száma: %sFrissítésLicensz frissítéseFrissítések, közlemények, marketing, de semmi SPAM!ElőfizetésTöltsd fel és aktiváld a letöltött verziótFantasztikusFelhasználó vezérlőpultFelhasználó IDFelhasználókÉrtékEllenőrzöttEmail ellenőrzéseRészletek megtekintéseFizetős funkciók megtekintéseFigyelmeztetésNéhány frissítést kapott a %s. %sWeboldal, email és közösségi média statisztikák (opcionális)Köszönjük, hogy a(z) %s szoftverünket választottad! Kérlek add meg a licensz kulcsod: Mire számítottál?Melyik funkcióra van szükséged?Mi a te %s?Mi lenne az elfogadható ár, amit tudnál fizetni?Pontosan mit kerestél?Mi a %s neve?WordPress.org bővítmény oldalSzeretnéd folytatni a frissítést?IgenMinden rendben!Már csak egy lépés van hátra - %sLicensz típusa: %sA(z) %s sikeresen frissítve.A(z) %s licensz kikapcsolása sikerült.A fiókodat sikeresen aktiváltuk a következő csomaggal: %sAz email címedet sikerült ellenőrizni - ez nagyszerű!A licensz kikapcsolásra került. Ha úgy gondolod, hogy ez tévedés volt, kérlek írj az ügyfélszolgálatra!A licensz lejárt. A(z) %s ingyenes verziója továbbra is használható korlátlanul.A licensz sikeresen aktiválva.A licenszedet sikeresen deaktiváltuk, az aktuális csomagod: %sA neved sikeresen frissítettük.Az előfizetés sikeresen aktiválva.Az előfizetés sikeresen módosítva: %s.Az előfizetés sikeresen frissítve.A próbaidőszakodat sikeresen aktiváltuk.IrányítószámAktívFigyelemAktiválásévAPIMégsemHibakeresésGratulálunkBlokkolvaKapcsolódvaLegfrissebb verzió letöltéseA legfrissebb ingyenes verzió letöltéseHaviLejáratÚtvonalEmail küldésehóÉvesÉvesEgyszeriCsomagNincs titkos kulcsSDK verziókLicenszSzinkronizálásLicensz szinkronizálásaSzerzőKiBeIngyenes próbaidőszak indításaMégsemMégsemdeaktiválod%s csomag%s számlázásLegjobbÜdvHoppáÜdv %s!TelepítveJuhuuulicenszWeboldalakúj Béta verzióúj verziónem ellenőrzöttÁrÁrakválaszthatóVerziókérek biztonsági és funkcionális frissítéseket, használati ismertetőket és ajánlatokat.ugrásHmmpróbaidő indításaelőfizetésváltaszpróbaidőPróbaidőTörlésVáltás kisebb csomagraSzerkesztésElrejtFeliratkozásLeiratkozásVásárlásMutasdUgrásFrissítésVáltás nagyobb csomagraPK!?,l+libraries/freemius/languages/freemius-ja.monu[<\\S]%  6 @_#U y      H '!O:!!!!!! !!!!&!-""P" e"o"~""""5""" # #'#A# Z# g#q#o###$$"$$2%!:%a\%0%%&&'&/&C&K&T&\& a&o&&&& & &Y&,';' L'X'a'f'v'('1'%':(K(P(a(i( y( ((( (( ((( q) ~)))))))) *(* D*O*[*fC++ ++Y+a,~,,, , ,;,- --. . . ,.9.jH.. ..3. /~/U//0'0)B0-l00S01'1B1ME171g1p32222 2233+3 331=3.o3O33An4y4*5aJ55B5,5 6 %6 26?6]6 v666 6 666466 777 7(7 /7;7 B7 N7Z7t7 y7 777!77 7777%8++8W8 o8 }8/88m8,939;9 A9M9 V9 a9)o99949959(/:X:`:-w::U:;1;~ <[<<= = =%=(4=*]="=1=+=* >&4>N[>>>>.>)>)?9?Y?a? p?{???? ????W? B@P@Y@t@{@@@@ @@ @5@l@&lAAA AAAAAB B"B&'BLNBfBC CC%C +C 7CDC LCZC pC}CC$D/DD) E 5E @E\KEEEECE3FIFiFdF-TGG GG'GMGGH ZHdHjHpHuH{HEHHHHIII*'IRI*ZI^IIIIdI]J fJsJ JJJiJWK"vKBK6KL (L6L-GLuLL&LLLL$LMMjM/|MM>M N&"NZINTNRN.LO.{O9OZO3?P<sPbPPQUdQ_QRKR(SG*S#rS)S$S)ST!T>T;ST6T>TU U&UFU$\UUUUU&U*V7CV{VVV3VVW(W@WTW*qW1WWW#W"X>XPX `XlXXX XXNXYU7G Zdw U. 6C@ H<׉ 9!H[-^Ҋ1Zo--$ $3 Xelp f$*OH_WH hrǐ  #vBґ&   %2Jf |Buɒ$? dq!B$ 3' [h z;iÔ-ٕ  <I'e >B/<!+*!֙-]&'˚38 lv ;q̜t> ɝ ֝ ]_x!˞Ҟ9%K;   MZ1jˠҠr. y;Z"3 J;k"ʣA)*T[0bl; Sft*ۥ07eʦr0NB=5sK NUrY̪Tr3jT34'9\3ʮ ݮ?K.z   ˯د߯65 9FM`dkr yðʰ !&- 4AH ȱձ    $1 4> EO_uc ٲ 1BRYo v ų۳%s - plugin name. As complete "PluginX" activation nowComplete "%s" Activation Now%s Add-on was successfully purchased.%s Installs%s Licenses%s ago%s and its add-ons%s commission when a customer purchases a new license.%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license.%s is a premium only add-on. You have to purchase a license first before activating the plugin.%s is the new owner of the account.%s minimum payout amount.%s or higher%s rating%s ratings%s sec%s star%s stars%s time%s times%s tracking cookie after the first visit to maximize earnings potential.%s's paid features%sClick here%s to choose the sites where you'd like to activate the license on.APIAccountAccount DetailsActionsActivateActivate %sActivate %s PlanActivate Free VersionActivate LicenseActivate license on all pending sites.Activate license on all sites in the network.Activate this add-onActivatedAdd Ons for %sAdd Ons of module %sAdd another domainAdd-OnAdd-OnsAdd-on must be deployed to WordPress.org or Freemius.AddressAddress Line %dAffiliateAffiliationAfter your free %s, pay as little as %sAgree & Activate LicenseAll RequestsAll TypesAllow & ContinueAlternatively, you can skip it for now and activate the license later, in your %s's network-level Account page.AmountAn automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now.Anonymous feedbackApply on all pending sites.Apply on all sites in the network.Apply to become an affiliateAre you sure you want to delete all Freemius data?Are you sure you want to proceed?As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days.Auto installation only works for opted-in users.Auto renews in %sAutomatic InstallationAverage RatingAwesomeBecome an affiliateBillingBlockingBlog IDBodyBusiness nameCan't find your license key?CancelCancel InstallationCancel SubscriptionCancel TrialCancelledCancelling the trial will immediately block access to all premium features. Are you sure?Change LicenseChange OwnershipChange PlanCheckoutCityClear API CacheClear Updates TransientsClick here to use the plugin anonymouslyClick to see reviews that provided a rating of %sClick to view full-size screenshot %dClone resolution admin notice products list labelProductsCodeCompatible up toContactContact SupportContact UsContributorsCouldn't activate %s.CountryCron TypeDateDeactivateDeactivate LicenseDeactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?DeactivationDebug LogDelegate to Site AdminsDelete All AccountsDetailsDon't have a license key?Donate to this pluginDownloadDownload %s VersionDownload the latest %s versionDownload the latest versionDownloadedDue to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.During the update process we detected %d site(s) that are still pending license activation.During the update process we detected %s site(s) in the network that are still pending your attention.EmailEmail addressEndEnter the domain of your website or other websites from where you plan to promote the %s.Enter the email address you've used for the upgrade below and we will resend you the license key.ErrorError received from the server:ExpiredExpires in %sExtra DomainsExtra domains where you will be marketing the product from.FileFilterFor compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.FreeFree TrialFree versionFreemius APIFreemius DebugFreemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error.Freemius StateFull nameFunctionGet commission for automated subscription renewals.Have a license key?Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!How do you like %s so far? Test all our %s premium features with a %d-day free trial.How to upload and activate?How will you promote us?I can't pay for it anymoreI couldn't understand how to make it workI don't like to share my information with youI found a better %sI have upgraded my account but when I try to Sync the License, the plan remains %s.I no longer need the %sI only needed the %s for a short periodIDIf you click it, this decision will be delegated to the sites administrators.If you have a moment, please let us know why you are %sIf you would like to give up the ownership of the %s's account to %s click the Change Ownership button.If you'd like to use the %s on those sites, please enter your license key below and click the activation button.In %sInstall Free Version NowInstall Free Version Update NowInstall NowInstall Update NowInstalling plugin: %sInvalid module ID.InvoiceIs ActiveIt looks like the license could not be activated.It looks like the license deactivation failed.It looks like you are not in trial mode anymore so there's nothing to cancel :)It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry.It looks like your site currently doesn't have an active license.It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.It's not what I was looking forJust letting you know that the add-ons information of %s is being pulled from an external server.KeyKindly share what didn't work so we can fix it for future users...Kindly tell us the reason so we can improve.LastLast UpdatedLast licenseLatest Free Version InstalledLatest Version InstalledLearn moreLengthLicenseLicense KeyLicense keyLicense key is empty.LifetimeLike the %s? Become our ambassador and earn cash ;-)Load DB OptionLocalhostLogLoggerMessageMethodMobile appsModuleModule PathModule TypeMore information about %sNameNetwork BlogNetwork UserNewNew Version AvailableNewer Free Version (%s) InstalledNewer Version (%s) InstalledNewsletterNextNoNo IDNo commitment for %s - cancel anytimeNo commitment for %s days - cancel anytime!No credit card requiredNo expirationNon-expiringNone of the %s's plans supports a trial period.O.KOnce your license expires you can still use the Free version but you will NOT have access to the %s features.Opt InOpt OutOtherOwner EmailOwner IDOwner NamePCI compliantPaid add-on must be deployed to Freemius.PayPal account email addressPaymentsPayouts are in USD and processed monthly via PayPal.PlanPlan %s do not exist, therefore, can't start a trial.Plan %s does not support a trial period.Plan IDPlease contact us herePlease contact us with the following message:Please download %s.Please enter the license key that you received in the email right after the purchase:Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential).Please follow these steps to complete the upgradePlease let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:Please provide details on how you intend to promote %s (please be as specific as possible).Please provide your full name.PluginPlugin HomepagePlugin IDPlugin InstallPlugin installer section titleChangelogPlugin installer section titleDescriptionPlugin installer section titleFAQPlugin installer section titleFeatures & PricingPlugin installer section titleInstallationPlugin installer section titleOther NotesPlugin installer section titleReviewsPlugin is a "Serviceware" which means it does not have a premium code version.PluginsPlugins & Themes SyncPremiumPremium %s version was successfully activated.Premium add-on version already installed.Premium versionPremium version already active.PricingPrivacy PolicyProcess IDProductsProgram SummaryPromotion methodsProvincePublic KeyPurchase LicenseQuotaRe-send activation emailRefer new customers to our %s and earn %s commission on each successful sale you refer!Renew licenseRequestsRequires WordPress VersionResultSDKSDK PathSave %sScheduled CronsScreenshotsSearch by addressSecret KeySecure HTTPS %s page, running from an external domainSeems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.Seems like you got the latest release.Select CountrySend License KeySet DB OptionSimulate Network UpgradeSingle Site LicenseSite IDSite successfully opted in.SitesSkip & %sSlugSocial media (Facebook, Twitter, etc.)Sorry for the inconvenience and we are here to help if you give us a chance.Sorry, we could not complete the email update. Another user with the same email is already registered.StartStart TrialStart my free %sStateSubmit & %sSubscriptionSupportSupport ForumSync Data From ServerTax / VAT IDTerms of ServiceThank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days.Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information.Thank you so much for using %s and its add-ons!Thank you so much for using %s!Thank you so much for using our products!Thank you!Thanks %s!Thanks for confirming the ownership change. An email was just sent to %s for final approval.The %s broke my siteThe %s didn't workThe %s didn't work as expectedThe %s is great, but I need specific feature that you don't supportThe %s is not workingThe %s suddenly stopped workingThe installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page.The remote plugin package does not contain a folder with the desired slug and renaming did not work.The upgrade of %s was successfully completed.ThemeTheme SwitchThemesThere is a new version of %s available.This plugin has not been marked as compatible with your version of WordPress.This plugin has not been tested with your current version of WordPress.TimestampTitleTotalTownTrialTypeUnable to connect to the filesystem. Please confirm your credentials.Unlimited LicensesUnlimited UpdatesUnlimited commissions.Up to %s SitesUpdateUpdate LicenseUpdates, announcements, marketing, no spamUpgradeUpload and activate the downloaded versionUsed to express elation, enthusiasm, or triumph (especially in electronic communication).W00tUser IDUsersValueVerification mail was just sent to %s. If you can't find it after 5 min, please check your spam box.VerifiedVerify EmailVersion %s was released.View detailsView paid featuresWarningWe can't see any active licenses associated with that email address, are you sure it's the right address?We couldn't find your email address in the system, are you sure it's the right address?We made a few tweaks to the %s, %sWe're excited to introduce the Freemius network-level integration.Website, email, and social media statistics (optional)What did you expect?What feature?What is your %s?What price would you feel comfortable paying?What you've been looking for?What's the %s's name?Where are you going to promote the %s?WordPress.org Plugin PageYesYes - %sYou already utilized a trial before.You are 1-click away from starting your %1$s-day free trial of the %2$s plan.You are all good!You are already running the %s in a trial mode.You are just one step away - %sYou do not have a valid license to access the premium version.You have a %s license.You have successfully updated your %s.You might have missed it, but you don't have to share any data and can just %s the opt-in.You've already opted-in to our usage-tracking, which helps us keep improving the %s.You've already opted-in to our usage-tracking, which helps us keep improving them.Your %s Add-on plan was successfully upgraded.Your %s free trial was successfully cancelled.Your account was successfully activated with the %s plan.Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s.Your affiliation account was temporarily suspended.Your email has been successfully verified - you are AWESOME!Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your free trial has expired. You can still continue using all our free features.Your license has been cancelled. If you think it's a mistake, please contact support.Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support.Your license has expired. You can still continue using the free %s forever.Your license was successfully activated.Your license was successfully deactivated, you are back to the %s plan.Your name was successfully updated.Your plan was successfully changed to %s.Your plan was successfully upgraded.Your trial has been successfully started.ZIP / Postal Codea positive responseRight onactive add-onActiveaddonX cannot run without pluginY%s cannot run without %s.addonX cannot run...%s cannot run without the plugin.advance notice of something that will need attention.Heads upallowas 5 licenses left%s leftas activating pluginActivatingas annual periodyearas application program interfaceAPIas close a windowDismissas code debuggingDebuggingas congratulationsCongratsas connection blockedBlockedas connection was successfulConnectedas download latest versionDownload Latestas download latest versionDownload Latest Free Versionas every monthMonthlyas expiration dateExpirationas file/folder pathPathas in the process of sending an emailSending emailas monthly periodmoas once a yearAnnualas once a yearAnnuallyas once a yearOnceas product pricing planPlanas secret encryption key missingNo Secretas software development kit versionsSDK Versionsas software licenseLicenseas synchronizeSyncas synchronize licenseSync Licenseas the plugin authorAuthoras turned offOffas turned onOnbased on %scall to actionStart free trialclose a windowDismissclose windowDismissdeactivatingdelegatedo %sNOT%s send me security & feature updates, educational content and offers.e.g. Professional Plan%s Plane.g. billed monthlyBilled %se.g. the best productBestexclamationHeyexclamationOopsgreetingHey %s,interjection expressing joy or exuberanceYee-hawlike websitesSitesmillisecondsmsnot verifiednounPricenounPricingproduct versionVersionsecondssecsend me security & feature updates, educational content and offers.skipsomething somebody says when they are thinking about what you have just said.Hmmstart the trialswitchingthe latest %s version heretrial periodTrialverbDeleteverbDowngradeverbEditverbHideverbOpt InverbOpt OutverbPurchaseverbShowverbSkipverbUpdateverbUpgradex-ago%s agoProject-Id-Version: WordPress SDK Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues PO-Revision-Date: 2015-10-01 21:38+0000 Last-Translator: Tomohyco Tsunoda, 2018 Language-Team: Japanese (http://app.transifex.com/freemius/wordpress-sdk/language/ja/) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Language: ja Plural-Forms: nplurals=1; plural=0; X-Poedit-Basepath: .. X-Poedit-KeywordsList: get_text_inline;get_text_x_inline:1,2c;$this->get_text_inline;$this->get_text_x_inline:1,2c;$this->_fs->get_text_inline;$this->_fs->get_text_x_inline:1,2c;$this->fs->get_text_inline;$this->fs->get_text_x_inline:1,2c;$fs->get_text_inline;$fs->get_text_x_inline:1,2c;$this->_parent->get_text_inline;$this->_parent->get_text_x_inline:1,2c;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c X-Poedit-SearchPath-0: . X-Poedit-SearchPathExcluded-0: *.js X-Poedit-SourceCharset: UTF-8 すぐに "%s" 有効化を完了してください%s のアドオンの支払いが完了しました。%sインストール%sラインセス%s 前%sとそのアドオンカスタマーが新規ライセンスを購入するごとに%sのコミッションが発生します。%s の無料試用が正常にキャンセルされました。 アドオンはプレミアムなので、自動的に無効化されました。 将来使用したい場合は、ライセンスを購入する必要があります。%s はプレミアムのみのアドオンです。そのプラグインを有効化する前にライセンスを購入する必要があります。%s は新しいオーナーです。%sお支払いの最低金額%sまたはそれ以上%s評価%s評価%s秒%sスター%sスター%s回%s回%s初回の訪問後、クッキーをトラッキングして収益の可能性を最大化しましょう。%sの有料機能%sここをクリックして%s ライセンスを有効化したいサイトを選択してください。APIアカウントアカウント詳細アクション有効化%sを有効化する%s プランを有効化フリーバージョンを有効化ライセンスを有効化保留中のサイトすべてでライセンスを有効にする。ネットワーク上にあるすべてのサイトのライセンスを有効にする。このアドオンを有効化有効化済み%s のアドオンモジュールのアドオン%sドメイン名を追加するアドオンアドオンアドオンが WordPress.org か Freemius にデプロイされている必要があります。住所住所欄 %dアフィリエイトアフィリエイト無料の %s の後は、わずか %s だけお支払ください。同意してライセンスを有効化すべてのリクエストすべてのタイプ許可して続けるまたは、今すぐスキップして、%sのネットワークレベルのアカウントページでライセンスを有効にすることもできます。総額%sから %s (有料版) の自動ダウンロードと自動インストールが%sで開始します。手動で行う場合は今すぐにキャンセルボタンをクリックしてください。匿名のフィードバック保留中のサイトすべてに反映させる。ネットワーク上にあるすべてのサイトに対して反映させる。アフィリエイトに応募するほんとうに全ての Freemius データを削除しますか?本当に続行していいですか?30日間の返金期間があるため、コミッションのお支払いは30日以降になります。自動インストールはオプトインしたユーザのみで動作します。%s に自動更新自動インストールレーティングの平均すごい!アフィリエイトになる請求書ブロッキングブログ ID本文商号ライセンスキーは見つかりませんか?キャンセルインストールをキャンセルするサブスクリプションをキャンセルするトライアルをキャンセルキャンセルトライアルをキャンセルするとすぐにすべてのプレミアム機能へのアクセスができなくなります。本当に実行しますか?ライセンスを変更オーナーを変更プラン変更チェックアウト市API キャッシュをクリアアップデートのトランジエントをクリアーにする匿名でプラグインを使用するにはこちらをクリッククリックして%sの評価をしているレビューを観るクリックしてフルサイズのスクリーンショットを見る %dプロダクトコード互換性のある最新バージョン連絡サポートに連絡連絡コントリビューター%s を有効化できません。国Cron タイプ日付無効化ライセンスを無効化ライセンスを無効化するとすべてのプレミアム機能が使えなくなりますが、他のサイトでライセンスを有効にすることができるようになります。本当に実行しますか?無効化デバッグログサイト管理者に委任する全てのアカウントを削除詳細ライセンスキーをお持ちではありませんか?このプラグインに寄付するダウンロード%s バージョンをダウンロード最新の %s をダウンロード最新版をダウンロードダウンロード済みアフィリエイト規約違反により、アフィリエイトアカウントを一時的に凍結させていただきました。ご質問等がありましたら、サポートにお問い合わせください。アップデートの処理中に%dサイトがライセンスの有効化が保留中であることを検知しました。アップデートの処理中に、ネットワーク内の%dサイトが対応待ちになっていることを検知しました。Emailメールアドレス終了%sのプロモーションを行う予定のあなたのサイトや他のサイトのドメイン名を入力してください。アップグレードに使用したメールアドレスを下に入力してください。そうすれば、ライセンスキーをお送りします。エラーサーバーからエラーを受信しました。期限切れ%s で期間終了追加のドメイン名プロダクトフォームのマーケティングを行う追加ドメイン名。ファイルフィルターWordPress.orgのガイドラインに準拠するため、トライアルを開始する前に、ユーザーと重要でないサイト情報のオプトイン、更新の確認やトライアルの状態確認のために%sが%sに対して定期的にデータを送信する許可を得るように設定してください。無料フリートライアルフリーバージョンFreemius APIFreemius デバッグFreemius SDK がプラグインのメインファイルを見つけることができませんでした。現在のエラーを添えて sdk@freemius.com に連絡してください。Freemius ステータスフルネーム機能サブスクリプションの自動更新でコミッションを得ましょう。ライセンスキーはお持ちですか?こんにちは。%sにアフィリエイトプログラムがあるのはご存知でしたか? %sがお好きなら、私たちのアンバサダーになって報酬を得ましょう!%s はどうですか? 私たちの全ての %s のプレミアム機能をお試しください。アップロードと有効化の方法どのように我々をプロモートしますか?もう払うことができませんどうしたら動作するか分かりませんでした。自分の情報を共有したくありませんより良い %sを見つけましたアカウントをアップグレードしましたが、ライセンスを同期しようとするとプランが %s のままです。%sはもう不要です短期間だけ %sが 必要です。ID決定をサイトの管理者に委任するにはクリックしてください。お時間があれば、なぜ%sするのか理由を教えてください。%sの所有権を%sへ譲りたい場合は、所有権の変更ボタンをクリックしてください。これらのサイトで%sを使う場合は、ライセンスキーを入力し、アクティベーションボタンをクリックしてください。%s 内フリーバージョンを今すぐインストールフリーバージョンの更新を今すぐインストール今すぐインストール今すぐ更新をインストールインストール中プラグイン: %sモジュール ID が不正です請求書有効ライセンスの有効化ができませんでした。ライセンスの無効化ができませんでした。すでにトライアルモードではないようなので、キャンセルする必要はありません :)まだ %s プランのようです。もしアップグレードやプランの変更をしたのなら、こちらで何らかの問題が発生しているようです。申し訳ありません。サイトは有効なライセンスを持っていないようです。認証パラメータの1つが間違っているようです。 公開鍵、秘密鍵、ユーザーIDを更新して、もう一度お試しください。探していたものではありません%s のアドオンに関する情報は、外部サーバーから取得されます。キー将来のユーザーのために修正できるよう、何が動作しなかったのかどうか共有してください…改善できるよう、どうか理由を教えてください。最終最終更新最新のライセンス最新のフリーバージョンがインストールされました最新版がイストールされました詳細はこちら長さライセンスライセンスキーライセンスキーライセンスキーが空です。ライフタイム%sは気に入りましたか? アンバサダーになって報酬を得ましょう ;-)DB オプションを読み込むlocalhostグロガーメッセージメソッドモバイルアプリケーションモジュールモジュールのパスモジュールタイプ%s に関する詳細情報名前ネットワークブログネットワークユーザ新規新しいバージョンがあります新しいフリーバージョン (%s) がインストールされました新しいバージョン (%s) がインストールされましたニュースレター次いいえID がありません%s の拘束はありません。いつでもキャンセルできます。%s 日以内であればいつでもキャンセルできます。クレジットカードは必要ありません。有効期限なし期限のない%sのプランにはトライアル期間はありません。O.K一度ライセンスの期限が切れると、フリーバージョンの利用は可能ですが、%sの機能を使うことができなくなります。オプトインオプトアウトその他所有者の Emailオーナー ID所有者名PCI コンプライアント有料アドオンは Freemius にデプロイされている必要があります。PayPal アカウントのメールアドレス支払いお支払いは USD かつ PayPal 経由で毎月行われます。プラン%s プランは存在しないため、試用を開始できません。%s プランにはトライアル期間はありません。プラン IDこちらで私たちに連絡をとってください。以下のメッセージとともに私たちに連絡をください。%s をダウンロードしてください。購入後すぐにメールで受け取ったライセンスキーを入力してください:関係のあるウェブサイトやソーシャルメディアの統計を提供してください。例: サイトの月間訪問者数、Emailの購読者数、フォロワー数等 (機密情報として取り扱います)アップグレードを完了するには以下の手順を完了させてください。セキュリティや機能のアップデート、学習用用コンテンツ、およびオファーについてお問い合わせを希望される場合は、お知らせください。どのように%sをプロモートするつもりなのか、詳細をお知らせください (できるだけ具体的にお願いします)フルネームを入力してください。プラグインプラグインのホームページプラグイン IDプラグインのインストール変更履歴説明FAQ機能 & 料金インストールその他の記述レビュープラグインはプレミアムコードバージョンのない「サービスウェア」です。プラグインプラグインとテーマを同期プレミアムプレミアムバージョンの %sは有効化に成功しました。プレミアムアドオンバージョンはすでにインストール済みです。プレミアムバージョンプレミアムバージョンはすでに有効になっています。料金表プライバシーポリシープロセス IDプロダクトプログラム概要プロモーション方法県・州・省公開鍵ライセンスを購入クォータ有効化メールを再送信新規カスタマーに私たちの%sを紹介して、売り上げごとに%sのコミッションを得ましょうライセンスを更新リクエスト数必要な WordPress のバージョン結果SDKSDK のパス%s を保存スケジュール Cronスクリーンショット住所で検索する秘密鍵外部ドメインで実行中のセキュアな HTTPS %sページトライアルのキャンセルに一時的な問題がありました。数分後に再度お試しください。最新版を取得できました。国を選択ライセンスキーを送信DB オプションを設定するネットワークアップグレードをシミュレートするシングルサイトライセンスサイト IDサイトのオプトインに成功しました。サイト数スキップと%sスラッグソーシャルメディア(Facebook、Twitter、その他)ご迷惑をおかけしてすいません。もし機会をいただけたらお手伝いをします。メールアドレスのアップデートを完了できませんでした。他のユーザーがすでに同じメールアドレスで登録しているようです。開始トライアルを開始無料の %s を開始州送信と%sサブスクリプションサポートサポートフォーラムサーバーからのデータを同期税金 / VAT ID利用規約アフィリエイトアカウントに応募いただきありがとうございます。残念ながら現時点では申請を受理することができませんでした。30日後に改めてお申込みください。アフィリエイトプログラムに応募いただきありがとうございます。14日以内にお申し込み詳細をレビューし、改めてご連絡いたします。%sとアドオンのご利用ありがとうございます!%sのご利用ありがとうございます!プロダクトのご利用ありがとうございます!ありがとうございます!ありがとう $s さん!所有権の変更を確認していただきありがとうございます。 %s に承認メールが送信されました。%s の影響でサイトを崩れました%s が動作しませんでした%sが期待通りに動きませんでした %s は素晴らしいのですが、サポートされていないある機能が必要です%s が動作していません%s の動作が突然停止しましたインストールプロセスが開始され、数分で完了します。完了までしばらくお待ちください。ページのリフレッシュなどは行わないでください。リモートプラグインパッケージには、目的のスラッグを含むフォルダが含まれていないため、リねームが機能しませんでした。%s のアップグレードが完了しました。テーマテーマ変更テーマ%sの入手可能な新しいバージョンがありますこのプラグインはインストールされた WordPress のバージョンに互換性がありません。このプラグインはインストールされた WordPress のバージョンでは検証されていません。タイムスタンプタイトルトータル町トライアルタイプファイルシステムに接続できません。視覚情報を確認してください。無制限ライセンス無制限のアップデート無制限のコミッション。%sサイトまで更新ライセンスを更新更新、発表、マーケティング、スパムなしアップグレードダウンロードしたバージョンをアップロードして有効化やったーユーザー IDユーザー値%s に確認メールを送信しました。もし5分以内にそれが届かない場合、迷惑メールボックスを確認してください。認証済み認証メールバージョン %s をリリースしました。詳細を表示有料の機能を表示する警告メールアドレスに関連付けられた有効なライセンスが見つかりません。メールアドレスが正しいか確認してください。システムではメールアドレスを見つけることができませんでした。メールアドレスが正しいか確認してください。プラグインを微調整します、 %s, %sFreeminus ネットワークレベルのインテグレーションをご紹介できることに興奮しています。ウェブサイト、Email またはソーシャルメディアの統計 (オプション)何を期待していましたか?何の機能ですか?自分の %s はなんですか? 支払ってもよいと思う価格はいくらですか?探していたのは何ですか?%sの名前は何ですか?%sのプロモーションを行うサイトはどこですか?WordPress.org のプラグインページはいはい以前すでに試用版を利用しました。%2$s プランの%1$s日間のフリートライアルを開始するまであとワンクリックです。すべて完璧です!すでに%sをトライアルモードで利用中です。もうあとわずかです - %sプレミアムバージョンにアクセスできる有効なライセンス持っていません。%s ライセンスを持っています。%s のアップデートが成功しました。見逃していたかもしれませんが、どんな情報も共有する必要はなく、オプトインを $s することができます。 %sの改善に役立つ使用状況のトラッキングにすでにオプトインしています。プロダクトの改善に役立つ使用状況のトラッキングにすでにオプトインしています。%s のアドオンのプランのアップグレードが完了しました。%s のフリートライアルはキャンセルされました。アカウントが %s プランで有効化できました。%sのアフィリエイト申請は受理されました! 次のリンクからアフィリエイトエリアにログインしてください:%sアフィリエイトアカウントは一時的に停止されました。あなたのメールアドレスの承認が完了しました。すごい!フリートライアル期間が終了しました。%1$s %3$sに邪魔されずに利用を継続するには,今すぐ %2$s のアップグレードを行ってください。フリートライアル期間が終了しました。無料で使える機能は引き続き利用可能です。ライセンスはキャンセルされました。もしそれが間違いだと思うならサポートに連絡してください。ライセンスの有効期限が切れました。 %1$s %3$sに邪魔されずに利用を継続するには,今すぐ%2$sアップグレードを行ってください。ライセンスは有効期限がきれました。%s の機能を引き続き利用することができます。ただし、アップデートやサポートをうけるにはライセンスをアップデートする必要があります。ライセンスの有効期限が切れました。無料バージョンの%s は引き続き利用できます。ライセンスの有効化が成功しました。ライセンスの無効化が完了しました。%s プランに戻りました。名前のアップデートが成功しました。プランの %s への変更が成功しました。プランのアップグレードが成功しました。トライアル版の利用を開始しました。ZIP / 郵便番号そうだ有効%s は、%s が無いと実行することができません。%s は、プラグインが無いと実行することができません。警告許可あと %s有効化中年API却下デバッグおめでとうブロック接続最新版をダウンロード最新のフリーバージョンをダウンロード月期限切れパスメール送信中月年次毎年一度プラン秘密鍵がありませんSDK バージョンライセンス同期ライセンスを同期作者オフオン%sを基にフリートライアルを開始却下却下無効化中代表セキュリティと機能のアップデート、学習用コンテンツやオファーを%s送らないでください%s。%s プラン%s への請求ベストヘイおっとおおい %s さん、ヤッホーサイト数ms未認証料金料金表バージョン秒セキュリティと機能のアップデート、学習用コンテンツやオファーを送ってください。スキップふむトライアルを開始変更中最新の %s バージョンはこちらです。トライアル削除ダウングレード編集非表示オプトインオプトアウト購入表示スキップ更新アップグレード%s 前PK!T ̀.libraries/freemius/languages/freemius-he_IL.monu[|S%1 W co6v_b#   ")1:BHK & -, Z o y     5   ! !'#!K! d! q!{!!!2"E""a""2"!"a"X#j######## ####$ $$ 1$Y;$$$ $$$$($1%%:%:`%%%%% % %%% % & &&,& & &&&' '&'<'E'Y'x' ''7( =(K(YO(a( ))1) 9) G);U))))* * * **j*@+ O+Y+3b++~+U),,,,),-,'-S;--'--7-g .r.x.. .... .1..'/OV//A&0yh0201a511B1,1 2 2 2*2H2 a2l2s2 {2 222422 233 33 3&3 -3 93E3_3 d3q3u33 3333%3+34 +4 94/F4v4mz4444 4 5 5 5)+5U5r54{5555(566-36a6Uu6617[7"8A8H8 X8b8(q8*8"818+9*F9&q9999.999:%: 4: ?:J:S:c:u: ~::::W: ;;(;C;J;N;W;_; o;{; ;5;l;&;<b<q< <<<<< <<&<L=fN== === = == = > #>0>A>>n?)? ? ?\?+@@@S@Cr@@@@-rAA AA'AMAG0B xBBBBBBEBBB C C/C6C*ECpC*xC^CD DDdD{D DD DDDiDWGG&HZ5H.H.H9HZ(I3I<IPIUEJJK6K(KGK#K)L$AL)fLLL;L6L>2MqMwMMM$MMN#N?N&]N*NNNN3N1OFO\OtOO*O1OPP#2PVPrPP PPPP PPNQQQpQQQQQ1Q R!R 1R >R IRVR nRCzRRQRS %S/SJS ]SiS xS S S S S S S S S S S#X(YGY WY cY6oYY_[Z5ZZ [ [%[ 5[ C[O[ _[ i[Hw[[ [[ [ [ [\$\C\O[\8\\ \ ]"]7]J]Q]5Y] ] ] ]]']$]^$^6^P^Y^._BH_+__2_.`a6`%````` aa#a5a :a6Fa}aaaaaaDb\brbbbb(b1b%c 6cCcHcYc acoc c#c c c c cccd d$d d e1(eZe neye.e'e ee fffYfeg kg#vg gg g;g h h hi(i>i Xi$eijii jj3$j!Xj~zjUj#Oksk7k@kJlPlnll' m2m7;mgsm m+m6nJn ^n$nnn:n.nO-o}o:oy8p6ppaqhqmlq=qrr7r1Or&r rr r rrr s4sPs_sssws ~ss s s sssssst#t>tMtRtWt%gt@t#tt u/u AumLuuuuuuuv)$vNvkv4zv v`v>wZw&bwBwwmwUxFy[ey#yyy z z z 6zAzYz xzzz zzz.z{* { K{X{ t{ { {{{{{{{6{W5||||||| ||}}.}5@}v}4}+~ =~ ^~l~~~ ~ ~~&~kft &) =I R] sG<ށA] n{.CL%Ƀ-x  DŽ%ԄGBB  E  "CZmvS8$)=LdR ‡އ'6i>W"6#Zm7͉މ&5QV(_M֊/X:$Ӌ5^I8ތZ3r<{__KZ'>Ώ /-']. ϐ&ڐACW ` ny   ۑ  # , :E^ o| ђ y  ēғٓ   *3u7 ܔ$ (1:A IT[bk t%s - plugin name. As complete "PluginX" activation nowComplete "%s" Activation Now%s Add-on was successfully purchased.%s Installs%s Licenses%s ago%s commission when a customer purchases a new license.%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license.%s is a premium only add-on. You have to purchase a license first before activating the plugin.%s is the new owner of the account.%s minimum payout amount.%s or higher%s rating%s ratings%s sec%s star%s stars%s time%s times%s tracking cookie after the first visit to maximize earnings potential.APIAccountAccount DetailsActionsActivateActivate %sActivate %s PlanActivate Free VersionActivate LicenseActivate license on all pending sites.Activate license on all sites in the network.Activate this add-onActivatedAdd Ons for %sAdd Ons of module %sAdd another domainAdd-OnAdd-OnsAdd-on must be deployed to WordPress.org or Freemius.AddressAddress Line %dAffiliateAffiliationAfter your free %s, pay as little as %sAgree & Activate LicenseAll RequestsAll TypesAllow & ContinueAmountAn automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now.Anonymous feedbackApply on all pending sites.Apply on all sites in the network.Apply to become an affiliateAre you sure you want to delete all Freemius data?Are you sure you want to proceed?As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days.Auto renews in %sAutomatic InstallationAverage RatingAwesomeBecome an affiliateBillingBlockingBlog IDBodyBusiness nameCan't find your license key?CancelCancel InstallationCancel SubscriptionCancel TrialCancelledCancelling the trial will immediately block access to all premium features. Are you sure?Change LicenseChange OwnershipChange PlanCheckoutCityClear API CacheClick here to use the plugin anonymouslyClick to see reviews that provided a rating of %sClick to view full-size screenshot %dClone resolution admin notice products list labelProductsCodeCompatible up toContactContact SupportContact UsContributorsCouldn't activate %s.CountryCron TypeDateDeactivateDeactivate LicenseDeactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?DeactivationDebug LogDelegate to Site AdminsDelete All AccountsDetailsDon't have a license key?Donate to this pluginDownloadDownload %s VersionDownload the latest %s versionDownload the latest versionDownloadedDue to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.EmailEmail addressEndEnter the domain of your website or other websites from where you plan to promote the %s.Enter the email address you've used for the upgrade below and we will resend you the license key.ErrorError received from the server:ExpiredExpires in %sExtra DomainsExtra domains where you will be marketing the product from.FileFilterFor compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.FreeFree TrialFree versionFreemius APIFreemius DebugFreemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error.Freemius StateFull nameFunctionGet commission for automated subscription renewals.Have a license key?Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!How do you like %s so far? Test all our %s premium features with a %d-day free trial.How to upload and activate?How will you promote us?I can't pay for it anymoreI couldn't understand how to make it workI don't like to share my information with youI found a better %sI have upgraded my account but when I try to Sync the License, the plan remains %s.I no longer need the %sI only needed the %s for a short periodIDIf you have a moment, please let us know why you are %sIf you would like to give up the ownership of the %s's account to %s click the Change Ownership button.In %sInstall Free Version NowInstall Free Version Update NowInstall NowInstall Update NowInvalid module ID.InvoiceIs ActiveIt looks like the license could not be activated.It looks like the license deactivation failed.It looks like you are not in trial mode anymore so there's nothing to cancel :)It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry.It looks like your site currently doesn't have an active license.It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.It's a temporary %s - I'm troubleshooting an issueIt's not what I was looking forJust letting you know that the add-ons information of %s is being pulled from an external server.KeyKindly share what didn't work so we can fix it for future users...Kindly tell us the reason so we can improve.LastLast UpdatedLast licenseLatest Free Version InstalledLatest Version InstalledLearn moreLengthLicenseLicense KeyLicense keyLicense key is empty.LifetimeLike the %s? Become our ambassador and earn cash ;-)Load DB OptionLocalhostLogLoggerMessageMethodMobile appsModuleModule PathModule TypeMore information about %sNameNetwork UserNewNew Version AvailableNewer Version (%s) InstalledNewsletterNextNoNo IDNo commitment for %s - cancel anytimeNo commitment for %s days - cancel anytime!No credit card requiredNo expirationNon-expiringNone of the %s's plans supports a trial period.O.KOnce your license expires you can still use the Free version but you will NOT have access to the %s features.Opt InOpt OutOtherOwner EmailOwner IDOwner NamePCI compliantPaid add-on must be deployed to Freemius.PayPal account email addressPaymentsPayouts are in USD and processed monthly via PayPal.PlanPlan %s do not exist, therefore, can't start a trial.Plan %s does not support a trial period.Plan IDPlease contact us herePlease contact us with the following message:Please download %s.Please enter the license key that you received in the email right after the purchase:Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential).Please follow these steps to complete the upgradePlease provide details on how you intend to promote %s (please be as specific as possible).Please provide your full name.PluginPlugin HomepagePlugin IDPlugin InstallPlugin installer section titleChangelogPlugin installer section titleDescriptionPlugin installer section titleFAQPlugin installer section titleFeatures & PricingPlugin installer section titleInstallationPlugin installer section titleOther NotesPlugin installer section titleReviewsPluginsPlugins & Themes SyncPremiumPremium %s version was successfully activated.Premium versionPremium version already active.PricingPrivacy PolicyProcess IDProcessingProductsProgram SummaryPromotion methodsProvincePublic KeyPurchase LicenseQuotaRe-send activation emailRefer new customers to our %s and earn %s commission on each successful sale you refer!Renew licenseRequestsRequires WordPress VersionResultSDKSDK PathSave %sScheduled CronsScreenshotsSearch by addressSecret KeySecure HTTPS %s page, running from an external domainSeems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.Seems like you got the latest release.Select CountrySend License KeySet DB OptionSimulate Network UpgradeSingle Site LicenseSite IDSitesSkip & %sSlugSocial media (Facebook, Twitter, etc.)Sorry for the inconvenience and we are here to help if you give us a chance.Sorry, we could not complete the email update. Another user with the same email is already registered.StartStart TrialStart my free %sStateSubmit & %sSubscriptionSupportSupport ForumSync Data From ServerTax / VAT IDTerms of ServiceThank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days.Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information.Thank you so much for using %s!Thank you so much for using our products!Thank you!Thanks %s!Thanks for confirming the ownership change. An email was just sent to %s for final approval.The %s broke my siteThe %s didn't workThe %s didn't work as expectedThe %s is great, but I need specific feature that you don't supportThe %s is not workingThe %s suddenly stopped workingThe installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page.The upgrade of %s was successfully completed.ThemeTheme SwitchThemesThere is a new version of %s available.This plugin has not been marked as compatible with your version of WordPress.This plugin has not been tested with your current version of WordPress.TimestampTitleTotalTownTrialTypeUnable to connect to the filesystem. Please confirm your credentials.Unlimited LicensesUnlimited UpdatesUnlimited commissions.Up to %s SitesUpdateUpdate LicenseUpdates, announcements, marketing, no spamUpgradeUpload and activate the downloaded versionUsed to express elation, enthusiasm, or triumph (especially in electronic communication).W00tUser IDUsersValueVerification mail was just sent to %s. If you can't find it after 5 min, please check your spam box.VerifiedVerify EmailVersion %s was released.View detailsView paid featuresWarningWe can't see any active licenses associated with that email address, are you sure it's the right address?We couldn't find your email address in the system, are you sure it's the right address?We made a few tweaks to the %s, %sWebsite, email, and social media statistics (optional)What did you expect?What feature?What is your %s?What price would you feel comfortable paying?What you've been looking for?What's the %s's name?Where are you going to promote the %s?Would you like to proceed with the update?YesYes - %sYou already utilized a trial before.You are 1-click away from starting your %1$s-day free trial of the %2$s plan.You are all good!You are already running the %s in a trial mode.You are just one step away - %sYou do not have a valid license to access the premium version.You have a %s license.You have successfully updated your %s.You might have missed it, but you don't have to share any data and can just %s the opt-in.Your %s Add-on plan was successfully upgraded.Your %s free trial was successfully cancelled.Your account was successfully activated with the %s plan.Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s.Your affiliation account was temporarily suspended.Your email has been successfully verified - you are AWESOME!Your free trial has expired. You can still continue using all our free features.Your license has been cancelled. If you think it's a mistake, please contact support.Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support.Your license has expired. You can still continue using the free %s forever.Your license was successfully activated.Your license was successfully deactivated, you are back to the %s plan.Your name was successfully updated.Your plan was successfully changed to %s.Your plan was successfully upgraded.Your trial has been successfully started.ZIP / Postal Codea positive responseRight onaddonX cannot run without pluginY%s cannot run without %s.addonX cannot run...%s cannot run without the plugin.advance notice of something that will need attention.Heads upallowas 5 licenses left%s leftas activating pluginActivatingas annual periodyearas application program interfaceAPIas close a windowDismissas code debuggingDebuggingas congratulationsCongratsas connection blockedBlockedas connection was successfulConnectedas download latest versionDownload Latestas every monthMonthlyas expiration dateExpirationas file/folder pathPathas in the process of sending an emailSending emailas monthly periodmoas once a yearAnnualas once a yearAnnuallyas once a yearOnceas product pricing planPlanas secret encryption key missingNo Secretas software development kit versionsSDK Versionsas software licenseLicenseas synchronizeSyncas synchronize licenseSync Licenseas the plugin authorAuthoras turned offOffas turned onOnbased on %scall to actionStart free trialclose a windowDismissclose windowDismissdeactivatingdelegatedo %sNOT%s send me security & feature updates, educational content and offers.e.g. Professional Plan%s Plane.g. billed monthlyBilled %se.g. the best productBestexclamationHeyexclamationOopsgreetingHey %s,interjection expressing joy or exuberanceYee-hawlike websitesSitesmillisecondsmsnot verifiednounPricenounPricingproduct versionVersionsecondssecsend me security & feature updates, educational content and offers.skipsomething somebody says when they are thinking about what you have just said.Hmmstart the trialswitchingthe latest %s version heretrial periodTrialverbDeleteverbDowngradeverbEditverbHideverbOpt InverbOpt OutverbPurchaseverbShowverbSkipverbUpdateverbUpgradex-ago%s agoProject-Id-Version: WordPress SDK Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues PO-Revision-Date: 2015-10-01 21:38+0000 Last-Translator: Vova Feldman , 2017-2018,2022 Language-Team: Hebrew (Israel) (http://app.transifex.com/freemius/wordpress-sdk/language/he_IL/) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Language: he_IL Plural-Forms: nplurals=3; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: 2; X-Poedit-Basepath: .. X-Poedit-KeywordsList: get_text_inline;get_text_x_inline:1,2c;$this->get_text_inline;$this->get_text_x_inline:1,2c;$this->_fs->get_text_inline;$this->_fs->get_text_x_inline:1,2c;$this->fs->get_text_inline;$this->fs->get_text_x_inline:1,2c;$fs->get_text_inline;$fs->get_text_x_inline:1,2c;$this->_parent->get_text_inline;$this->_parent->get_text_x_inline:1,2c;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c X-Poedit-SearchPath-0: . X-Poedit-SearchPathExcluded-0: *.js X-Poedit-SourceCharset: UTF-8 השלם הפעלת "%s" עכשיוההרחבה %s נרכשה בהצלחה.%s התקנות%s Licensesלפני %s%s commission when a customer purchases a new license.%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license.%s is a premium only add-on. You have to purchase a license first before activating the plugin.%s הינו הבעלים החד של חשבון זה.%s minimum payout amount.%s ומעלהדרוג %s%s דרוגים%s שניותכוכב %s%s כוכביםפעם %s%s פעמים%s tracking cookie after the first visit to maximize earnings potential.APIחשבוןפרטי חשבוןפעולותהפעלהActivate %sהפעל חבילה %sהפעלת גירסה חינאמיתהפעלת רישיוןהפעלת רישיון על כל האתרים התלויים והעומדים.הפעלת רישיון על כל האתרים ברשת.הפעל את ההרחבהActivatedהרחבות עבור %sAdd Ons of module %sAdd another domainAdd-OnAdd-OnsAdd-on must be deployed to WordPress.org or Freemius.כתובתכתובת %sAffiliateאפיליאציהAfter your free %s, pay as little as %sהסכמה והפעלת רישיוןכל הבקשותכל הסוגיםאפשר\י והמשכ\יסכוםהורדה והתקנה אוטומטית של %s (גרסה בתשלום) מ-%2$s תתחיל בעוד %3$s. אם ברצונך לבצע את ההתקנה ידנית - לחץ על כפתור הביטול עכשיו.פידבק אנונימייישום על כל האתרים התלויים והעומדים.יישום על כל האתרים ברשת.Apply to become an affiliateAre you sure you want to delete all Freemius data?האם את/ה בטוח רוצה להמשיך?As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days.עדכן אוטומטית בעוד %sהתקנה אוטומטיתדירוג ממוצעאדירBecome an affiliateבילינגBlockingמזהה בלוגBodyשם עסקהאם אינך מוצא את מפתח הרישיון?בטלבטל התקנהבטל מנויביטבוטלביטול הניסיון יחסום מייד את הפיטצ'רים שהינם בתשלום. האם ברצונך בכל זאת להמשיך?שינוי רישיוןעדכון בעלותשינוי חבילהCheckoutעירניקוי מטמון ה-APIClick here to use the plugin anonymouslyClick to see reviews that provided a rating of %sClick to view full-size screenshot %dמוצריםCodeCompatible up toContactצור קשריצירת קשרתורמיםלא ניתן להפעיל את %s.מדינהCron Typeתאריךכיבוישיחרור רישיוןביטול הרישיון יחסום את כל הפיטצ'רים שבתשלום אך יאפשר להפעיל את הרישיון על אתר אחר. האם תרצו להמשיך בכל זאת?דיאקטיבציהDebug Logהאצלה למנהלי האתריםמחיקת כל החשבונותפרטיםהאם אין ברשותך מפתח רישיון?תרום לתוסףהורדההורד גרסת %sהורד\י את גרסת ה-%s העדכניתהורד את הגרסה האחרונהDownloadedDue to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.דוא"לכתובת דוא"לEndEnter the domain of your website or other websites from where you plan to promote the %s.הזן את כתובת הדואל שאיתה שידרגת כדי לקבל את הרישיון שוב.שגיאההוחזרה שגיאה מהשרת:פג תוקףפג תוקף בעוד %sExtra DomainsExtra domains where you will be marketing the product from.קובץפילטרFor compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.חינםניסיון חינםגירסה חינאמיתFreemius APIניפוי תקלות פרימיוסFreemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error.מצב פרימיוסFull nameפונקציהGet commission for automated subscription renewals.האם ברשותך רישיון?Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!How do you like %s so far? Test all our %s premium features with a %d-day free trial.איך להעלות ולהפעיל?How will you promote us?אני לא יכול/ה להמשיך לשלם על זהלא הצלחתי להבין איך לגרום לזה לעבודאני לא אוהב את הרעיון של שיתוף מידע איתכםמצאתי %s יותר טובשידרגתי את החשבון שלי אבל כשאני מנסה לבצע סנכרון לרישיון החבילה נשארת %s.I no longer need the %sI only needed the %s for a short periodמזההIf you have a moment, please let us know why you are %sIf you would like to give up the ownership of the %s's account to %s click the Change Ownership button.בעוד %sהתקן גרסה חינאמית עכשיוהתקן עדכון גרסה חינאמית עכשיוהתקן עכשיוהתקן עדכון במיידימזהה המודול לא תקני.חשבוניתהאם פעילנראה שלא ניתן להפעיל את הרישיון.נראה שניתוק הרישיון נכשל.It looks like you are not in trial mode anymore so there's nothing to cancel :)It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry.נראה לאתר עדיין אין רישיון פעיל.It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.זה %s זמני - אני מנסה לפתור בעיהחיפשתי משהו אחרJust letting you know that the add-ons information of %s is being pulled from an external server.Keyאנא שתפ\י מה לא עבד כדי שנוכל לתקן זאת עבור משתמשים עתידיים...אנא שתף את הסיבה כדי שנוכל להשתפר.Lastעודכן לאחרונהרישיון אחרוןגרסה חינאמית עדכנית הותקנההגרסה האחרונה הותקנהLearn moreLengthרישיוןLicense Keyמפתח רישיוןמפתח הרישיון ריק.לכל החייםLike the %s? Become our ambassador and earn cash ;-)Load DB Optionשרת לוקאליLogLoggerהודעהMethodMobile appsמודולModule Pathסוג מודולמידע נוסף אודות %sשםמשתמש רשתחדשיש גרסה חדשהגרסה חדשה (%s) הותקנהניוסלטרNextלאאין מזההNo commitment for %s - cancel anytimeללא התחייבות ל-%s ימין - בטלו בכל רגע!לא נדרש כרטיס אשראיללא תפוגהNon-expiringNone of the %s's plans supports a trial period.אוקייOnce your license expires you can still use the Free version but you will NOT have access to the %s features.Opt InOpt Outאחרמייל הבעליםמזהה הבעליםשם הבעליםעומד בתקן PCIPaid add-on must be deployed to Freemius.PayPal account email addressתשלומיםPayouts are in USD and processed monthly via PayPal.חבילההחבילה %s אינה קיימת, לכן, לא ניתן להתחיל תקופת ניסיון.תוכנית %s אינה תומכת בתקופת ניסיון.Plan IDאנא צור איתנו קשר כאןאנא צור איתנו קשר יחד עם ההודעה הבאה:נא להוריד את %s.אנא הזן את הרישיון שקיבלת לתיבת הדואל שלך לאחר השלמת הרכישה.Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential).נא לבצע את הצעדים הבאים להשלמת השידרוגPlease provide details on how you intend to promote %s (please be as specific as possible).נא למלא את שמך המלא.תוסףעמוד התוסףPlugin IDהתקנת תוסףלוג שינוייםתיאורשאלות נפוצותפיטצ'רים ומחיריםהתקנההיערות נוספותביקורותתוספיםPlugins & Themes SyncPremiumPremium %s version was successfully activated.גירסת פרימיוםהגרסה בתשלום כבר פעילה.מחירוןמדיניות פרטיותProcess IDProcessingמוצריםProgram SummaryPromotion methodsפרובינציהמפתח פומביקניית רישיוןQuotaשליחה חוזרת של מייל האקטיבציהRefer new customers to our %s and earn %s commission on each successful sale you refer!חידוש רישיוןRequestsRequires WordPress VersionResultSDKמיקום SDKשמירת %sScheduled Cronsצילומי מסךחפש לפי כתובתמפתח סודיSecure HTTPS %s page, running from an external domainנראה שיש תקלה זמנית המונעת את ביטול הניסיון. אנא נסו שוב בעוד כמה דקות.נראה שיש לך את הגרסה האחרונה.בחר מדינהשליחת מפתח רישיוןSet DB Optionסמלוץ עדכון לרשתרשיון לאתר אחדמזהה אתראתריםדלג ו%sמזהה כתובתSocial media (Facebook, Twitter, etc.)מצטערים על חוסר הנעימות, אנחנו כאן כדי לעזור אם תאפשר\י זאת.Sorry, we could not complete the email update. Another user with the same email is already registered.Startהתחל תקופת ניסיוןהתחל את %s הניסיון שלימחוז/מדינהSubmit & %sמנויתמיכהפורום תמיכהסנכרון מידע מהשרתח.פ.תנאי השירותThank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days.Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information.אנו מודים לך על היותך כמשתמש של %s!אנו מודים לך על השימוש במוצרים שלנו!תודה רבה!תודה %s!תודה על אישור ביצוע החלפת הבעלות. הרגע נשלח מייל ל-%s כדי לקבל אישור סופי.ה%s הרס לי את האתרה%s לא עבדה%s לא עבד כמצופהThe %s is great, but I need specific feature that you don't supportה%s לא עובדה%s הפסיק פתאום לעבודתהליך ההתקנה התחיל ויכול לקחת מספר דקות לסיום. אנא המתינו בסבלנות עד לסיום מבלי לרענן את הדפדפן.The upgrade of %s was successfully completed.תבניתהחלפת תֵמָהתבניותיש גרסה חדשה עבור ה%s.התוסף לא סומן כתואם לגרסת הוורדפרס שלך.תוסף זה לא נבדק עם גרסת הוורדפרס שלך.TimestampכותרתTotalכפרניסיוןסוגUnable to connect to the filesystem. Please confirm your credentials.רשיונות ללא הגבלהעדכונים ללא הגבלהUnlimited commissions.עד %s אתריםעדכןעדכון רישיוןעדכונים, הכרזות, הודעות שיווקיות, ללא דואר זבלשדרגהעלה\י והפעיל\י את הגרסה שהורדתישמזהה משתמשמשתמשיםValueVerification mail was just sent to %s. If you can't find it after 5 min, please check your spam box.מאומתאמת כתובת דוא"לגרסה %s הושקה.פרטים נוספיםצפה בפיטצ'רים שבתשלוםWarningWe can't see any active licenses associated with that email address, are you sure it's the right address?We couldn't find your email address in the system, are you sure it's the right address?We made a few tweaks to the %s, %sWebsite, email, and social media statistics (optional)למה ציפית?איזה פיטצ'ר?מה ה%s שלך?מה המחיר שכן תרגיש\י בנוח לשלם?מה חיפשת?What's the %s's name?Where are you going to promote the %s?האם ברצונך להמשיך עם העידכון?כןYes - %sהניסיון כבר נוצל בעבר.You are 1-click away from starting your %1$s-day free trial of the %2$s plan.את\ה מסודר!You are already running the %s in a trial mode.You are just one step away - %sאין ברשותך רישיון בר תוקף לשימוש בגרסת הפרימיום.יש לך רישיון %s.עידכנת בהצלחה את ה%s.אולי פספסת את זה אבל אינך חייב\ת לשתף כל מידע איתנו, ביכולתך %s על שיתוף המידע.חבילת ההרחבה %s שודרגה בהצלחה.תקופת הניסיון החינמית של %s בוטלה בהצלחה.חשבונך הופעל בהצלחה עם חבילת %s.Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s.Your affiliation account was temporarily suspended.Your email has been successfully verified - you are AWESOME!תקופת הניסיון שלך הסתיימה. הפיטצ'רים החינאמיים עדיין ניתנים לשימוש.רשיונך בוטל. אם לדעתך זו טעות, נא ליצור קשר עם התמיכה.Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support.Your license has expired. You can still continue using the free %s forever.הרישיון הופעל בהצלחה.רישיונך נותק בהצלחה, חזרת לחבילת %sשמך עודכן בהצלחה.החבילה עודכנה בהצלחה אל %s.החבילה שודרגה בהצלחה.הניסיון שלך הופעל בהצלחה.מיקוד / תא דוארמעולה%s לא יכול לעבוד ללא %s.ההרחבה %s אינה יכולה לפעול ללא התוסף.לתשמות לבךאפשרנשארו %sמפעילשנהAPIסגירהדיבוגמזל טובחסוםמחוברהורד גרסה אחרונהחודשיתפוגהנתיבשולח דוא"לחודשיםשנתישנתיפעם אחתחבילהאין מפתח סודיגרסאות SDKרישיוןסינכרוןסינכרן רישיוןAuthorכבוידלוקמבוסס על %sהתחלת ניסיון חינםסגירהסגירהdeactivatingהאצל%sאל%2$s תשלחו לי עדכוני אבטחה, פיטצ'רים, תוכן חינוכי, ומידע על מבצעים.חבילה %sמחוייב על בסיס %sהכי טובהייאופסהיי %s,ישששאתריםmsלא מאומתמחירמחירוןגרסהsecתשלחו לי עדכוני אבטחה ופיטצ'רים, תוכן חינוכי, ומידע אודות מבצעים.דלגאממהתחל תקופת ניסיוןswitchingגרסת ה-%s האחרונה כאןניסיוןמחקשנמךערוךהסתרOpt InOpt Outרכישההצגדלגעדכןשדרגלפני %sPK!}.libraries/freemius/languages/freemius-es_ES.monu[:##A##n$h%Sz%%% % & &&6&&]&_'#r'' ' ' ''''''@'H<((O((( )+)3)C)K) T)`)q)))&)-)* *!*0*E*X*_*5g*** * *'** + +#+o4+++GJ,T,,---"-.2.!R.at.+.0/3/E/\/k/s/////// / // /E/;0X0_0s0 11%1 91 F1 P1^1o1Y111 2 22&262 O2(Z212%2:233,343 D3 O3\3r3 z33 33x3 4 4 44a5y555t5%6?6U6k6t6666 667[68f88 8 9'9+9Y?9a99:!: ): 7:;E::::; ; ; ;;j;0<5?< u<<3<2<<~=U=== >#>)>>-h>>S>>'?>?MA?7?g?p/@@@y@:ASA sAAAA%A AB BB1B.BO C[CACyDDDaD/EB3E,vEE E EEE EF FF %F 0F_"_B_6`=J`` ``-``a&aEa*_aaa$aMa b/bLbolb>bc 2c&ScZzcTcR*d.}d.dd-e9fZUf3f<fb!gPgUg_+hhK&i(riGi#i%j)-j$WjU|j)jjk+k;@k6|k>kkkl3l$Ilnllll&l*m70mhmmm3mmmn-nAn*^n1nnn#no+o=o MoYoyoo ooNo p)pGpbprppp1pppp q q *q 7q BqOqXq pq ~q9qCqrQ r_r or |rrrr rr r r r r s s s $s 0s =sJsW@xxwypy&pz1zz zzz9{?{k|#l|| |||| | ||}V }Zb}}W}1~5~>~G~N~d~m~ u~~~~~0~/8QZo  A ! *36jr€5>U`9%x'&݃>%:h`4ɄGF^w   х ܅-K>+ֆn} Ň χ݇z  ƈ̈$ 9;R; ʉԉ܉  #; AN T_hs܊ #`"Ɍ"Wz ۍ %yu7! X($ -:6I;˓ғ  zDFBL~u."ܖ$#$-Hv_,2R59l˜/#әٙ#])ؚC,Ly *U4O=Нeo `5qƟ#ן !*3GVh" B #'.6> Wem~ ˡѡ.%@ ISV6]>"Ӣ ;N|Rϣ S _$m#Τ7+0\DbM+ &20NhNN=/m4i'$ Ԫ  2 ? K\X٫3.DUqx Ҭ   5+;tgܭ ! 7A E R]fx 1xe&ޯ0Fd !̰ Ӱ߰M&2PY@G[#z ò ϲܲ#'=ݳ4!1ٴ n$A۵$/Tk0Z (LC6 zO U_{V׹ .9.J y' Ѻߺ} } Ļѻ"0MI.@xɾ0$?[#z)ȿ˿*ӿRQ'eqB^v$gU!Rw42U216n7`AmdHic{O%'uA(&'!NYp0 &< cm v  & 7: @KSX jx  ' g    "3K7^ #@Sov   %s to access version %s security & feature updates, and support. The %s's %sdownload link%s, license key, and installation instructions have been sent to %s. If you can't find the email after 5 min, please check your spam box. The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s.%s - plugin name. As complete "PluginX" activation nowComplete "%s" Activation Now%s Add-on was successfully purchased.%s Installs%s Licenses%s ago%s and its add-ons%s commission when a customer purchases a new license.%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license.%s is a premium only add-on. You have to purchase a license first before activating the plugin.%s is the new owner of the account.%s minimum payout amount.%s or higher%s rating%s ratings%s sec%s star%s stars%s time%s times%s to access version %s security & feature updates, and support.%s tracking cookie after the first visit to maximize earnings potential.%s's paid features%sClick here%s to choose the sites where you'd like to activate the license on.APIASCII arrow left icon←ASCII arrow right icon➤AccountAccount DetailsActionsActivateActivate %sActivate %s PlanActivate %s featuresActivate Free VersionActivate LicenseActivate license on all pending sites.Activate license on all sites in the network.Activate this add-onActivatedAdd Ons for %sAdd Ons of module %sAdd another domainAdd-OnAdd-OnsAdd-on must be deployed to WordPress.org or Freemius.AddressAddress Line %dAffiliateAffiliationAfter your free %s, pay as little as %sAgree & Activate LicenseAll RequestsAll TypesAllow & ContinueAlternatively, you can skip it for now and activate the license later, in your %s's network-level Account page.AmountAn automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now.An unknown error has occurred while trying to set the user's beta mode.An unknown error has occurred while trying to toggle the license's white-label mode.An unknown error has occurred.An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned.Anonymous feedbackApply on all pending sites.Apply on all sites in the network.Apply to become an affiliateAre you sure you want to delete all Freemius data?Are you sure you want to proceed?As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days.Associate with the license owner's account.Auto installation only works for opted-in users.Auto renews in %sAutomatic InstallationAverage RatingAwesomeBecome an affiliateBetaBillingBilling & InvoicesBlockingBlog IDBodyBundle PlanBusiness nameBuy a license nowBuy licenseBy changing the user, you agree to transfer the account ownership to:Can't find your license key?CancelCancel %s & ProceedCancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site.Cancel %s?Cancel InstallationCancel SubscriptionCancel TrialCancelledCancelling %sCancelling %s...Cancelling the subscriptionCancelling the trial will immediately block access to all premium features. Are you sure?Change LicenseChange OwnershipChange PlanChange UserCheckoutClear API CacheClear Updates TransientsClick hereClick here to use the plugin anonymouslyClick to see reviews that provided a rating of %sClick to view full-size screenshot %dClone resolution admin notice products list labelProductsCodeCompatible up toContactContact SupportContact UsContributorsCouldn't activate %s.CountryCron TypeDateDeactivateDeactivate LicenseDeactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site.Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?DeactivationDebug LogDebug mode was successfully enabled and will be automatically disabled in 60 min. You can also disable it earlier by clicking the "Stop Debug" link.Delegate to Site AdminsDelete All AccountsDetailsDisabling white-label modeDon't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support.Don't have a license key?Donate to this pluginDowngrading your planDownloadDownload %s VersionDownload Paid VersionDownload the latest %s versionDownload the latest versionDownloadedDue to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.During the update process we detected %d site(s) that are still pending license activation.During the update process we detected %s site(s) in the network that are still pending your attention.EmailEmail addressEnabling white-label modeEndEnter email addressEnter the domain of your website or other websites from where you plan to promote the %s.Enter the email address you've used for the upgrade below and we will resend you the license key.ErrorError received from the server:ExpiredExpires in %sExtra DomainsExtra domains where you will be marketing the product from.FileFilterFor compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.FreeFree TrialFree versionFreemius APIFreemius DebugFreemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error.Freemius StateFreemius is our licensing and software updates engineFull nameFunctionGet commission for automated subscription renewals.Get updates for bleeding edge Beta versions of %s.Have a license key?Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!How do you like %s so far? Test all our %s premium features with a %d-day free trial.How to upload and activate?How will you promote us?I Agree - Change UserI can't pay for it anymoreI couldn't understand how to make it workI don't like to share my information with youI found a better %sI have upgraded my account but when I try to Sync the License, the plan remains %s.I no longer need the %sI only needed the %s for a short periodIDIf you click it, this decision will be delegated to the sites administrators.If you have a moment, please let us know why you are %sIf you would like to give up the ownership of the %s's account to %s click the Change Ownership button.If you'd like to use the %s on those sites, please enter your license key below and click the activation button.Important Upgrade Notice:In %sIn case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?Install Free Version NowInstall Free Version Update NowInstall NowInstall Update NowInstalling plugin: %sInvalid module ID.Invalid new user ID or email address.Invalid site details collection.InvoiceIs ActiveIs this your client's site? %s if you wish to hide sensitive info like your email, license key, prices, billing address & invoices from the WP Admin.It looks like the license could not be activated.It looks like the license deactivation failed.It looks like you are not in trial mode anymore so there's nothing to cancel :)It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry.It looks like your site currently doesn't have an active license.It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.It's not what I was looking forJoin the Beta programJust letting you know that the add-ons information of %s is being pulled from an external server.KeyKindly share what didn't work so we can fix it for future users...Kindly tell us the reason so we can improve.LastLast UpdatedLast licenseLatest Free Version InstalledLatest Version InstalledLearn moreLengthLicenseLicense AgreementLicense IDLicense KeyLicense issues?License keyLicense key is empty.LifetimeLike the %s? Become our ambassador and earn cash ;-)Load DB OptionLocalhostLogLoggerMessageMethodMigrate Options to NetworkMobile appsModuleModule PathModule TypeMore information about %sNameNetwork BlogNetwork UserNewNew Version AvailableNewer Free Version (%s) InstalledNewer Version (%s) InstalledNewsletterNextNoNo IDNo commitment for %s - cancel anytimeNo commitment for %s days - cancel anytime!No credit card requiredNo expirationNon-expiringNone of the %s's plans supports a trial period.O.KOnce your license expires you can still use the Free version but you will NOT have access to the %s features.Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license.Opt InOpt OutOpt in to make "%s" better!OtherOwner EmailOwner IDOwner NamePCI compliantPaid add-on must be deployed to Freemius.PayPal account email addressPaymentsPayouts are in USD and processed monthly via PayPal.PlanPlan %s do not exist, therefore, can't start a trial.Plan %s does not support a trial period.Plan IDPlease contact us herePlease contact us with the following message:Please download %s.Please enter the license key that you received in the email right after the purchase:Please enter the license key to enable the debug mode:Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential).Please follow these steps to complete the upgradePlease let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.Please provide details on how you intend to promote %s (please be as specific as possible).Please provide your full name.PluginPlugin HomepagePlugin IDPlugin InstallPlugin installer section titleChangelogPlugin installer section titleDescriptionPlugin installer section titleFAQPlugin installer section titleFeatures & PricingPlugin installer section titleInstallationPlugin installer section titleOther NotesPlugin installer section titleReviewsPlugin is a "Serviceware" which means it does not have a premium code version.PluginsPlugins & Themes SyncPremiumPremium %s version was successfully activated.Premium add-on version already installed.Premium versionPremium version already active.PricingPrivacy PolicyProceedProcess IDProcessingProductsProgram SummaryPromotion methodsProvincePublic KeyPurchase LicensePurchase MoreQuick FeedbackQuotaRe-send activation emailRefer new customers to our %s and earn %s commission on each successful sale you refer!Renew licenseRenew your license nowRequestsRequires WordPress VersionResultSDKSDK PathSave %sSavedScheduled CronsScreenshotsSearch by addressSecret KeySecure HTTPS %s page, running from an external domainSeems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes.Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.Seems like you got the latest release.Select CountrySend License KeySet DB OptionSimulate Network UpgradeSimulate Trial PromotionSingle Site LicenseSite IDSite successfully opted in.SitesSkip & %sSlugSo you can reuse the license when the %s is no longer active.Social media (Facebook, Twitter, etc.)Sorry for the inconvenience and we are here to help if you give us a chance.Sorry, we could not complete the email update. Another user with the same email is already registered.StartStart DebugStart TrialStart my free %sStateStop DebugSubmitSubmit & %sSubscriptionSupportSupport ForumSync Data From ServerTax / VAT IDTerms of ServiceThank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days.Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information.Thank you so much for using %s and its add-ons!Thank you so much for using %s!Thank you so much for using our products!Thank you!Thanks %s!Thanks for confirming the ownership change. An email was just sent to %s for final approval.The %s broke my siteThe %s didn't workThe %s didn't work as expectedThe %s is great, but I need specific feature that you don't supportThe %s is not workingThe %s suddenly stopped workingThe installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page.The remote plugin package does not contain a folder with the desired slug and renaming did not work.The upgrade of %s was successfully completed.ThemeTheme SwitchThemesThere is a %s of %s available.There is a new version of %s available.This plugin has not been marked as compatible with your version of WordPress.This plugin has not been tested with your current version of WordPress.TimestampTitleTo enter the debug mode, please enter the secret key of the license owner (UserID = %d), which you can find in your "My Profile" section of your User Dashboard:TotalTownTrialTypeUnable to connect to the filesystem. Please confirm your credentials.Unlimited LicensesUnlimited UpdatesUnlimited commissions.Up to %s SitesUpdateUpdate LicenseUpdates, announcements, marketing, no spamUpgradeUpload and activate the downloaded versionUsed to express elation, enthusiasm, or triumph (especially in electronic communication).W00tUser DashboardUser IDUser keyUsersValueVerification mail was just sent to %s. If you can't find it after 5 min, please check your spam box.VerifiedVerify EmailVersion %s was released.View detailsView paid featuresWarningWe can't see any active licenses associated with that email address, are you sure it's the right address?We couldn't find your email address in the system, are you sure it's the right address?We couldn't load the add-ons list. It's probably an issue on our side, please try to come back in few minutes.We made a few tweaks to the %s, %sWe're excited to introduce the Freemius network-level integration.Website, email, and social media statistics (optional)Welcome to %s! To get started, please enter your license key:What did you expect?What feature?What is your %s?What price would you feel comfortable paying?What you've been looking for?What's the %s's name?Where are you going to promote the %s?WordPress.org Plugin PageWould you like to proceed with the update?YesYes - %sYou already utilized a trial before.You are 1-click away from starting your %1$s-day free trial of the %2$s plan.You are all good!You are already running the %s in a trial mode.You are just one step away - %sYou can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.You do not have a valid license to access the premium version.You have a %s license.You have purchased a %s license.You have successfully updated your %s.You might have missed it, but you don't have to share any data and can just %s the opt-in.You've already opted-in to our usage-tracking, which helps us keep improving the %s.You've already opted-in to our usage-tracking, which helps us keep improving them.Your %s Add-on plan was successfully upgraded.Your %s free trial was successfully cancelled.Your %s license was flagged as white-labeled to hide sensitive information from the WP Admin (e.g. your email, license key, prices, billing address & invoices). If you ever wish to revert it back, you can easily do it through your %s. If this was a mistake you can also %s.Your %s license was successfully deactivated.Your account was successfully activated with the %s plan.Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s.Your affiliation account was temporarily suspended.Your email has been successfully verified - you are AWESOME!Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your free trial has expired. You can still continue using all our free features.Your license has been cancelled. If you think it's a mistake, please contact support.Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support.Your license has expired. You can still continue using the free %s forever.Your license was successfully activated.Your license was successfully deactivated, you are back to the %s plan.Your name was successfully updated.Your plan was successfully activated.Your plan was successfully changed to %s.Your plan was successfully upgraded.Your subscription was successfully cancelled. Your %s plan license will expire in %s.Your trial has been successfully started.ZIP / Postal Codea positive responseRight onactive add-onActiveaddonX cannot run without pluginY%s cannot run without %s.addonX cannot run...%s cannot run without the plugin.advance notice of something that will need attention.Heads upallowas 5 licenses left%s leftas activating pluginActivatingas annual periodyearas application program interfaceAPIas close a windowDismissas code debuggingDebuggingas congratulationsCongratsas connection blockedBlockedas connection was successfulConnectedas download latest versionDownload Latestas download latest versionDownload Latest Free Versionas every monthMonthlyas expiration dateExpirationas file/folder pathPathas in the process of sending an emailSending emailas monthly periodmoas once a yearAnnualas once a yearAnnuallyas once a yearOnceas product pricing planPlanas secret encryption key missingNo Secretas software development kit versionsSDK Versionsas software licenseLicenseas synchronizeSyncas synchronize licenseSync Licenseas the plugin authorAuthoras turned offOffas turned onOnbased on %scall to actionStart free trialclose a windowDismissclose windowDismissdeactivatingdelegatedo %sNOT%s send me security & feature updates, educational content and offers.e.g. Professional Plan%s Plane.g. billed monthlyBilled %se.g. the best productBestexclamationHeyexclamationOopsgreetingHey %s,installed add-onInstalledinterjection expressing joy or exuberanceYee-hawlicenselike websitesSitesmillisecondsmsnew Beta versionnew versionnot verifiednounPricenounPricingoptionalproduct versionVersionrevert it nowsecondssecseems like the key you entered doesn't match our records.send me security & feature updates, educational content and offers.skipsomething somebody says when they are thinking about what you have just said.Hmmstart the trialsubscriptionswitchingthe latest %s version heretrialtrial periodTrialverbDeleteverbDowngradeverbEditverbHideverbOpt InverbOpt OutverbPurchaseverbShowverbSkipverbUpdateverbUpgradex-ago%s agoProject-Id-Version: WordPress SDK Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues PO-Revision-Date: 2015-10-01 21:38+0000 Last-Translator: Leo Fajardo , 2022 Language-Team: Spanish (Spain) (http://app.transifex.com/freemius/wordpress-sdk/language/es_ES/) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Language: es_ES Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2; X-Poedit-Basepath: .. X-Poedit-KeywordsList: get_text_inline;get_text_x_inline:1,2c;$this->get_text_inline;$this->get_text_x_inline:1,2c;$this->_fs->get_text_inline;$this->_fs->get_text_x_inline:1,2c;$this->fs->get_text_inline;$this->fs->get_text_x_inline:1,2c;$fs->get_text_inline;$fs->get_text_x_inline:1,2c;$this->_parent->get_text_inline;$this->_parent->get_text_x_inline:1,2c;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c X-Poedit-SearchPath-0: . X-Poedit-SearchPathExcluded-0: *.js X-Poedit-SourceCharset: UTF-8 %s para acceder a la versión %s de actualizaciones de funciones, seguridad y soporte. El %s de %s enlace de descarga de %s, la clave de licencia y las instrucciones de instalación se han enviado a %s. Si no encuentras el correo electrónico después de 5 minutos, comprueba tu bandeja de correo no deseado. La versión de pago de %1$s ya está instalada. Por favor, actívala para empezar a beneficiarte de las características de %2$s. %3$s%1$s detendrá inmediatamente todos los pagos recurrentes futuros y tu licencia del plan %2$s caducará en %3$s.Completar la activación de "%s" ahoraEl complemento %s ha sido comprado correctamente.%s Instalaciones%s Licenciashace %s%s y sus complementos%s comisión cuando un cliente compra una nueva licencia.la prueba gratuita de %s fue cancelada con éxito. Puesto que el complemento es sólo premium se desactivó automáticamente. Si quieres utilizarlo en el futuro, deberás comprar una licencia.%s es un complemento único de premium. Tienes que comprar una licencia primero antes de activar el plugin.%s es el nuevo dueño de la cuenta.%s cantidad mínima a pagar.%s o mayor%s calificación%s calificaciones%s seg%s estrella%s estrellas%s vez%s veces%s para acceder a la versión %s de actualizaciones de funciones, seguridad y soporte.%s tracking cookie después de la primera visita para maximizar las ganancias potenciales.%s características de pago%sClick aquí %s para elegir los sitios sobre los que te gustaría activar la licencia.API←➤CuentaDetalles de la cuentaAccionesActivarActivar %sActivar plan %sActivar características %sActivar versión gratuitaActivar licenciaAplicar licencia en todos los sitios pendientes.Activar licencia en todos los sitios de la red.Activar este complementoActivadoComplementos para %sComplementos del módulo %sAñadir otro dominioComplementoComplementosEl complemento debe implementarse en WordPress.org o en Freemius.DirecciónLínea de la dirección %dAfiliadoAfiliaciónDespués de su período gratuito %s, pague sólo %sDe acuerdo y activar licenciaTodas las peticionesTodos los TiposPermitir y continuarAlternativamente, puedes saltarlo ahora y activar la licencia después, en tu %s página de cuenta a nivel de red.CantidadUna descarga automatizada y la instalación de %s (versión de pago) de %s comenzará en %s. Si quieres hacerlo manualmente - haz clic en el botón de cancelación.Se ha producido un error desconocido al intentar establecer el modo beta del usuario.Se ha producido un error desconocido al intentar activar el modo de marca blanca de la licencia.Se ha producido un error desconocido.Una actualización a una versión Beta reemplazará tu versión instalada de %s con la última versión Beta - úsalo con precaución, y no en sitios de producción. Te hemos avisado.Comentarios anónimosAplicar en todos los sitios pendientes.Aplicar en todos los sitios de la red.Aceptar para hacerse afiliado¿Está seguro que desea eliminar todos los datos de Freemius?¿Estás seguro que quieres proceder?Como aplazamos 30 días para posible devoluciones, sólo pagamos comisiones que son de más de 30 días.Asocia con la cuenta del propietario de la licencia.La instalación automática sólo funciona para usuarios que aceptaron.Auto renovaciones en %sInstalación automáticaCalificación mediaIncreíbleHacerse afiliadoBetaFacturaciónFacturación y facturasBloqueandoID del blogCuerpoPlan combinadoNombre de la empresaCompra una licencia ahoraComprar licenciaAl cambiar al usuario, usted acepta transferir la propiedad de la cuenta a:¿No puedes encontrar tu clave de licencia?CancelarCancelar %s y procederCancelar %s - No necesito más actualizaciones de características y seguridad, ni soporte para %s porque no pretendo utilizar%s en este, u otro sitio.¿Cancelar %s?Cancelar instalaciónCancelar suscripciónCancelar período de pruebaCanceladoCancelando %sCancelando %s...Cancelando la suscripciónLa cancelación del período de prueba bloqueará inmediatamente el acceso a todas las funciones premium. ¿Estás seguro?Cambiar licenciaCambiar propietarioCambiar PlanCambiar usuarioPagarBorrar caché de la APIBorrar transients de actualizacionesHaz clic aquíHaz click aquí para utilizar el plugin de forma anónimaHaz clic para ver los comentarios con una valoración de %sClick para ver la captura de pantalla a tamaño completo %dProductosCódigoCompatible hastaContactoContactar soporteContáctanosColaboradoresNo se puede activar %s.PaísTipo de cronFechaDesactivarDesactivar licenciaDesactivar o desinstalar %s deshabilitará automáticamente la licencia, que podrás usar en otro sitio.Al desactivar tu licencia todas las características premium se bloquearán, pero posibilitará poder activar tu licencia en otro sitio. ¿Estás seguro que quieres continuar?DesactivaciónLog de DebugEl modo de depuración se ha activado con éxito y se desactivará automáticamente en 60 minutos. También puedes desactivarlo antes haciendo clic en el enlace "Detener depuración".Delegar a administradores del sitioBorrar todas las cuentasDetallesDesactivar el modo de marca blancaNo cancelar %s - Todavía estoy interesado en obtener actualizaciones de características y seguridad, así como poder contactar con soporte.¿No tienes una clave de licencia?Donar a este pluginBajando tu planDescargaDescargar versión %sDescargar la versión de pagoDescargar la última versión %sDescargar la última versiónDescargadoDebido al nuevo %sEU Reglamento General de Protección de Datos (RGPD)%s los requisitos de obligado cumplimiento requieren que proporciones tu consentimiento explícito, una vez más, confirmando que estás de acuerdo :-)Debido a la violación de nuestros términos de afiliados, hemos decidido bloquear temporalmente tu cuenta de afiliación. Si tienes alguna pregunta, por favor contacta nuestro soporte.Durante el proceso de actualización hemos detectado%d sitio(s) que aún están pendientes de la activación de licencia.Durante el proceso de actualización detectamos %s sitio(s) en la red que todavía están pendientes de tu atención.Correo electrónicoDirección de correo electrónicoActivar el modo de marca blancaFinIntroduce el correo electrónicoIntroduce el dominio de tu sitio web o de otros sitios web donde planeas promocionar %s.Escribe abajo la dirección de correo electrónico que has usado para la actualización y te reenviaremos la clave de licencia.ErrorError recibido del servidor:CaducadoCaduca en %sDominios extraDominios extra desde donde promocionarás el producto.ArchivoFiltroPara el cumplimiento de las directrices de WordPress.org, antes de empezar el período de prueba te pedimos que aceptes con tu usuario e información no sensible del sitio web, permitiendo a %s enviar datos periódicamente a %s para comprobar si hay actualizaciones de versión y para validar la versión de prueba.GratisPeríodo de prueba gratuitoVersión gratuitaAPI FreemiusDebug FreemiusFreemius SDK no pudo encontrar el archivo principal del plugin. Por favor contacta a sdk@freemius.com con el error actual.Estado FreemiusFreemius es nuestro motor de licencias y actualizaciones de softwareNombre completoFunciónObtén comisiones por renovaciones automatizadas de las suscripciones.Obten actualizaciones para las versiones Beta de vanguardia de %s.¿Tienes una clave de licencia?Hey, ¿sabías que %s tiene un programa de afiliados? ¡Si te gusta %s puedes convertirte en nuestro embajador y ganar dinero!¿Qué te pareció %s hasta ahora? Prueba todas nuestras funciones premium de %s con una prueba gratuita de %d-días.¿Cómo subirlo y activarlo?¿Como nos promocionarás?Estoy de acuerdo - Cambiar usuarioNo puedo pagarlo durante más tiempoNo entiendo cómo hacerlo funcionarNo me gusta compartir mi información contigoHe encontrado un %s mejorHe actualizado mi cuenta, pero cuando intento sincronizar la licencia, el plan sigue siendo %s.Ya no necesito el %sSólo necesitaba la %s por un corto períodoIDSi haces click, esta decisión será delegada a los administradores de los sitios.Si tienes un momento, por favor, dinos por qué estás %sSi deseas renunciar a la titularidad de la cuenta de %s a %s haz clic en el botón de cambio de titularidad.Si quieres utilizar %s en estos sitios, introduce por favor tu clave de licencia abajo y haz click en el botón de activación.Aviso importante de actualización:En %sEn caso de que NO estés planeando utilizar este %s en este sitio (o en cualquier otro sitio), ¿te gustaría cancelar también %s?Instalar la versión gratuita ahoraInstalar la actualización gratuita ahoraInstalar ahoraInstalar actualización ahoraInstalando plugin: %sId de módulo no válido.Nuevo ID de usuario o dirección de correo electrónico no válido.Colección de detalles del sitio no válida.FacturaEstá activo¿Es este el sitio de clientes? %s si deseas ocultar información sensible como tu correo electrónico, clave de licencia, precios, dirección de facturación y facturas de la administración de WP.Parece que la licencia no se pudo activar.Parece que la desactivación de licencia ha fallado.Parece que ya no estás en modo de prueba, así que no hay nada que cancelar :)Parece que todavía estás en el plan %s. Si actualizaste o cambiaste tu plan, probablemente sea un problema de nuestra parte - lo sentimos.Parece que tu sitio actualmente no tiene una licencia activa.Parece que uno de los parámetros de autenticación es incorrecto. Actualiza tu clave pública, clave secreta e ID de usuario e inténtelo de nuevo.No es lo que estaba buscandoÚnete al programa BetaSólo déjanos informarte que la información de complementos de %s se está extrayendo de un servidor externo.ClavePor favor, comparte lo que no funcionó para que podamos arreglarlo para los futuros usuarios...Por favor, dínos la razón para que podamos mejorar.ÚltimoÚltima actualizaciónÚltima licenciaÚltima versión gratuita instaladaÚltima versión instaladaSaber másLongitudLicenciaAcuerdo de licenciaID de licenciaClave de licencia¿Problemas de licencia?Clave de licenciaLa clave de licencia está vacía.Permanente¿Te gusta %s? Conviértete en nuestro embajador y gana dinero ;-)Cargar opción de BDLocalhostLogLoggerMensajeMétodoMigrar opciones a la redApps móvilesMóduloRuta del móduloTipo de móduloMás información sobre %sNombreBlog de redUsuario de redNuevoNueva versión disponibleVersión gratuita más reciente (%s) instaladaVersión más reciente (%s) instaladaBoletínSiguienteNoSin IDSin compromiso para %s - cancelar en cualquier momentoSin compromiso por %s días - ¡cancelar en cualquier momento!No se necesita tarjeta de créditoSin caducidadSin caducidadNinguno de los planes de %s soportan un período de prueba.O.KUna vez que caduque tu licencia todavía puedes utilizar la versión gratuita pero NO tendrás acceso a las funciones de %s.Una vez que tu licencia caduque no podrás seguir utilizando %s, a no ser que lo actives de nuevo con una licencia premium válida.InscribirseDarse de baja¡Inscríbite para hacer "%s" Mejor!OtraCorreo electrónico del propietarioID del propietarioNombre del propietarioCompatible con PCIEl complemento de pago se debe implementar en Freemius.Dirección de correo electrónico de PayPalPagosLos pagos son en USD y se procesan mensualmente por medio de PayPal.PlanEl plan %s no existe, por lo tanto, no puedes comenzar un período de prueba.El plan %s no admite un período de prueba.ID del planContacta aquí con nosotrosPor favor contáctanos con el siguiente mensaje:Por favor descarga %s.Por favor, introduce la clave de licencia que recibiste en el correo electrónico al realizar la compra:Por favor, introduce la clave de licencia para activar el modo de depuración:Siéntete libre de proporcionarnos estadísticas de tu sitio web o social media, p.ej. visitas únicas mensuales, número de suscriptores de correo electrónico, seguidores, etc. (mantendremos esta información confidencial)Por favor, sigue estos pasos para completar la actualizaciónIndica si deseas que te contactemos para actualizaciones de seguridad y nuevas funciones, contenido educativo y ofertas ocasionales:Ten en cuenta que no podremos abaratar los precios desactualizados para renovaciones/nuevas suscripciones después de una cancelación. Si eliges renovar la suscripción manualmente en el futuro, después de un aumento de precio, que generalmente ocurre una vez al año, se te cobrará el precio actualizado.Por favor, danos detalles de como pretendes promocionar %s (por favor, se lo más específico que puedas)Por favor, dinos tu nombre completo.PluginPágina web del pluginID del pluginInstalar pluginRegistro de cambiosDescripciónFAQCaracterísticas y preciosInstalaciónOtras notasValoracionesEl plugin es un "Serviceware" lo que significa que no tiene una versión de código premium.PluginsSincronizar plugins y temasPremiumLa versión Premium %s ha sido activada con éxito.Versión del complemento premium ya instalada.Versión premiumVersión premium ya activa.PrecioPolítica de privacidadProcederID del procesoProcesandoProductosSumario del programaMétodos de promociónProvinciaClave públicaComprar licenciaComprar másComentarios rápidosCuotaReenviar correo electrónico de activación¡Envíanos nuevos usuarios a nuestro %s y gana %s de comisión en cada venta satisfactoria que nos hayas referido!Renovar la licenciaRenueva tu licencia ahoraPeticionesNecesita la versión de WordPressResultadoSDKRuta del SDKGuardar %sGuardadoCrons programadosCapturas de pantallaBuscar por direcciónClave secretaPágina segura HTTPS %s, desde un dominio externoParece que estamos teniendo algún problema temporal con tu cancelación de la suscripción. Vuelve a intentarlo en unos minutos.Parece que estamos teniendo algún problema temporal con tu cancelación de prueba. Vuelve a intentarlo en unos minutos.Parece que tienes la última versión.Seleccionar paísEnviar clave de licenciaGuardar opción en BDSimular actualización de redSimular período de pruebaLicencia para un único sitioID del sitioSitio dado de alta correctamente.SitiosSaltar y %sRutaDe este modo, podrás reutilizar la licencia cuando el %s ya no esté activo.Social media (Facebook, Twitter, etc.)Disculpa las molestias y estamos aquí para ayudarte si nos das una oportunidad.Lo sentimos, no podemos completar la actualización de correo electrónico. Ya hay registrado otro usuario con esa dirección de correo electrónico.InicioIniciar DepuraciónComenzar el período de pruebaComenzar mi período gratuito de %sEstadoDetener la depuraciónEnviarEnviar y %sSuscripciónSoporteForo de soporteSincronizar datos desde el servidorTax / Núm IVATérminos de servicioGracias por aplicar a nuestro programa de asociados, infortunadamente, de momento hemos decidido rechazar tu petición. Por favor, prueba de nuevo en 30 días.Gracias por aplicar a nuestro programa de afiliados, revisaremos tu petición durante los próximos 14 días y te volveremos a contactar con información adicional.¡Muchas gracias por utilizar %s y sus complementos!¡Muchas gracias por utilizar %s!¡Muchas gracias por utilizar nuestros productos!¡Gracias!¡Gracias %s!Gracias por confirmar el cambio de propiedad. Se envió un correo electrónico a %s para su aprobación final.%s ha roto mi sitioEl %s no funcionabaEl %s no funciona como esperaba%s es genial, pero necesito una característica que no soportáisEl %s no funciona%s de repente ha dejado de funcionarEl proceso de instalación ha comenzado y puede tardar unos minutos en completarse. Por favor, espera hasta que se finalice - no actualices esta página.El paquete de plugin remoto no contiene una carpeta con el Slug deseado y el cambio de nombre no funcionó.La actualización de %s se completó con éxito.TemaCambiar temaTemasHay una %s de %s disponible.Hay una nueva versión de %s disponible.Este puglin no ha sido marcado como compatible con tu versión de WordPress.Este plugin no ha sido probado con tu versión actual de WordPress.TimestampTítuloPara entrar en el modo de depuración, introduce la clave secreta del propietario de la licencia (UserID = %d), que puedes encontrar en la sección "Mi perfil" de tu panel de control de usuario:TotalMunicipioPeríodo de prueba gratuitoTipoNo es posible conectarse al sistema de archivos. Por favor, confirma tus credenciales.Licencias ilimitadasActualizaciones IlimitadasComisiones Ilimitadas.Hasta %s sitiosActualizarActivar licenciaActualizaciones, anuncios, marketing, sin spamActualizarCargar y activar la versión descargadaW00tPanel de escritorio del usuarioID de usuarioClave de usuarioUsuariosValorEl correo de verificación se acaba de enviar a %s. Si no puedes encontrarlo después de 5 min, comprueba tu carpeta de spam.VerificadoVerificar correo electrónicoLa versión %s se ha lanzado.Ver detallesVer las funciones de pagoAtencionNo vemos ninguna licencia activa asociada a esa dirección de correo electrónico, ¿estás seguro de que es la dirección de correo electrónico correcta?No podemos encontrar tu dirección de correo electrónico en el sistema, ¿estás seguro de que es la dirección de correo electrónico correcta?No pudimos cargar la lista de complementos. Probablemente sea un problema por nuestra parte, por favor, inténtalo de nuevo en unos minutos.Hemos realizado algunas optimizaciones al %s, %sEstamos emocionados de introducir la integración de Freemius a nivel de red.Sitio web, correo electrónico y estadísticas de social media (opcional)¡Bienvenido a %s! Para empezar, introduce tu clave de licencia:¿Qué esperas?¿Qué característica?¿Cual es tú %s?¿Con qué precio te sentirías cómodo pagando?¿Que has estado buscando?¿Cuál es el nombre de %s?¿Dónde vas a promocionar %s?Página del plugin en WordPress.org¿Deseas continuar con la actualización?SiSi - %sYa utilizaste un período de prueba antes.Estás a sólo 1-click de comenzar tu %1$s días de prueba gratuita del plan %2$s.¡Está todo listo!Estás ejecutando %s en modo de prueba.Estás a sólo un paso - %sTodavía puedes disfrutar de todas las funciones de %s pero no tendrás acceso a soporte y actualizaciones de %s.No tienes una licencia válida para acceder a la versión premium.Tienes una licencia %s.Has comprado una licencia %s.Has actualizado correctamente tu %s.Es posible que te lo hayas perdido, pero no tienes que compartir ningún dato y puedes solo aceptar %s.Ya has optado por nuestro seguimiento de uso, lo que nos ayuda a seguir mejorando %s.Ya has optado por nuestro seguimiento de uso, lo que nos ayuda a seguir mejorando.Tu complemento %s del plan se actualizó con éxito.Tu prueba gratuita de %s fue cancelada con éxito.Tu licencia %s ha sido marcada como etiqueta blanca para ocultar información sensible del administrador de WP (por ejemplo, tu correo electrónico, clave de licencia, precios, dirección de facturación y facturas). Si alguna vez deseas revertirlo, puedes hacerlo fácilmente a través de tu %s. Si se trata de un error, también puedes %s.Tu licencia %s ha sido desactivada correctamente.Tu cuenta se ha activado correctamente con el plan %s.¡Tu aplicación al programa de afiliación para %s ha sido aceptada! Entra en tu área de afiliado desde: %s.Tu cuenta de afiliado ha sido suspendida temporalmente.Tu email ha sido verificado correctamente - ¡Eres IMPRESIONANTE!Tu período de prueba ha caducado. %1$sActualiza ahora %2$s para continuar usando el %3$s sin interrupciones.Tu período de prueba ha caducado. Todavía puedes seguir usando todas nuestras funciones gratuitas.Tu licencia ha sido cancelada. Si crees que es un error, ponte en contacto con el servicio de asistencia.Tu licencia ha caducado. %1$sActualiza ahora %2$s para continuar usando el %3$s sin interrupciones.Tu licencia ha caducado. Todavía puedes seguir usando todas las funciones de %s, pero tendrás que renovar tu licencia para seguir recibiendo actualizaciones y soporte.Tu licencia ha caducado. Puedes seguir usando el plan gratuito %s para siempre.Tu licencia fue activada correctamente.Tu licencia fue desactivada correctamente, has vuelto al plan %s.Tu nombre fue actualizado correctamente.Tu plan se activó con éxito.Tu plan se cambió correctamente a %s.Tu plan se actualizó con éxito.Tu suscripción ha sido cancelada correctamente. Tu %s licencia del plan caducará en %s.Tu versión de prueba se ha iniciado con éxito.Código postalBien hechoActivo%s no se puede ejecutar sin %s.%s no se puede ejecutar sin el plugin.Atenciónpermitirquedan %sActivandoañoAPIDescartarDepurandoFelicidadesBloqueadoConectadoDescargar la últimaDescargar la última versión gratuitaMensualCaducidadRutaEnviando correo electrónicomeAnualAnualmenteUna vezPlanSin clave secretaVersiones SDKLicenciaSincronizarSincronizar licenciaAutorApagadoEncendidobasado en %sComenzar el período de prueba gratuitoDescartarDescartardesactivandodelegar%sNO%s me envíes actualizaciones de seguridad y nuevas funcionalidades, contenido educativo y ofertas.Plan %sFacturado %sEl mejorHeyOopsHey %s,InstaladoVayalicenciaSitiosmsnueva versión Betanueva versiónno verificadoPrecioPrecioopcionalVersiónrevertirlo ahorasegparece que la clave que has introducido no coincide con nuestros registros.envíame actualizaciones de seguridad y nuevas funcionalidades, contenido educativo y ofertas.saltarHmmcomenzar el período de pruebasuscripcióncambiandola última versión %s aquíperíodo de pruebaPeríodo de Prueba GratuitoBorrarDegradarEditarOcultarInscribirseDarse de bajaComprarMostrarSaltarActualizarActualizarhace %sPK!_l.libraries/freemius/languages/freemius-zh_CN.monu[<\*\*A]**nB+2+Z+h?,S,%, "- .-:-A-T-6-1._.F/f/#}//%/ / / /0 000#0@,0+m0H00O0ME1j112)22233333 33334&4-=4k4 4444445455 5 (5'45\5 u5 55o5 66G6T6P7o78,8"H8k8(828!8>9aE9+909::-:<:D:X:]:e:x:::: : :: :E:y;;;;; X<c<w< < < <<<Y<7=F= W= c=o=x=}== =(=1=% >:2>m> r>>>> > >>> >7>!? &?1?xD?? R@ _@i@@A*A2A5BAxAXAtAaB{BBBBBBB C CCrD[DfDGE ME[EpEEEYE_Ea\FFFFG G G !G;/GkGpGwGOvHH H H HHjHjI5yI II3I2I)J~=J:JUJMKiKKK)K-K LSLsL'LLgLMMZlM7M>Mm>NgNpOOOyOP8P XPdPwP PP%P PQQ-QDQ bQ&lQQ1)R.[RORRAZSSyS26TiTTaT UU%UB)U,lUU U UUU UUV V V &V2V BVNVdV4mVV VVVVVVVVW W(W /W ;WGWaW,fW W WWWnXrX X!XX XXX%X Y%Y+8YdY |Y Y/YYmY~9ZZZZi[q\\ \\ \ \)\.\V]fq]|]U^r^4{^^5^(^__-3_a_Uu_6_`1`~`|a[bbcc +c5c(Dc*mc"c1c+c*d&DdNkdddd.d)e9eIeieqee e eeeee ee ee ffW*f fffffffggggg .g:g Lg5Wgsglh&nhhh h hiiij(j0jLj Rj\j aj=mj&jLjfkk k kkkk kk k kk k l !l.l?ll%lm/mm)m n n\"nnnn6oKo^oC}oooopmpxqd|q-qr r"r)r'HrprMsGks,ss sttStuu4vw"w'w-wE2wxwwwwww*wx* x^7xxxxxxdx'y 0y=y Vydywyyyyy yyzizWznzH{"{B|6S|=|| ||-|*}H}&^}/}}#}*}~"~+~/I~Qy~U~D!$fM/o;> &"<IDZˁO&vT~RӃ.&.U-;ą9Z:3<ɆbPiU_pK (WG#ȉ%)$<aU)Xɋ;ދ6>Qь$ &B^&|*7΍;3Tˎߎ*1'Yu#ɏۏ .CW\ anNwƐ.?PU[1vđԑ 7 @ N9ZCؒQݒ/ ? LVp  ɓ ӓ ߓ    '4:3Qԙ&S-c '3<Q,\$-Lg  ǝ֝͝:ޝ G:7V̞v#JNU^gn{ #2E-U0ǡΡݡ  3S Zf mz ʢYڢ4;<<:P ۤ$$ 2.K,z$O'2-Z  Ѧ ަ   &3I<Yh!!(: ̨ ب  'E4z ©ԩ ),=jqx  ̪ Ӫ9ު&\6l  Ȭ+լH`]׭)AT[ pM}V˯ "/B[nu;`ǰf(ѱ ر '$+B 7 A N [h[xԳ/")Akp:i0ϵ!9\Q#X9@dz2߷ETXUkoa (5H`,! #D$W|&ENdu0)Zkv6/GB Q*.5H^}  ʿڿ6Qgn u  9 W dq$+ ;&E l *! ( 5)?iYme - :G#  ),3 `m }!8 /Caj'?0+!u P1  Nls !   !( / <IMT d q~L0FY` v  (]]O$   )3R Yc hAt$`i<    !+M `zm u,'  _ w/>2V a^h{K0N Ubi9,9f% zEbwGmt{6  *DKjo  W .=TmlQeZ%/6;F0"S n!4SD[5G:&Mtg0%:W2o7MP(yVW#2&V }#D)O$k4TWZrQJ!8Lk#IX   6=D MZ^bip w    '.>ELS\ov} F #*18 ?IP W d nx  3?#* .; B LYt{  %s to access version %s security & feature updates, and support. The %s's %sdownload link%s, license key, and installation instructions have been sent to %s. If you can't find the email after 5 min, please check your spam box. The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s"The ", e.g.: "The plugin"The %s's%1$s has been placed into safe mode because we noticed that %2$s is an exact copy of %3$s.%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s.%s - plugin name. As complete "PluginX" activation nowComplete "%s" Activation Now%s Add-on was successfully purchased.%s Installs%s Licenses%s ago%s and its add-ons%s automatic security & feature updates and paid functionality will keep working without interruptions until %s (or when your license expires, whatever comes first).%s commission when a customer purchases a new license.%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license.%s is a premium only add-on. You have to purchase a license first before activating the plugin.%s is my client's email address%s is my email address%s is the new owner of the account.%s minimum payout amount.%s opt-in was successfully completed.%s or higher%s rating%s ratings%s sec%s star%s stars%s time%s times%s to access version %s security & feature updates, and support.%s to activate the license once you get it.%s tracking cookie after the first visit to maximize earnings potential.%s's paid features%sClick here%s to choose the sites where you'd like to activate the license on.Click here to learn more about updating PHP.A confirmation email was just sent to %s. The email owner must confirm the update within the next 4 hours.A confirmation email was just sent to %s. You must confirm the update within the next 4 hours. If you cannot find the email, please check your spam folder.APIAPI connectivity state is unknownUnknownASCII arrow left icon←ASCII arrow right icon➤AccountAccount DetailsAccount is pending activation. Please check your email and click the link to activate your account and then submit the affiliate form again.ActionsActivateActivate %sActivate %s PlanActivate %s featuresActivate Free VersionActivate LicenseActivate license on all pending sites.Activate license on all sites in the network.Activate this add-onActivatedAdd Ons for %sAdd Ons of module %sAdd another domainAdd-OnAdd-OnsAdd-on must be deployed to WordPress.org or Freemius.AddressAddress Line %dAffiliateAffiliationAfter your free %s, pay as little as %sAgree & Activate LicenseAll RequestsAll TypesAllow & ContinueAlternatively, you can skip it for now and activate the license later, in your %s's network-level Account page.AmountAn automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now.An unknown error has occurred while trying to set the user's beta mode.An unknown error has occurred while trying to toggle the license's white-label mode.An unknown error has occurred.An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned.Anonymous feedbackApply on all pending sites.Apply on all sites in the network.Apply to become an affiliateAre both %s and %s your email addresses?Are you sure you want to delete all Freemius data?Are you sure you want to proceed?Are you sure you would like to proceed with the disconnection?As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days.Associate with the license owner's account.Auto installation only works for opted-in users.Auto renews in %sAutomatic InstallationAverage RatingAwesomeBecome an affiliateBetaBillingBilling & InvoicesBlockingBlog IDBodyBundleBundle PlanBusiness nameBuy a license nowBuy licenseBy changing the user, you agree to transfer the account ownership to:By disconnecting the website, previously shared diagnostic data about %1$s will be deleted and no longer visible to %2$s.Can't find your license key?CancelCancel %s & ProceedCancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site.Cancel %s?Cancel InstallationCancel SubscriptionCancel TrialCancelledCancelling %sCancelling %s...Cancelling the subscriptionCancelling the trial will immediately block access to all premium features. Are you sure?Change LicenseChange OwnershipChange PlanChange UserCheckoutCityClear API CacheClear Updates TransientsClick hereClick here to use the plugin anonymouslyClick to see reviews that provided a rating of %sClick to view full-size screenshot %dClone resolution admin notice products list labelProductsCodeCommunicationCompatible up toContactContact SupportContact UsContributorsCouldn't activate %s.CountryCron TypeCurrent %s & SDK versions, and if active or uninstalledDateDeactivateDeactivate LicenseDeactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site.Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?DeactivationDebug LogDebug mode was successfully enabled and will be automatically disabled in 60 min. You can also disable it earlier by clicking the "Stop Debug" link.Delegate to Site AdminsDelete All AccountsDetailsDiagnostic InfoDiagnostic data will no longer be sent from %s to %s.Disabling white-label modeDisconnecting the website will permanently remove %s from your User Dashboard's account.Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support.Don't have a license key?Donate to this pluginDowngrading your planDownloadDownload %s VersionDownload Paid VersionDownload the latest %s versionDownload the latest versionDownloadedDue to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.Duplicate WebsiteDuring the update process we detected %d site(s) that are still pending license activation.During the update process we detected %s site(s) in the network that are still pending your attention.EmailEmail addressEmail address updateEnabling white-label modeEndEnter email addressEnter the domain of your website or other websites from where you plan to promote the %s.Enter the email address you've used during the purchase and we will resend you the license key.Enter the email address you've used for the upgrade below and we will resend you the license key.Enter the new email addressErrorError received from the server:ExpiredExpires in %sExtensionsExtra DomainsExtra domains where you will be marketing the product from.FileFilterFor compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.For delivery of security & feature updates, and license management, %s needs toFreeFree TrialFree versionFreemius APIFreemius DebugFreemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error.Freemius StateFreemius is our licensing and software updates engineFull nameFunctionGet commission for automated subscription renewals.Get updates for bleeding edge Beta versions of %s.Have a license key?Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!Homepage URL & title, WP & PHP versions, and site languageHow do you like %s so far? Test all our %s premium features with a %d-day free trial.How to upload and activate?How will you promote us?I Agree - Change UserI can't pay for it anymoreI couldn't understand how to make it workI don't like to share my information with youI found a better %sI have upgraded my account but when I try to Sync the License, the plan remains %s.I no longer need the %sI only needed the %s for a short periodIDIf this is a long term duplicate, to keep automatic updates and paid functionality after %s, please %s.If you click it, this decision will be delegated to the sites administrators.If you didn't get the email, try checking your spam folder or search for emails from %4$s.If you have a moment, please let us know why you are %sIf you skip this, that's okay! %1$s will still work just fine.If you wish to cancel your %1$s plan's subscription instead, please navigate to the %2$s and cancel it there.If you would like to give up the ownership of the %s's account to %s click the Change Ownership button.If you'd like to use the %s on those sites, please enter your license key below and click the activation button.Important Upgrade Notice:In %sIn case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?Install Free Version NowInstall Free Version Update NowInstall NowInstall Update NowInstalling plugin: %sInvalid clone resolution action.Invalid module ID.Invalid new user ID or email address.Invalid site details collection.InvoiceIs %2$s a duplicate of %4$s?Is %2$s a new website?Is %2$s the new home of %4$s?Is ActiveIs active, deactivated, or uninstalledIs this your client's site? %s if you wish to hide sensitive info like your email, license key, prices, billing address & invoices from the WP Admin.It looks like the license could not be activated.It looks like the license deactivation failed.It looks like you are not in trial mode anymore so there's nothing to cancel :)It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry.It looks like your site currently doesn't have an active license.It requires license activation.It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.It's a temporary %s - I'm troubleshooting an issueIt's not what I was looking forJoin the Beta programJust letting you know that the add-ons information of %s is being pulled from an external server.Keep SharingKeep automatic updatesKeyKindly share what didn't work so we can fix it for future users...Kindly tell us the reason so we can improve.LastLast UpdatedLast licenseLatest Free Version InstalledLatest Version InstalledLearn moreLengthLicenseLicense AgreementLicense IDLicense KeyLicense issues?License keyLicense key is empty.LifetimeLike the %s? Become our ambassador and earn cash ;-)Load DB OptionLocalhostLogLoggerLong-Term DuplicateMessageMethodMigrateMigrate LicenseMigrate Options to NetworkMobile appsModuleModule PathModule TypeMore information about %sNameNames, slugs, versions, and if active or notNetwork BlogNetwork UserNever miss an important updateNever miss important updates, get security warnings before they become public knowledge, and receive notifications about special offers and awesome new features.NewNew Version AvailableNew WebsiteNewer Free Version (%s) InstalledNewer Version (%s) InstalledNewsletterNextNoNo - only move this site's data to %sNo IDNo commitment for %s - cancel anytimeNo commitment for %s days - cancel anytime!No credit card requiredNo expirationNon-expiringNone of the %s's plans supports a trial period.O.KOnce your license expires you can still use the Free version but you will NOT have access to the %s features.Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license.Opt InOpt OutOpt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info.Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info. This will help us make the %s more compatible with your site and better at doing what you need it to.Opt in to make "%s" better!OtherOwner EmailOwner IDOwner NamePCI compliantPaid add-on must be deployed to Freemius.Part of an activation link message.Click herePart of the message telling the user what they should receive via email.a license keyPart of the message telling the user what they should receive via email.the installation instructionsPart of the message that tells the user to check their spam folder for a specific email.the product's support email addressPayPal account email addressPaymentsPayouts are in USD and processed monthly via PayPal.PlanPlan %s do not exist, therefore, can't start a trial.Plan %s does not support a trial period.Plan IDPlease contact us herePlease contact us with the following message:Please download %s.Please enter the license key that you received in the email right after the purchase:Please enter the license key to enable the debug mode:Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential).Please follow these steps to complete the upgradePlease let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.Please provide details on how you intend to promote %s (please be as specific as possible).Please provide your full name.PluginPlugin HomepagePlugin IDPlugin InstallPlugin installer section titleChangelogPlugin installer section titleDescriptionPlugin installer section titleFAQPlugin installer section titleFeatures & PricingPlugin installer section titleInstallationPlugin installer section titleOther NotesPlugin installer section titleReviewsPlugin is a "Serviceware" which means it does not have a premium code version.PluginsPlugins & Themes SyncPremiumPremium %s version was successfully activated.Premium add-on version already installed.Premium versionPremium version already active.PricingPrivacy PolicyProceedProcess IDProcessingProductsProgram SummaryPromotion methodsProvincePublic KeyPurchase LicensePurchase MoreQuick FeedbackQuotaRe-send activation emailRefer new customers to our %s and earn %s commission on each successful sale you refer!Renew licenseRenew your license nowRequestsRequires PHP VersionRequires WordPress VersionReset Deactivation SnoozingResultSDKSDK PathSave %sSavedScheduled CronsScreenshotsSearch by addressSecret KeySecure HTTPS %s page, running from an external domainSeems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes.Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.Seems like you got the latest release.Select CountrySend License KeySet DB OptionSharing diagnostic data with %s helps to provide functionality that's more relevant to your website, avoid WordPress or PHP version incompatibilities that can break your website, and recognize which languages & regions the plugin should be translated and tailored to.Show error detailsSimulate Network UpgradeSimulate Trial PromotionSingle Site LicenseSite IDSite successfully opted in.SitesSkip & %sSlugSnooze & %sSo you can reuse the license when the %s is no longer active.Social media (Facebook, Twitter, etc.)Sorry for the inconvenience and we are here to help if you give us a chance.Sorry, we could not complete the email update. Another user with the same email is already registered.StartStart DebugStart TrialStart my free %sStateStay ConnectedStop DebugSubmitSubmit & %sSubscriptionSupportSupport ForumSync Data From ServerTax / VAT IDTerms of ServiceThank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days.Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information.Thank you for updating to %1$s v%2$s!Thank you so much for using %s and its add-ons!Thank you so much for using %s!Thank you so much for using our products!Thank you!Thanks %s!Thanks for confirming the ownership change. An email was just sent to %s for final approval.Thanks for upgrading.Thanks!The %1$s will be periodically sending essential license data to %2$s to check for security and feature updates, and verify the validity of your license.The %s broke my siteThe %s didn't workThe %s didn't work as expectedThe %s is great, but I need specific feature that you don't supportThe %s is not workingThe %s suddenly stopped workingThe following products'The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page.The products below have been placed into safe mode because we noticed that %2$s is an exact copy of %3$s:%1$sThe products below have been placed into safe mode because we noticed that %2$s is an exact copy of these sites:%3$s%1$sThe remote plugin package does not contain a folder with the desired slug and renaming did not work.The upgrade of %s was successfully completed.ThemeTheme SwitchThemesThere is a %s of %s available.There is a new version of %s available.There was an unexpected API error while processing your request. Please try again in a few minutes and if it still doesn't work, contact the %s's author with the following:This plugin has not been marked as compatible with your version of WordPress.This plugin has not been tested with your current version of WordPress.This plugin requires a newer version of PHP.This will allow %s toTimestampTitleTo avoid breaking your website due to WordPress or PHP version incompatibilities, and recognize which languages & regions the %s should be translated and tailored to.To ensure compatibility and avoid conflicts with your installed plugins and themes.To enter the debug mode, please enter the secret key of the license owner (UserID = %d), which you can find in your "My Profile" section of your User Dashboard:To let you manage & control where the license is activated and ensure %s security & feature updates are only delivered to websites you authorize.To provide additional functionality that's relevant to your website, avoid WordPress or PHP version incompatibilities that can break your website, and recognize which languages & regions the %s should be translated and tailored to.TotalTownTrialTypeUnable to connect to the filesystem. Please confirm your credentials.Unlimited LicensesUnlimited UpdatesUnlimited commissions.Up to %s SitesUpdateUpdate LicenseUpdates, announcements, marketing, no spamUpgradeUpload and activate the downloaded versionUsed to express elation, enthusiasm, or triumph (especially in electronic communication).W00tUser DashboardUser IDUser keyUsersValueVerification mail was just sent to %s. If you can't find it after 5 min, please check your spam box.VerifiedVerify EmailVersion %s was released.View %s StateView Basic %s InfoView Basic Profile InfoView Basic Website InfoView Diagnostic InfoView License EssentialsView Plugins & Themes ListView detailsView paid featuresWarningWe can't see any active licenses associated with that email address, are you sure it's the right address?We couldn't find your email address in the system, are you sure it's the right address?We couldn't load the add-ons list. It's probably an issue on our side, please try to come back in few minutes.We have introduced this opt-in so you never miss an important update and help us make the %s more compatible with your site and better at doing what you need it to.We made a few tweaks to the %s, %sWe're excited to introduce the Freemius network-level integration.Website, email, and social media statistics (optional)Welcome to %s! To get started, please enter your license key:What did you expect?What feature?What is your %s?What price would you feel comfortable paying?What you've been looking for?What's the %s's name?Where are you going to promote the %s?WordPress & PHP versions, site language & titleWordPress.org Plugin PageWould you like to merge %s into %s?Would you like to proceed with the update?YesYes - %sYes - both addresses are mineYes - move all my data and assets from %s to %sYes, %%2$s is replacing %%4$s. I would like to migrate my %s from %%4$s to %%2$s.Yes, %2$s is a duplicate of %4$s for the purpose of testing, staging, or development.Yes, %2$s is a new and different website that is separate from %4$s.You already utilized a trial before.You are 1-click away from starting your %1$s-day free trial of the %2$s plan.You are all good!You are already running the %s in a trial mode.You are just one step away - %sYou can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.You do not have a valid license to access the premium version.You have a %s license.You have purchased a %s license.You have successfully updated your %s.You marked this website, %s, as a temporary duplicate of %s.You marked this website, %s, as a temporary duplicate of these sitesYou might have missed it, but you don't have to share any data and can just %s the opt-in.You should receive %3$s for %1$s to your mailbox at %2$s in the next 5 minutes.You should receive a confirmation email for %1$s to your mailbox at %2$s. Please make sure you click the button in that email to %3$s.You should receive a confirmation email for %s to your mailbox at %s. Please make sure you click the button in that email to %s.You've already opted-in to our usage-tracking, which helps us keep improving the %s.You've already opted-in to our usage-tracking, which helps us keep improving them.Your %s Add-on plan was successfully upgraded.Your %s free trial was successfully cancelled.Your %s license was flagged as white-labeled to hide sensitive information from the WP Admin (e.g. your email, license key, prices, billing address & invoices). If you ever wish to revert it back, you can easily do it through your %s. If this was a mistake you can also %s.Your %s license was successfully deactivated.Your WordPress user's: first & last name, and email addressYour account was successfully activated with the %s plan.Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s.Your affiliation account was temporarily suspended.Your email has been successfully verified - you are AWESOME!Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your free trial has expired. You can still continue using all our free features.Your license has been cancelled. If you think it's a mistake, please contact support.Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support.Your license has expired. You can still continue using the free %s forever.Your license was successfully activated.Your license was successfully deactivated, you are back to the %s plan.Your name was successfully updated.Your plan was successfully activated.Your plan was successfully changed to %s.Your plan was successfully upgraded.Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist the following domains:%2$sYour subscription was successfully cancelled. Your %s plan license will expire in %s.Your trial has been successfully started.ZIP / Postal Codea positive responseRight onactivate a license hereactive add-onActiveaddonX cannot run without pluginY%s cannot run without %s.addonX cannot run...%s cannot run without the plugin.advance notice of something that will need attention.Heads upallowas 5 licenses left%s leftas activating pluginActivatingas annual periodyearas application program interfaceAPIas close a windowDismissas code debuggingDebuggingas congratulationsCongratsas connection blockedBlockedas connection was successfulConnectedas download latest versionDownload Latestas download latest versionDownload Latest Free Versionas every monthMonthlyas expiration dateExpirationas file/folder pathPathas in the process of sending an emailSending emailas monthly periodmoas once a yearAnnualas once a yearAnnuallyas once a yearOnceas product pricing planPlanas secret encryption key missingNo Secretas software development kit versionsSDK Versionsas software licenseLicenseas synchronizeSyncas synchronize licenseSync Licenseas the plugin authorAuthoras turned offOffas turned onOnbased on %scall to actionStart free trialclose a windowDismissclose windowDismisscomplete the opt-indatadaysdeactivatingdelegatedo %sNOT%s send me security & feature updates, educational content and offers.e.g. Professional Plan%s Plane.g. billed monthlyBilled %se.g. the best productBestexclamationHeyexclamationOopsgreetingHey %s,hourhoursinstalled add-onInstalledinterjection expressing joy or exuberanceYee-hawlicenselike websitesSitesmillisecondsmsnew Beta versionnew versionnot verifiednounPricenounPricingoptionalproduct versionVersionproductsrevert it nowsecondssecseems like the key you entered doesn't match our records.send me security & feature updates, educational content and offers.skipsomething somebody says when they are thinking about what you have just said.Hmmstart the trialsubscriptionswitchingthe above-mentioned sitesthe latest %s version heretrialtrial periodTrialverbDeleteverbDowngradeverbEditverbHideverbOpt InverbOpt OutverbPurchaseverbShowverbSkipverbUpdateverbUpgradex-ago%s agoProject-Id-Version: WordPress SDK Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues PO-Revision-Date: 2015-10-01 21:38+0000 Last-Translator: linn Yu <977869645@qq.com>, 2023 Language-Team: Chinese (China) (http://app.transifex.com/freemius/wordpress-sdk/language/zh_CN/) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Language: zh_CN Plural-Forms: nplurals=1; plural=0; X-Poedit-Basepath: .. X-Poedit-KeywordsList: get_text_inline;get_text_x_inline:1,2c;$this->get_text_inline;$this->get_text_x_inline:1,2c;$this->_fs->get_text_inline;$this->_fs->get_text_x_inline:1,2c;$this->fs->get_text_inline;$this->fs->get_text_x_inline:1,2c;$fs->get_text_inline;$fs->get_text_x_inline:1,2c;$this->_parent->get_text_inline;$this->_parent->get_text_x_inline:1,2c;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c X-Poedit-SearchPath-0: . X-Poedit-SearchPathExcluded-0: *.js X-Poedit-SourceCharset: UTF-8 %s以访问版本%s的安全和功能更新以及支持。%s 的 %s 下载链接 %s、许可证密钥和安装说明已发送到 %s。如果您在 5 分钟后找不到该电子邮件,请检查您的垃圾邮件箱。已安装%1$s的付费版本。请激活它以开始使用%2$s功能。%3$s公司%s 的%1$s 已进入安全模式,因为我们注意到 %2$s 是 %3$s 的精确副本。%1$s将立即停止所有未来的定期付款,并且您的%2$s计划许可证将在%3$s过期。立即完成”%s“激活已成功购买%s加载项。%s安装%s许可证%s以前%s及其附加组件%s 自动安全和功能更新以及付费功能将继续运行而不会中断,直到 %s(或当您的许可证到期时,无论先到者)。%s客户购买新许可证时收取佣金。%s免费试用已成功取消。因为附加组件是高级的,所以它被自动停用。如果你想在将来使用它,你必须购买许可证。%s是一款仅限高级的附加组件。在激活插件之前,必须先购买许可证。%s 是我客户的电子邮件地址%s 是我的电子邮件地址%s是帐户的新主人。%s最低付款金额。%s选择加入已成功完成。%s或更高%s级%s评级%s秒%s星%s星%s时间%年代%s以访问版本%s的安全和功能更新以及支持。%s获得许可证后激活它。%s首次访问后跟踪Cookie,以最大限度地提高收益潜力。%s的付费功能%s单击此处%s可选择要激活许可证的站点。单击此处了解有关更新 PHP 的更多信息。刚刚向 %s 发送了一封确认电子邮件。电子邮件所有者必须在接下来的 4 小时内确认更新。刚刚向 %s 发送了一封确认电子邮件。您必须在接下来的 4 小时内确认更新。如果您找不到该电子邮件,请检查您的垃圾邮件文件夹。API未知←➤帐户帐户详细信息帐户正在等待激活。请检查您的电子邮件并单击链接激活您的帐户,然后再次提交会员表格。行动激活激活%s激活%s计划激活%s功能激活免费版本激活许可证在所有挂起的站点上激活许可证。在网络中的所有站点上激活许可证。激活此加载项激活%s的加载项模块%s的加载项添加另一个域附加组件附加组件加载项必须部署到WordPress.org或Freemius。网址地址行%d会员推广联盟免费%s后,只需支付%s同意并激活许可证所有请求所有类型允许并继续或者,您可以暂时跳过它,稍后在%s的网络级帐户页中激活许可证。数量从%s自动下载并安装的%s(付费版本)将在%s中启动。如果要手动执行此操作,请单击“取消”按钮。尝试设置用户的测试模式时发生了未知错误。尝试切换许可证的白标模式时发生未知错误。发生未知错误。测试版的更新将用最新的测试版替换您安装的%s版本-请小心使用,不要在生产站点上使用。你被警告了。匿名反馈在所有挂起的站点上应用。应用于网络中的所有站点。申请成为推广联盟%s 和 %s 都是您的电子邮件地址吗?是否确实要删除所有Freemius数据?确定要继续吗?您确定要继续断开连接吗?由于我们保留30天的潜在退款,我们只支付超过30天的佣金。与许可证所有者的帐户关联。自动安装仅适用于已选择的用户。在%s中自动续订自动安装平均评级真棒加入推广联盟测试版本开具账单账单和发票阻止博客ID身体批量捆绑计划企业名称立即购买许可证购买许可证通过更改用户,您同意将帐户所有权转让给:断开网站连接后,之前共享的关于 %1$s 的诊断数据将被删除,%2$s 将不再可见。找不到您的许可证密钥?取消取消%s并继续取消%s-我不再需要任何安全和功能更新,也不需要%s的支持服务,因为我不打算在此或任何其他站点上使用%s。取消%s?取消安装取消订阅取消试用取消正在取消%s正在取消%s...取消订阅取消试用将立即阻止使用所有高级功能。你确定吗?更改许可证变更所有权变更计划更改用户结帐城市清除 API 缓存清除更新瞬态点击这里单击此处匿名使用插件单击此处可查看评分为%s的评论单击此处可查看全尺寸屏幕截图%d产品代码沟通兼容至联系方式联系支持联系我们贡献者无法激活%s。国家Cron类型当前的 %s 和 SDK 版本,以及是否激活或卸载日期停用停用许可证停用或卸载%s将自动禁用许可证,您可以在其他站点上使用该许可证。停用许可证将阻止所有高级功能,但将在其他站点上激活许可证。确定要继续吗?停用调试日志调试模式已成功启用,将在60分钟以内自动禁用。您也可以通过单击“停止调试”链接提前禁用。委派给站点管理员删除所有帐户细节诊断信息诊断数据将不再从 %s 发送到 %s。禁用白标模式断开网站连接将从您的用户仪表板帐户中永久删除 %s。不要取消%s-我仍然对获取安全和功能更新感兴趣,也可以联系支持人员。没有许可证密钥?捐赠给这个插件降低您的计划下载下载%s版本下载付费版本下载最新的%s版本下载最新版本下载由于新的%sEU通用数据保护条例(GDPR)%s的合规性要求,要求您再次明确表示同意,确认您已经加入:-)由于违反我们的推广联盟条款,我们决定暂时封锁您的附属帐户。如果您有任何问题,请联系支持人员。复制网站在更新过程中,我们检测到%d个网站仍在等待许可证激活。在更新过程中,我们在网络中检测到%s个网站仍然需要您的注意。电子邮件电子邮件地址电子邮件地址更新启用白标模式终点请输入邮箱地址请输入您计划升级%s的网站或其他网站的域。输入您在购买时使用的电子邮件地址,我们将重新向您发送许可证密钥。请在下面输入您用于升级的电子邮件地址,我们将重新向您发送许可证密钥。输入新的电子邮件地址错误从服务器收到错误:期满%s后过期扩展额外域您将营销产品的额外的网域。文件过滤为了遵守WordPress.org指南,在开始试用之前,我们要求您选择使用您的用户和非敏感站点信息,允许%s定期向%s发送数据以检查版本更新并验证您的试用。为了提供安全和功能更新以及许可证管理,%s 需要免费的免费试用免费版本Freemius APIFreemius 调试Freemius SDK找不到插件的主文件。请关于当前错误与sdk@freemius.com联系。Freemius 状态Freemius是我们的许可和软件更新引擎全名函数获得自动续订的佣金。获取%s的前沿测试版本的更新。有许可证密钥吗?嘿,你知道%s有会员计划吗?如果您喜欢%s,您可以成为我们的大使并赚取一些现金!主页 URL 和标题、WP 和 PHP 版本以及站点语言到目前为止,您觉得%s怎么样?使用%d天免费试用来测试我们所有的%s高级功能。如何上传和激活?您将如何推广我们?我同意 - 更改用户我再也付不起了我不明白该怎么做我不想和你分享我的信息我找到了更好的%s我已经升级了我的帐户,但是当我尝试同步许可证时,计划仍然是%s。我不再需要%s我只需要在短时间内使用%sID如果这是长期副本,要在 %s 之后保留自动更新和付费功能,请 %s。如果单击它,此决定将委派给站点管理员。如果您没有收到电子邮件,请尝试检查您的垃圾邮件文件夹或搜索来自%4$s。如果您有空,请告诉我们您为什么是%s如果你跳过这个,没关系! %1$s 仍然可以正常工作。如果您希望取消 %1$s 计划的订阅,请导航至 %2$s 并在那里取消。如果要将%s帐户的所有权放弃给%s,请单击“更改所有权”按钮。如果要在这些站点上使用%s,请在下面输入许可证密钥,然后单击“激活”按钮。重要升级通知:在%s中如果您不打算在此站点(或任何其他站点)上使用此%s,是否也要取消%s?立即安装免费版本立即安装免费版本更新立即安装立即安装更新正在安装插件:%s无效的克隆解析操作。模块ID无效。无效的新用户ID或电子邮件地址。无效网站详细信息集合。发票%2$s 是 %4$s 的副本吗?%2$s 是一个新网站吗?%2$s 是 %4$s 的新主页吗?处于活动状态处于激活、已停用或已卸载这是您客户的网站吗?如果您希望从WP管理员那里隐藏敏感信息,例如电子邮件,许可证密钥,价格,账单地址和发票,则为%s。似乎无法激活许可证。许可证停用似乎失败。看起来您不再处于试用模式,所以没有什么需要取消的:)看来你还在%s计划中。如果你确实升级或更改了计划,这可能是我们这边的问题-对不起。您的站点当前似乎没有活动许可证。它需要许可证激活。似乎身份验证参数中的一个不正确。请更新您的公钥、密钥和用户ID,然后重试。这是一个临时的 %s - 我正在解决一个问题这不是我要找的加入测试计划只是让您知道%s的加载项信息是从外部服务器提取的。继续分享保持自动更新钥匙请分享什么地方出了错,以便我们可以为未来的用户修复它...请告诉我们原因以便我们改进。最后上次更新时间最后一个许可证已安装最新的免费版本已安装最新版本了解更多长度许可证许可协议许可证ID许可证密钥许可问题?许可证密钥许可证密钥为空。终身喜欢%s吗?成为我们的大使并赚取现金 ;-)加载数据库选项本机日志记录器长期副本消息方法迁移迁移许可证将选项迁移到网络移动应用程序模块模块路径模块类型有关%s的详细信息姓名名称、别名、版本,以及是否处于激活状态网络博客网络用户绝不会错过重要更新绝不会错过重要更新,在它们成为公众知识之前获得安全警告,并接收有关特别优惠和令人敬畏的新功能的通知。新的新版本可用新网站已安装较新的免费版本(%s)安装了较新的版本(%s)新闻稿下一个不否 - 仅将此站点的数据移动到 %s无 ID%s没有承诺-随时取消%s天没有承诺-随时取消!不需要信用卡永不过期非到期%s的任何计划都不支持试用期。好许可证过期后,您仍然可以使用免费版本,但您将无法使用%s功能。许可证过期后,您将无法再使用%s,除非您使用有效的高级许可证再次激活。选择加入选择退出选择接收有关安全和功能更新、教育内容和临时优惠的电子邮件通知,并共享一些基本的 WordPress 环境信息。选择接收有关安全和功能更新、教育内容和临时优惠的电子邮件通知,并共享一些基本的 WordPress 环境信息。这将帮助我们使 %s 与您的网站更加兼容,并更好地完成您需要它做的事情。选择加入以使“%s”更好!其他所有者电子邮件所有者 ID所有者名称PCI兼容付费附加组件必须部署到Freemius。点击这里许可证密钥安装说明产品的支持电子邮件地址PayPal 帐户电子邮件地址付款付款以美元为单位,每月通过 PayPal 处理。计划计划%s不存在,因此无法启动试用。计划%s不支持试用期。计划ID请在这里联系我们请通过以下信息与我们联系:请下载%s。请输入购买后在电子邮件中收到的许可证密钥:请输入许可证密钥以启用调试模式:请随时提供任何相关的网站或社交媒体统计数据,例如每月独特的网站访问量、电子邮件订户数量、关注者数量等(我们将对这些信息保密)。请按照以下步骤完成升级如果您希望我们与您联系以获取安全和功能更新、教育内容和偶尔的优惠,请告知我们:请注意,取消后,我们将无法获取更新/新订阅的过期定价。如果您选择在未来手动续订,在价格上涨后,将向您收取更新后的价格。请提供有关您打算如何推广%s的详细信息(请尽可能具体)。请提供您的全名。插件插件主页插件ID插件安装变更日志说明常见问题解答功能和定价安装其他注意事项评论插件是一个“服务软件”,这意味着它没有高级代码版本。插件插件和主题同步高级版本高级%s版本已成功激活。已安装高级加载项版本。高级版高级版本已激活。定价隐私政策继续进程 ID处理产品计划摘要推广方式省公钥购买许可证购买更多快速反馈配额重新发送激活电子邮件向新客户介绍我们的%s,并在每次销售成功时获得%s佣金!更新许可证立即续订许可证请求需要 PHP 版本需要WordPress版本复位失活休眠结果软件开发工具包SDK 路径保存%s已保存计划的 Crons屏幕截图按网址搜索密钥从外部域运行的安全HTTPS%s页面似乎关于您的订阅取消发生了一些暂时的问题。请过几分钟再试一次。看来关于您的试用取消发生了一些暂时的问题。请过几分钟再试一次。好像你得到了最新的版本。选择国家发送许可证密钥设置数据库选项与 %s 共享诊断数据有助于提供与您的网站更相关的功能,避免可能破坏您的网站的 WordPress 或 PHP 版本不兼容,并识别插件应该翻译和定制的语言和地区。显示错误详细信息模拟试用推广模拟试用推广单站点许可证站点 ID站点已成功选择加入。站点跳过&%sSlug暂停 & %s因此,当%s不再活动时,您可以重新使用许可证。社交媒体(脸书、推特等)很抱歉给您带来不便,如果您能给我们一个机会,我们将竭诚为您服务。对不起,我们无法完成电子邮件更新。具有相同电子邮件的另一个用户已注册。起点开始调试开始试用开始我的免费%s国家保持联系停止调试提交提交 & %s订阅支持支持论坛从服务器得到的同步数据税务/增值税ID服务条款感谢您申请我们的推广联盟计划,不幸的是,我们现在决定拒绝您的申请。请在30天后再试。感谢您申请我们的推广联盟计划,我们将在未来14天内审查您的详细信息,并将与您联系提供更多的信息。感谢您更新到 %1$s v%2$s!非常感谢您使用%s及其附加组件!非常感谢您使用%s!非常感谢您使用我们的产品!谢谢您!谢谢%s!感谢您确认所有权变更。刚刚向%s发送了一封电子邮件以获得最终批准。谢谢你的升级。谢谢!%1$s 将定期向 %2$s 发送必要的许可数据以检查安全和功能更新,并验证您的许可的有效性。%s破坏了我的网站%s不起作用%s没有按预期工作%s很好,但我需要您不支持的特定功能%s不起作用%s突然停止工作以下产品安装过程已开始,可能需要几分钟才能完成。请等待完成-不要刷新此页。以下产品已进入安全模式,因为我们注意到 %3$s 是 %1$s 的精确副本:%2$s以下产品已进入安全模式,因为我们注意到 %3$s 是这些网站的精确副本:%1$s%2$s远程插件包不包含具有所需段塞的文件夹,重命名失败。%s的升级已成功完成。主题主题切换主题有一个%s的%s可用。有新版本的%s可用。处理您的请求时出现意外的 API 错误。请过几分钟再试,如果还是不行,请联系 %s 的作者并提供以下信息:此插件尚未标记为与您的WordPress版本兼容。此插件尚未用当前版本的WordPress进行测试。此插件需要较新版本的 PHP。这将允许 %s时间戳标题为避免因 WordPress 或 PHP 版本不兼容而破坏您的网站,并识别 %s 应翻译和定制的语言和地区。确保兼容性并避免与您安装的插件和主题发生冲突。要进入调试模式,请输入许可证所有者的密钥(用户ID=%d),您可以在用户仪表板的“我的配置文件”部分找到该密钥:使您能够管理和控制许可证的激活位置,并确保%s安全和功能更新仅发送到您授权的网站。要提供与您的网站相关的其他功能,请避免可能破坏您的网站的 WordPress 或 PHP 版本不兼容,并识别 %s 应翻译和定制的语言和地区。总计城镇审判类型无法连接到文件系统。请确认您的凭据。无限许可证无限更新佣金无上限。最多%s个站点更新更新许可证更新、公告、营销,无垃圾邮件升级上传并激活下载的版本W00t用户仪表盘用户 ID用户密钥用户值验证邮件刚刚发送到%s。如果5分钟后找不到,请检查垃圾邮件框。已验证的验证电子邮件版本%s已发布。查看%s状态查看基本 %s 信息查看基本资料信息查看基本网站信息查看诊断信息查看许可证要点查看插件和主题列表查看详细信息查看付费功能警告我们看不到与该电子邮件地址关联的任何活动许可证,您确定它是正确的地址吗?我们在系统中找不到您的电子邮件地址,您确定地址正确吗?无法加载加载项列表。这可能是我们这边的问题,请几分钟后再来。我们引入了此选项,这样您就不会错过重要的更新,并帮助我们使 %s 与您的站点更加兼容,并更好地完成您需要它做的事情。我们对%s,%s进行了一些调整我们很高兴介绍Freemius网络级集成。网站、电子邮件和社交媒体统计(可选)欢迎使用%s!首先,请输入您的许可证密钥:你期望什么?什么功能?你的%s是什么?你愿意付出什么价钱?你在找什么?%s的名字是什么?您打算在哪里推广%s?WordPress 和 PHP 版本、网站语言和标题WordPress.org 插件页面要将 %s 合并到 %s 中吗?是否继续更新?是是-%s是的 - 两个地址都是我的是 - 将我的所有数据和资产从 %s 移至 %s是的,%%2$s 正在替换 %%4$s。我想将我的 %s 从 %%4$s 迁移到 %%2$s。是的,%2$s 是 %4$s 的副本,用于测试、暂存或开发。是的,%2$s 是一个与 %4$s 不同的新网站。您以前已经试用过。您只需单击一次就可以开始%1$s天的%2$s计划免费试用。你们都很好!您已经在试用模式下运行%s。你离%s只差一步。您仍然可以享受所有%s功能,但您将无法获得%s安全和功能更新以及支持服务。您没有访问高级版本的有效许可证。您有%s许可证。您购买了 %s 许可证。您已成功更新%s。您将此网站 %s 标记为 %s 的临时副本。您将此网站 %s 标记为这些网站的临时副本您可能错过了,但您不必共享任何数据,只需选择%s即可。在接下来的 5 分钟内,您将在 %2$s 的邮箱中收到 %3$s 的 %1$s。您应该会在 %2$s 的邮箱中收到一封 %1$s 的确认邮件。请确保您点击了发送至 %3$s 的电子邮件中的按钮。您应该会在 %s 的邮箱中收到一封 %s 的确认电子邮件。请确保单击该电子邮件中发送给 %s 的按钮。您已经选择加入我们的使用情况跟踪,这有助于我们不断改进%s。您已经选择了我们的使用情况跟踪,这有助于我们不断改进它们。%s加载项计划已成功升级。您的%s免费试用已成功取消。您的%s许可证被标记为白标,以对WP管理员隐藏敏感信息(例如,您的电子邮件,许可证密钥,价格,账单地址和发票)。如果您希望将其还原,则可以通过%s轻松完成。如果这是一个错误,您也可以%s。您的%s许可证已成功停用。您的 WordPress 用户:名字和姓氏,以及电子邮件地址您的帐户已用%s计划成功激活。您的%s会员申请已被接受!登录到您的分支区域,地址为%s。您的会员帐户已暂时挂起。你的电子邮件已被成功验证-你太棒了!您的免费试用已过期。%1$s立即升级%2$s以继续使用%3$s而不中断。您的免费试用已过期。您仍然可以继续使用我们所有的免费功能。你的许可证被取消了。如果您认为这是一个错误,请联系支持人员。您的许可证过期了。%1$s立即升级%2$s以继续使用%3$s而不中断。您的许可证过期了。您仍然可以继续使用所有%s功能,但您需要续订许可证才能继续获取更新和支持。您的许可证过期了。您仍然可以继续永远使用免费的%s。您的许可证已成功激活。您的许可证已成功停用,您回到了%s计划。您的姓名已成功更新。您的计划已成功激活。您的计划已成功更改为%s。您的计划已成功升级。您的服务器阻止访问 Freemius 的 API,这对 %1$s 同步至关重要。请联系您的主机以将以下域列入白名单:%2$s您的订阅已成功取消。您的%s计划许可证将在%s后过期。您的试用已成功开始。邮政编码就在在此处激活许可证活跃的没有%s,%s无法运行。没有插件,%s无法运行。抬头允许剩下%s正在激活年API关闭调试恭喜此路不通已连接下载最新版本下载最新免费版本每月到期路径发送电子邮件瞬间每年每年一次计划没有秘密SDK 版本许可证同步同步许可证作者关闭打开基于%s开始免费试用关闭关闭完成选择加入数据天停用中代表不%s要%s向我发送安全和功能更新、教育内容和服务。%s计划帐单%s最好的嘿哎呀嘿%s,小时小时安装哟哈许可证站点毫秒新测试版新版本未验证价格定价可选的版本产品现在还原秒好像你输入的钥匙和我们的记录不符。请给我发送安全和功能更新,教育内容和优惠。跳过嗯开始试用订阅切换中上述网站这儿有最新的%s版本试用试用删除降级编辑隐藏选择加入选择退出购买显示跳过更新升级%s以前PK!C$$.libraries/freemius/languages/freemius-cs_CZ.monu[,,A-nohSG%  6*_#? c }      @ + !5!9!X!x!!!! !!!!!! ""("/"57"m" u"'"" ""o"K#GR###c$2v$!$$$$$%%#%,%4% 9%G% Y%e%%%% % % %%%Y&]&l& }&&&&&(&1&%':A'|'''' ' '''' '' ( (("(<(R(h(q((( (( ((_(aC))))) ) ) ***O+f+ k+ v+ ++j+ , ,#,,,:@,U{,,,-)!--K-y--'--7->.pG... ... /%/E/ M/&W/1~/./O/2/0b000B0,0 1 1 1+1I1 b1m1t1|1 1 11 111 111112 2 2#2=2,B2o2s2!22 22222 23 33363 <3H3 Q3 \3)j3V33444F45K4(444-44U 56a515~5I6h6o6 66(6*6"617+A7&m777777778 8 88 /8=8L8 e8s88888888 88 859l99&9999: : :: :=*:Lh:f:; ";.;?; E; Q;^; f;t; ;;;><%</<+=)K= u= =\===>C/>s>>>d/?-?? ??'?@ @$@*@@@@@E@'A:ALA[AbA*qAA*A^A.B=BEBKBdQBB BB BBCC6CKCcC ~CCCWC"CB!D6dD=DD DD- E;EYE/oEE*EEE$EF>6FuF F&F9F<GbKGPGUG_UHHKPI(IGI# J%1J)WJ$JUJ)J&K;8K6tKKKKK$L'LAL]LyL&L*L7L!M8MVM3oMMMMMM*N1BNtNN#NNNN OO)O >OKONTOOOOO PP1-P_PgP{PP P P P PP PCP5QQ:QQ Q QQQQ QQ R R R "R /R =R GR QR ]R jR;wRXWe XerXX,X !Y /Y;YDY4WYYbfZZ!Z [ [ ([6[ ;[ F[P[Y[Wb[6[[[[\\\ "\ ,\9\L\`\{\\ \\\\D\"])] 1]R] d]r]{]^O ^]^x^-_.F_u___ __ __ ``` $`1`E`*T`````` ` ``ta}aa aaaa"a8a;0b=lbbbbbbbbccc !c-c Ac LcYcac|cc cc ccdd d2d^8deddee 4e>eMe]ede%jeWfff g g!gj0g g ggg%gmgjh0h$hh%hi5i!Jili.oiSiwijjpj jjjj/jk k.k!Kk+mkBk&kll3l]:lGl llm)m"uu*Xv vvv vvvv vAvy@www 2x %*Pa0t!Ń,!9:t"/ʄ61_<gQ+F>-+5߈*Q@2ʼn$ &1DHL S ] i v, ʊ֊ߊ  %5 JT\k Ë]͋+4 C NY_nt| ÌWŌ )- F S_ zǍ ٍ   %s to access version %s security & feature updates, and support. The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s.%s - plugin name. As complete "PluginX" activation nowComplete "%s" Activation Now%s Add-on was successfully purchased.%s Installs%s Licenses%s ago%s and its add-ons%s commission when a customer purchases a new license.%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license.%s is a premium only add-on. You have to purchase a license first before activating the plugin.%s is the new owner of the account.%s minimum payout amount.%s or higher%s rating%s ratings%s sec%s star%s stars%s time%s times%s to access version %s security & feature updates, and support.%s to activate the license once you get it.APIASCII arrow left icon←ASCII arrow right icon➤AccountAccount DetailsActionsActivateActivate %sActivate %s PlanActivate %s featuresActivate Free VersionActivate LicenseActivate this add-onActivatedAdd Ons for %sAdd-OnAdd-OnsAdd-on must be deployed to WordPress.org or Freemius.AddressAffiliateAfter your free %s, pay as little as %sAgree & Activate LicenseAll TypesAllow & ContinueAlternatively, you can skip it for now and activate the license later, in your %s's network-level Account page.AmountAn unknown error has occurred while trying to set the user's beta mode.An unknown error has occurred.An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned.Anonymous feedbackAre you sure you want to delete all Freemius data?Are you sure you want to proceed?Auto renews in %sAverage RatingAwesomeBecome an affiliateBillingBilling & InvoicesBlockingBlog IDBodyBusiness nameBuy a license nowBuy licenseCan't find your license key?CancelCancel %s & ProceedCancel SubscriptionCancel TrialCancelledCancelling %sCancelling %s...Cancelling the subscriptionCancelling the trial will immediately block access to all premium features. Are you sure?Change LicenseChange OwnershipChange PlanCheckoutCityClear API CacheClear Updates TransientsClick here to use the plugin anonymouslyClick to see reviews that provided a rating of %sClick to view full-size screenshot %dClone resolution admin notice products list labelProductsCodeCompatible up toContactContact SupportContact UsContributorsCouldn't activate %s.CountryDateDeactivateDeactivate LicenseDeactivationDebug LogDetailsDon't have a license key?Donate to this pluginDowngrading your planDownloadDownload %s VersionDownload the latest %s versionDownload the latest versionDownloadedEmailEmail addressEndEnter the email address you've used during the purchase and we will resend you the license key.Enter the email address you've used for the upgrade below and we will resend you the license key.Enter the new email addressErrorError received from the server:ExpiredExpires in %sExtra DomainsFileFilterFor compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.For delivery of security & feature updates, and license management, %s needs toFreeFree TrialFree versionFreemius APIFreemius DebugFreemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error.Freemius StateFull nameFunctionHave a license key?Homepage URL & title, WP & PHP versions, and site languageHow do you like %s so far? Test all our %s premium features with a %d-day free trial.How to upload and activate?How will you promote us?I can't pay for it anymoreI couldn't understand how to make it workI don't like to share my information with youI found a better %sI no longer need the %sI only needed the %s for a short periodIDIf you have a moment, please let us know why you are %sIf you skip this, that's okay! %1$s will still work just fine.If you'd like to use the %s on those sites, please enter your license key below and click the activation button.In %sInstall Free Version NowInstall NowInstall Update NowInstalling plugin: %sInvalid module ID.Invalid new user ID or email address.InvoiceIs ActiveIs active, deactivated, or uninstalledIt looks like the license could not be activated.It looks like the license deactivation failed.It looks like you are not in trial mode anymore so there's nothing to cancel :)It's a temporary %s - I'm troubleshooting an issueIt's not what I was looking forJoin the Beta programKeyKindly share what didn't work so we can fix it for future users...Kindly tell us the reason so we can improve.LastLast UpdatedLast licenseLatest Free Version InstalledLatest Version InstalledLearn moreLengthLicenseLicense AgreementLicense IDLicense KeyLicense issues?License keyLicense key is empty.LifetimeLocalhostLogLoggerMessageMethodModuleModule PathModule TypeMore information about %sNameNames, slugs, versions, and if active or notNewNew Version AvailableNewer Free Version (%s) InstalledNewer Version (%s) InstalledNewsletterNextNoNo IDNo credit card requiredNo expirationO.KOpt InOpt OutOpt in to make "%s" better!OtherOwner EmailOwner IDOwner NamePCI compliantPaid add-on must be deployed to Freemius.Part of the message telling the user what they should receive via email.a license keyPayPal account email addressPaymentsPayouts are in USD and processed monthly via PayPal.PlanPlan %s do not exist, therefore, can't start a trial.Plan %s does not support a trial period.Plan IDPlease contact us herePlease contact us with the following message:Please download %s.Please enter the license key that you received in the email right after the purchase:Please enter the license key to enable the debug mode:Please follow these steps to complete the upgradePlease let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:Please provide your full name.PluginPlugin HomepagePlugin IDPlugin InstallPlugin installer section titleChangelogPlugin installer section titleDescriptionPlugin installer section titleFAQPlugin installer section titleFeatures & PricingPlugin installer section titleInstallationPlugin installer section titleReviewsPluginsPlugins & Themes SyncPremiumPremium versionPremium version already active.Privacy PolicyProceedProductsProvincePublic KeyPurchase LicensePurchase MoreQuick FeedbackRe-send activation emailRenew licenseRenew your license nowRequestsRequires WordPress VersionResultSDKSDK PathSave %sScheduled CronsScreenshotsSearch by addressSecret KeySecure HTTPS %s page, running from an external domainSeems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.Seems like you got the latest release.Select CountrySend License KeySingle Site LicenseSite IDSitesSkip & %sSlugSnooze & %sSo you can reuse the license when the %s is no longer active.Sorry for the inconvenience and we are here to help if you give us a chance.Sorry, we could not complete the email update. Another user with the same email is already registered.StartStart TrialStart my free %sStateSubmit & %sSubscriptionSupportSupport ForumSync Data From ServerTax / VAT IDTerms of ServiceThank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days.Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information.Thank you for updating to %1$s v%2$s!Thank you so much for using %s and its add-ons!Thank you so much for using %s!Thank you so much for using our products!Thank you!Thanks %s!Thanks for confirming the ownership change. An email was just sent to %s for final approval.The %s broke my siteThe %s didn't workThe %s didn't work as expectedThe %s is great, but I need specific feature that you don't supportThe %s is not workingThe %s suddenly stopped workingThe installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page.The remote plugin package does not contain a folder with the desired slug and renaming did not work.The upgrade of %s was successfully completed.ThemeTheme SwitchThemesThere is a new version of %s available.This will allow %s toTimestampTitleTo enter the debug mode, please enter the secret key of the license owner (UserID = %d), which you can find in your "My Profile" section of your User Dashboard:TotalTownTrialTypeUnable to connect to the filesystem. Please confirm your credentials.Unlimited LicensesUnlimited UpdatesUp to %s SitesUpdateUpdate LicenseUpdates, announcements, marketing, no spamUpgradeUpload and activate the downloaded versionUsed to express elation, enthusiasm, or triumph (especially in electronic communication).W00tUser DashboardUser IDUsersValueVerification mail was just sent to %s. If you can't find it after 5 min, please check your spam box.VerifiedVerify EmailVersion %s was released.View %s StateView Basic %s InfoView Basic Profile InfoView Basic Website InfoView Diagnostic InfoView License EssentialsView Plugins & Themes ListView detailsView paid featuresWarningWe couldn't find your email address in the system, are you sure it's the right address?We made a few tweaks to the %s, %sWe're excited to introduce the Freemius network-level integration.Website, email, and social media statistics (optional)Welcome to %s! To get started, please enter your license key:What did you expect?What feature?What is your %s?What price would you feel comfortable paying?What you've been looking for?What's the %s's name?WordPress & PHP versions, site language & titleWordPress.org Plugin PageWould you like to proceed with the update?YesYes - %sYou already utilized a trial before.You are just one step away - %sYou do not have a valid license to access the premium version.You have a %s license.You have purchased a %s license.You have successfully updated your %s.Your account was successfully activated with the %s plan.Your email has been successfully verified - you are AWESOME!Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your free trial has expired. You can still continue using all our free features.Your license has been cancelled. If you think it's a mistake, please contact support.Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support.Your license has expired. You can still continue using the free %s forever.Your license was successfully activated.Your license was successfully deactivated, you are back to the %s plan.Your name was successfully updated.Your plan was successfully activated.Your plan was successfully changed to %s.Your plan was successfully upgraded.Your subscription was successfully cancelled. Your %s plan license will expire in %s.Your trial has been successfully started.ZIP / Postal CodeaddonX cannot run without pluginY%s cannot run without %s.addonX cannot run...%s cannot run without the plugin.allowas 5 licenses left%s leftas activating pluginActivatingas annual periodyearas application program interfaceAPIas close a windowDismissas code debuggingDebuggingas congratulationsCongratsas connection blockedBlockedas connection was successfulConnectedas download latest versionDownload Latestas download latest versionDownload Latest Free Versionas every monthMonthlyas expiration dateExpirationas file/folder pathPathas in the process of sending an emailSending emailas monthly periodmoas once a yearAnnualas once a yearAnnuallyas once a yearOnceas product pricing planPlanas secret encryption key missingNo Secretas software development kit versionsSDK Versionsas software licenseLicenseas synchronizeSyncas synchronize licenseSync Licenseas the plugin authorAuthoras turned offOffas turned onOnbased on %sclose a windowDismissclose windowDismissdeactivatingdelegatedo %sNOT%s send me security & feature updates, educational content and offers.e.g. Professional Plan%s Plane.g. billed monthlyBilled %se.g. the best productBestexclamationHeyexclamationOopsgreetingHey %s,interjection expressing joy or exuberanceYee-hawlicenselike websitesSitesmillisecondsmsnew Beta versionnew versionnot verifiednounPricenounPricingproduct versionVersionsecondssecsend me security & feature updates, educational content and offers.skipsomething somebody says when they are thinking about what you have just said.Hmmstart the trialsubscriptionswitchingthe latest %s version heretrialtrial periodTrialverbDeleteverbDowngradeverbEditverbHideverbOpt InverbOpt OutverbPurchaseverbShowverbSkipverbUpdateverbUpgradex-ago%s agoProject-Id-Version: WordPress SDK Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues PO-Revision-Date: 2015-10-01 21:38+0000 Last-Translator: Karolína Vyskočilová , 2019-2023 Language-Team: Czech (Czech Republic) (http://app.transifex.com/freemius/wordpress-sdk/language/cs_CZ/) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Language: cs_CZ Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3; X-Poedit-Basepath: .. X-Poedit-KeywordsList: get_text_inline;get_text_x_inline:1,2c;$this->get_text_inline;$this->get_text_x_inline:1,2c;$this->_fs->get_text_inline;$this->_fs->get_text_x_inline:1,2c;$this->fs->get_text_inline;$this->fs->get_text_x_inline:1,2c;$fs->get_text_inline;$fs->get_text_x_inline:1,2c;$this->_parent->get_text_inline;$this->_parent->get_text_x_inline:1,2c;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c X-Poedit-SearchPath-0: . X-Poedit-SearchPathExcluded-0: *.js X-Poedit-SourceCharset: UTF-8 %s pro přístup k verzi %s zajišťující podporu a nejen bezpečnostní aktualizace. Placená verze %1$s je již nainstalována. Aktivujte jí, abyste mohli těžit z %2$s funkcí. %3$s%1s okamžitě zastaví všechny budoucí opakující se platby a licence k plánu %s vyprší za %s.Dokončit aktivaci „%s“Rozšíření %s bylo úspěšně zakoupeno.%s instalací%s licencíPřed %s%s a jeho doplňky%s provizi, když zákazník zakoupí novou licenci.%s bezplatná zkušební verze byla úspěšně zrušena. Jelikož toto rozšíření nenabízí bezplatnou verzi, bylo automaticky deaktivováno. Chcete-li jej v budoucnu používat, budete si muset zakoupit licenci.%s je pouze prémiové rozšíření. Před aktivací pluginu si musíte nejprve zakoupit licenci.%s je nový vlastník účtu.%s minimální částka výplaty.%s nebo vyšší%s hodnocení%s hodnocení%s s%s hvězda%s hvězd%s krát%s krát%s pro přístup k verzi %s zajišťující podporu a nejen bezpečnostní aktualizace.%s, abyste mohli aktivovat licenci, až ji obdržíte.API←➤ÚčetDetaily účtuAkceAktivovatAktivovat %sAktivovat %s plánAktivovat %s funkceAktivovat bezplatnou verziAktivovat licenciAktivovat toto rozšířeníAktivovanýRozšíření pro %sDoplněkDoplňkyRozšíření musí být nasazeno na WordPress.org nebo na Freemius.AdresaPartnerPo bezplatné %s platit jen v %sAktivovat licenciVšechny typyPovolit a pokračovatPřípadně můžete aktivaci licence prozatím přeskočit a provést ji později na stránce účtu na úrovni sítě %s.ČástkaBěhem nastavování uživatelského beta módu došlo k neočekávané chybě.Došlo k neznámé chybě.Aktualizováním na Beta verzi nahradíte nainstalovanou verzi %s nejnovějším vydáním Beta verze - používejte s opatrností a ne na produkčních webech. Varovali jsme vás.Anonymní zpětná vazbaOpravdu chcete smazat veškerá Freemius data?Opravdu chcete pokračovat?Automaticky se obnoví za %sPrůměrné hodnoceníÚžasnéStaňte se naším afiliátemFakturaceFakturace a platbyBlokováníBlog IDTěloJméno firmyKoupit licenci teďKoupit licenciNemůžete najít svůj licenční klíč?ZrušitZrušit %s > pokračovatZrušit předplatnéZrušit zkušební verziZrušenaRuším %sRuším %s...Ruším předplatnéZrušení zkušební verze okamžitě zablokuje přístup ke všem prémiovým funkcím. Opravdu chcete pokračovat?Změnit licenciZměnit vlastnictvíZměnit plánPokladnaMěstoVyčistit API cacheVyčistit transitenty aktualizacíKlikněte zde pro anonymní používání tohoto pluginuKliknutím zobrazíte recenze, které stojí za hodnocenímKlikněte pro zobrazení plné velikosti snímku obrazovky %dProduktyKódKompatibilní až poKontaktKontaktovat podporuSupportPřispěvateléNelze aktivovat %s.ZeměDatumDeaktivovatDeaktivovat licenciDeaktivaceLadící logDetailyNemáte licenční klíč?Přispějte na tento pluginSnižuji vaše předplatnéStáhnoutStáhnout verzi %sStáhnout nejnovější verzi %sStáhnout nejnovější verziStaženoE-mailE-mailová adresaKonecZadejte e-mail, který jste použili při nákupu, a my vám znovu zašleme licenční klíč.Zadejte e-mail použitý při objednávce, abychom vám na něj mohli znovu poslat licenční klíč.Zadejte nový e-mailChybaChyba přijatá ze serveru:VypršeloVyprší za %sDalší doménySouborFiltrAby bylo vyhověno WordPress.org pokynům, před zahájením zkušebního období vás žádáme, abyste s námi sdíleli data o uživateli a necitlivé informace o webu, to umožní %s periodicky odesílat data do %s za účelem kontroly aktualizací a ověření nároku na zkušební verzi.Pro poskytování aktualizací zabezpečení a funkcí a správu licencí %s potřebujeZdarmaZkušební verze zdarmaVerze zdarmaFreemius APIFreemius DebugFreemius SDK nemohl najít hlavní soubor pluginu. S aktuální chybou se obraťte se na sdk@freemius.com.Stav FreemiusCelé jménoFunkceMáte licenční klíč?URL webu, verze WP a PHP a jazyk webuJak se vám líbí %s? Otestujte všechny naše %s nadstandardní funkce s %d-denní zkušební verze zdarma.Jak nahrát a aktivovat?Jakým způsobem budete mé produkty propagovat?je drahý a nemohu si ho už dovolitNedokázal jsem jej zprovoznitNechci s vámi sdílet své informacenašel jsem lepší %sjiž nepotřebuji %spotřeboval jsem %s jen dočasněIDMáte-li chvilku, dejte nám vědět, proč %sPokud tuto část přeskočíte, nevadí! %1$s bude stále fungovat bez problémů.Pokud chcete použít %s na těchto stránkách, zadejte platný licenční klíč a klikněte na tlačítko aktivovat.Za %sNainstalovat verzi zdarmaInstalovatNainstalovat aktualizaciInstaluji plugin: %sNeplatné ID modulu.Neplatné ID uživatele nebo e-mailová adresa.FakturaJe aktivníJe aktivní, deaktivovaný nebo odinstalovanýLicenci se nepodařilo aktivovat.Deaktivace licence pravděpodobně selhala.Zkušební režim už vám skončil, takže už není co rušit :)dočasná %s - ladím problém na webuNení to to, co jsem hledalOdebírat betaverzeKlíčDejte nám prosím vědět, co nefungovalo, ať to můžeme opravit pro další uživatele...Dejte nám prosím vědět z jakého důvodu, ať to můžeme zlepšit.PosledníPoslední aktualizacePoslední licenceNainstalována nejnovější verze zdarmaNainstalována nejnovější verzePřečtěte si víceDélkaLicenceLicenční smlouvaLicenční IDLicenční klíčProblémy s licencí?Licenční klíčLicenční klíč je prázdný.DoživotníLocalhostZáznamLoggerZprávaMetodaModulCesta k moduluTyp moduluVíce informací o %sJménoJména, slugy, verze a jestli jsou nebo nejsou aktivníNovýNová verze k dispoziciNovější verze zdarma (%s) nainstalovánaNovější verze (%s) nainstalovánaNewsletterNásledujícíNeŽádné IDKreditní karta není vyžadovánaBez vypršeníOKOdebírat novinkyOdhlásit seZúčastněte se, aby byl "%s" ještě lepší!jinéE-mail vlastníkaID vlastníkaJméno vlastníkaKompatibilní s PCIPlacený doplněk musí být nasazen na Freemius.licenční klíčE-mailová adresa účtu PayPalPlatbyVyplácení probíhá přes PayPal jednou za měsíc a v USD.Druh členstvíPlán %s neexistuje, proto nemůžete použít zkušební verzi.Plán %s nepodporuje zkušební období.ID členstvíKontaktujte nás prosím zdeKontaktujte nás prosím s následující zprávou:Stáhněte si prosím %s.Zadejte licenční klíč, který najdete v e-mailu odeslaném po provedení objednávky:Pro povolení módu ladění chyb zadejte licenční klíč.Dokončete upgrade provedením následujících krokůVyberte si, jestli chcete být informování o bezpečnostních aktualizacích, vylepšení funkcionality, vzdělávacímu obsahu nebo občasných obchodních nabídkách:Zadejte prosím jméno a příjmení.PluginHlavní stránka pluginuID pluginuInstalace pluginuHistorie změnPopisFAQVlastnosti a ceníkInstalaceVaše hodnoceníPluginyPluginy a synchronizace šablonPrémiumPrémiová verzePrémiová verze je již aktivní.Zásady ochrany osobních údajůPokračovatProduktyOkresVeřejný klíčKoupit licenciZakoupit dalšíRychlá zpětná vazbaZnovu poslat aktivační e-mailObnovit licenciObnovte svou licenci teďŽádostiVyžaduje verzi WordPressVýsledekSDKCesta l SDKUložit %sPlánované cronySnímky obrazovkyHledat podle adresyTajný klíčZabezpečená stránka HTTPS %s spuštěná z externí doményOmlouváme se, ale měli jsme nějaký dočasný problém se zrušením vaší zkušební licence. Zkuste to znovu za několik minut.Pravděpodobně máte nejnovější verzi.Vyberte zemiOdeslat licenční klíčLicence pro jednu instalaciID stránkyWebyPřeskočit & %sZkratkaOdložit & %sLicenci tak můžete znovu použít, když už %s není aktivní.Omlouváme se za způsobené nepříjemnosti, ale když se nám dáte šanci, tak se vám ze všech sil pokusíme pomoci.Omlouváme se, ale aktualizaci e-mailu jsem nemohli dokončit. Uživatel s vámi zadaným e-mailem už je registrován.ZačátekZačít zkušební verziZačít můj bezplatný %sKrajOdeslat & %sPředplatnéPodporaFórum podporySynchronizovat data ze serveruDIČPodmínky službyDěkujeme, že jste se přihlásili do našeho partnerského programu, ale bohužel jsme se v tuto chvíli rozhodli vaši žádost zamítnout. Zkuste to prosím znovu za 30 dní.Děkujeme, že jste se přihlásili do našeho partnerského programu, během následujících 14 dnů prověříme vaše údaje a ozveme se vám s dalšími informacemi.Děkujeme za aktualizaci na verzi %1$s v%2$s!Moc vám děkujeme za používání %s a jeho doplňků!Moc vám děkujeme za používání %s!Moc vám děkujeme za používání našich produktů!Děkujeme!Děkujeme %s!Děkujeme za potvrzení změny vlastnictví. E-mail byl právě odeslán na adresu %s, ke konečnému schválení.%s rozbil můj web%s nefungoval%s nefungoval podle očekávání%s je skvělý, ale potřebuji funkci, kterou není podporovaná%s nefunguje%s náhle přestal pracovatProces instalace byl zahájen a může trvat několik minut. Počkejte prosím na dokončení - neobnovujte tuto stránku.Balíček remote pluginů neobsahuje složku s žádoucím "slug" a přejmenování nefunguje.Aktualizace %s byla úspěšně dokončena.ŠablonaZměna šablonyŠablonyJe k dispozici nová verze %s.To dovolí %s Datum a časNadpisPro zapnutí módu ladění chyb zadejte tajný klíč vlastníka licence (uživatel s ID = %d), který najdete v sekci "Můj profil" vaší nástěnky.CelkemMěstoZkouškaTypNelze se připojit k souborovému systému. Potvrďte prosím své přístupové údaje.Neomezené množství instalacíNeomezené aktualizaceAž pro %s webůAktualizovatAktualizovat licenciAktualizace, oznámení, marketing, žádný spamUpgradeNahrát a aktivovat stáhnutou verziCože?Uživatelská nástěnkaID uživateleUživateléHodnotaOvěřovací zpráva byla právě odeslána na e-mail %s. Pokud ji nenajdete do 5 min, zkontrolujte prosím složku pro spam.OvěřenoOvěřit e-mailByla vydána verze %s.Zobrazit %s stavZobrazit základní informace o %sZobrazit základní informace o profiluZobrazit základní informace o stránceZobrazit diagnostické informaceZobrazit základní informace k licenciZobrazit seznam pluginů a šablonZobrazit podrobnostiZobrazit placené funkceVarováníNemohli jsme najít vaši e-mailovou adresu v systému, jste si jisti, že je to správná adresa?Udělali jsme několik vylepšení %s, %sJsme rádi, že vám můžeme ukázat integraci Freemiusu i v rámci sítě webů.Statistika o webová stránc, emaiul a sociálních médiích%s vás vítá! Pro začátek zadejte svůj licenční klíč:Co jste očekávali?Jaká funkce?Jaké je vaše "%s"?Jakou cenu byste byli ochotni platit?Co jste hledali?Jak se %s jmenuje?Cerzi WordPressu a PHP, jazyk webu a jeho jménoNázev pluginu na WordPress.orgChcete pokračovat v aktualizaci?AnoAno - %sZkušební verzi jste již dříve využili.Jste jen na krok od - %sNemáte platnou licenci pro přístup k prémiové verzi.Máte licenci „%s“.Zakoupili jste licenci %s.Úspěšně jste aktualizovali %s.Účet byl úspěšně aktivován s %s plánem.Váš e-mail byl úspěšně ověřen - jste skvělý!Platnost bezplatné zkušební verze vypršela. %1$s Upgradujte nyní%2$s abyste mohli pokračovat v používání %3$s bez přerušení.Platnost bezplatné zkušební verze vypršela. Stále můžete pokračovat v používání všech našich bezplatných funkcí.Vaše licence byla zrušena. Pokud si myslíte, že je to chyba, obraťte se na naší podporu.Vaše licence vypršela. %1$sObnovte předplatné%2$s, abyste mohli mohli %3$s používat bez omezení.Vaše licence vypršela. Stále však můžete používat všechny funkce verze %s, ale pro získání technické podpory a nejnovějších aktualizací budete muset obnovit svou licenci.Vaše licence vypršela. Stále však můžete používat %s zdarma bez omezení.Vaše licence byla úspěšně aktivována.Vaše licence byla úspěšně deaktivována, jste zpět na plánu %s.Vaše jméno bylo úspěšně aktualizováno.Vaše licence byla úspěšně aktivována.Váše předplatné bylo úspěšně změněno na %s.Váš plán byl úspěšně aktualizován.Vaše předplatné bylo úspěšně zrušeno. Platnost licence %s vyprší za %s.Vaše zkušebí verze byla úspěšně spuštěna.PSČ / směrovací číslo%s nelze spustit bez %s.%s nelze spustit bez tohoto pluginu.povolitZbývá %sProbíhá aktivacerokAPISkrýtDebuggingGratulujemeZablokovánoPřipojenoStáhněte si nejnovějšíStáhněte si nejnovější bezplatnou verziMěsíčněExpiraceSložkaProbíhá odesílání e-mailůpoRočněRočněJedenkrátDruh členstvíTajný klíč chybíSDK verzeLicenceSynchronizovatSynchronizovat licenceAutorVypnutoZapnutozaloženo na %sSkrýtSkrýtdeaktivujetedelegovat%sneposílejte%s mi bezpečnostní aktualizace a vylepšení, vzdělávací obsah a nabídky.%s plánÚčtováno %sNejlepšíDobrý denJejdaDobrý den %s,HurálicenceWebymsnová Beta verzenová verzenení ověřenoCenaCeníkVerzesposílejte mi bezpečnostní aktualizace a vylepšení, vzdělávací obsah a nabídky.přeskočitHmmspustit zkušební verzipředplatnépřepínámnejnovější %s verze zdezkušebníZkušební verzeSmazatPřejít na nižší verziUpravitSkrýtOdebírat novinkyOdhlásit seZakoupitZobrazitPřeskočitAktualizovatVylepšitPřed %sPK!p2WW&libraries/freemius/languages/index.phpnu[9aE9+909::-:<:D:X:]:e:x:::: : :: :E:y;;;;; X<c<w< < < <<<Y<7=F= W= c=o=x=}== =(=1=% >:2>m> r>>>> > >>> >7>!? &?1?xD?? R@ _@i@@A*A2A5BAxAXAtAaB{BBBBBBB C CCrD[DfDGE ME[EpEEEYE_Ea\FFFFG G G !G;/GkGpGwGOvHH H H HHjHjI5yI II3I2I)J~=J:JUJMKiKKK)K-K LSLsL'LLgLMMZlM7M>Mm>NgNpOOOyOP8P XPdPwP PP%P PQQ-QDQ bQ&lQQ1)R.[RORRAZSSyS26TiTTaT UU%UB)U,lUU U UUU UUV V V &V2V BVNVdV4mVV VVVVVVVVW W(W /W ;WGWaW,fW W WWWnXrX X!XX XXX%X Y%Y+8YdY |Y Y/YYmY~9ZZZZi[q\\ \\ \ \)\.\V]fq]|]U^r^4{^^5^(^__-3_a_Uu_6_`1`~`|a[bbcc +c5c(Dc*mc"c1c+c*d&DdNkdddd.d)e9eIeieqee e eeeee ee ee ffW*f fffffffggggg .g:g Lg5Wgsglh&nhhh h hiiij(j0jLj Rj\j aj=mj&jLjfkk k kkkk kk k kk k l !l.l?ll%lm/mm)m n n\"nnnn6oKo^oC}oooopmpxqd|q-qr r"r)r'HrprMsGks,ss sttStuu4vw"w'w-wE2wxwwwwww*wx* x^7xxxxxxdx'y 0y=y Vydywyyyyy yyzizWznzH{"{B|6S|=|| ||-|*}H}&^}/}}#}*}~"~+~/I~Qy~U~D!$fM/o;> &"<IDZˁO&vT~RӃ.&.U-;ą9Z:3<ɆbPiU_pK (WG#ȉ%)$<aU)Xɋ;ދ6>Qь$ &B^&|*7΍;3Tˎߎ*1'Yu#ɏۏ .CW\ anNwƐ.?PU[1vđԑ 7 @ N9ZCؒQݒ/ ? LVp  ɓ ӓ ߓ    '4]Xvx'%  4˜nǝ'6^$z*  *1\8=Qӟ%[@ci mw L U `m3ˣ22 JTdyF  4L j x eئh>'ϧ)%'+ ?7*w2vթ-LGz"ª )8 =H `k s}  Wī|/ ɬӬ ̭  o8 ̮ٮ $ 2+?Jk7  $8 @Lhm?v ɰݰp ( 6@ .66L"Z ڴ $,!QsL hr .OTaloθ{>ڹ   'D5z%` / BO^G;OBX4нI{hž).X$s34!!kC8I YShBQYut!;A%1':T"o0"$@ _)iAHB\*D%"H2) 5tQ f<g ) %, 3 AK\l} D   ,6 >H'Y  / (B * K kv41:/N_5p|&R#  ". AEN ' OoDt)29\QE;$S])o    R&y/+ *0 F Q \ir  q,(, 5 BN ]iMm2(8PEa   5?R VJb(m~D  2; KV e s 4=2r - z "Lo*U*(Sos 3 +1SPb4 p[ ag{-8 DI \h{ .L%_)+#=Miqlh0L9EH6=Uq;&$-08CWMPE:=i(;'RzF R j*DL_HXfQ~17<iM.=#9ae2:4yoqd[i*[)%GO)&-'>O .^      %     ( 7 <  @  J V  n  x  (            ! / 6 ; Q W [ ^ !o        V : B V \ b  i s z                d L~       (DJO X b mx  %s to access version %s security & feature updates, and support. The %s's %sdownload link%s, license key, and installation instructions have been sent to %s. If you can't find the email after 5 min, please check your spam box. The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s"The ", e.g.: "The plugin"The %s's%1$s has been placed into safe mode because we noticed that %2$s is an exact copy of %3$s.%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s.%s - plugin name. As complete "PluginX" activation nowComplete "%s" Activation Now%s Add-on was successfully purchased.%s Installs%s Licenses%s ago%s and its add-ons%s automatic security & feature updates and paid functionality will keep working without interruptions until %s (or when your license expires, whatever comes first).%s commission when a customer purchases a new license.%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license.%s is a premium only add-on. You have to purchase a license first before activating the plugin.%s is my client's email address%s is my email address%s is the new owner of the account.%s minimum payout amount.%s opt-in was successfully completed.%s or higher%s rating%s ratings%s sec%s star%s stars%s time%s times%s to access version %s security & feature updates, and support.%s to activate the license once you get it.%s tracking cookie after the first visit to maximize earnings potential.%s's paid features%sClick here%s to choose the sites where you'd like to activate the license on.Click here to learn more about updating PHP.A confirmation email was just sent to %s. The email owner must confirm the update within the next 4 hours.A confirmation email was just sent to %s. You must confirm the update within the next 4 hours. If you cannot find the email, please check your spam folder.APIAPI connectivity state is unknownUnknownASCII arrow left icon←ASCII arrow right icon➤AccountAccount DetailsAccount is pending activation. Please check your email and click the link to activate your account and then submit the affiliate form again.ActionsActivateActivate %sActivate %s PlanActivate %s featuresActivate Free VersionActivate LicenseActivate license on all pending sites.Activate license on all sites in the network.Activate this add-onActivatedAdd Ons for %sAdd Ons of module %sAdd another domainAdd-OnAdd-OnsAdd-on must be deployed to WordPress.org or Freemius.AddressAddress Line %dAffiliateAffiliationAfter your free %s, pay as little as %sAgree & Activate LicenseAll RequestsAll TypesAllow & ContinueAlternatively, you can skip it for now and activate the license later, in your %s's network-level Account page.AmountAn automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now.An unknown error has occurred while trying to set the user's beta mode.An unknown error has occurred while trying to toggle the license's white-label mode.An unknown error has occurred.An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned.Anonymous feedbackApply on all pending sites.Apply on all sites in the network.Apply to become an affiliateAre both %s and %s your email addresses?Are you sure you want to delete all Freemius data?Are you sure you want to proceed?Are you sure you would like to proceed with the disconnection?As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days.Associate with the license owner's account.Auto installation only works for opted-in users.Auto renews in %sAutomatic InstallationAverage RatingAwesomeBecome an affiliateBetaBillingBilling & InvoicesBlockingBlog IDBodyBundleBundle PlanBusiness nameBuy a license nowBuy licenseBy changing the user, you agree to transfer the account ownership to:By disconnecting the website, previously shared diagnostic data about %1$s will be deleted and no longer visible to %2$s.Can't find your license key?CancelCancel %s & ProceedCancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site.Cancel %s?Cancel InstallationCancel SubscriptionCancel TrialCancelledCancelling %sCancelling %s...Cancelling the subscriptionCancelling the trial will immediately block access to all premium features. Are you sure?Change LicenseChange OwnershipChange PlanChange UserCheckoutCityClear API CacheClear Updates TransientsClick hereClick here to use the plugin anonymouslyClick to see reviews that provided a rating of %sClick to view full-size screenshot %dClone resolution admin notice products list labelProductsCodeCommunicationCompatible up toContactContact SupportContact UsContributorsCouldn't activate %s.CountryCron TypeCurrent %s & SDK versions, and if active or uninstalledDateDeactivateDeactivate LicenseDeactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site.Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?DeactivationDebug LogDebug mode was successfully enabled and will be automatically disabled in 60 min. You can also disable it earlier by clicking the "Stop Debug" link.Delegate to Site AdminsDelete All AccountsDetailsDiagnostic InfoDiagnostic data will no longer be sent from %s to %s.Disabling white-label modeDisconnecting the website will permanently remove %s from your User Dashboard's account.Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support.Don't have a license key?Donate to this pluginDowngrading your planDownloadDownload %s VersionDownload Paid VersionDownload the latest %s versionDownload the latest versionDownloadedDue to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.Duplicate WebsiteDuring the update process we detected %d site(s) that are still pending license activation.During the update process we detected %s site(s) in the network that are still pending your attention.EmailEmail addressEmail address updateEnabling white-label modeEndEnter email addressEnter the domain of your website or other websites from where you plan to promote the %s.Enter the email address you've used during the purchase and we will resend you the license key.Enter the email address you've used for the upgrade below and we will resend you the license key.Enter the new email addressErrorError received from the server:ExpiredExpires in %sExtensionsExtra DomainsExtra domains where you will be marketing the product from.FileFilterFor compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.For delivery of security & feature updates, and license management, %s needs toFreeFree TrialFree versionFreemius APIFreemius DebugFreemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error.Freemius StateFreemius is our licensing and software updates engineFull nameFunctionGet commission for automated subscription renewals.Get updates for bleeding edge Beta versions of %s.Have a license key?Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!Homepage URL & title, WP & PHP versions, and site languageHow do you like %s so far? Test all our %s premium features with a %d-day free trial.How to upload and activate?How will you promote us?I Agree - Change UserI can't pay for it anymoreI couldn't understand how to make it workI don't like to share my information with youI found a better %sI have upgraded my account but when I try to Sync the License, the plan remains %s.I no longer need the %sI only needed the %s for a short periodIDIf this is a long term duplicate, to keep automatic updates and paid functionality after %s, please %s.If you click it, this decision will be delegated to the sites administrators.If you didn't get the email, try checking your spam folder or search for emails from %4$s.If you have a moment, please let us know why you are %sIf you skip this, that's okay! %1$s will still work just fine.If you wish to cancel your %1$s plan's subscription instead, please navigate to the %2$s and cancel it there.If you would like to give up the ownership of the %s's account to %s click the Change Ownership button.If you'd like to use the %s on those sites, please enter your license key below and click the activation button.Important Upgrade Notice:In %sIn case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?Install Free Version NowInstall Free Version Update NowInstall NowInstall Update NowInstalling plugin: %sInvalid clone resolution action.Invalid module ID.Invalid new user ID or email address.Invalid site details collection.InvoiceIs %2$s a duplicate of %4$s?Is %2$s a new website?Is %2$s the new home of %4$s?Is ActiveIs active, deactivated, or uninstalledIs this your client's site? %s if you wish to hide sensitive info like your email, license key, prices, billing address & invoices from the WP Admin.It looks like the license could not be activated.It looks like the license deactivation failed.It looks like you are not in trial mode anymore so there's nothing to cancel :)It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry.It looks like your site currently doesn't have an active license.It requires license activation.It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.It's a temporary %s - I'm troubleshooting an issueIt's not what I was looking forJoin the Beta programJust letting you know that the add-ons information of %s is being pulled from an external server.Keep SharingKeep automatic updatesKeyKindly share what didn't work so we can fix it for future users...Kindly tell us the reason so we can improve.LastLast UpdatedLast licenseLatest Free Version InstalledLatest Version InstalledLearn moreLengthLicenseLicense AgreementLicense IDLicense KeyLicense issues?License keyLicense key is empty.LifetimeLike the %s? Become our ambassador and earn cash ;-)Load DB OptionLocalhostLogLoggerLong-Term DuplicateMessageMethodMigrateMigrate LicenseMigrate Options to NetworkMobile appsModuleModule PathModule TypeMore information about %sNameNames, slugs, versions, and if active or notNetwork BlogNetwork UserNever miss an important updateNever miss important updates, get security warnings before they become public knowledge, and receive notifications about special offers and awesome new features.NewNew Version AvailableNew WebsiteNewer Free Version (%s) InstalledNewer Version (%s) InstalledNewsletterNextNoNo - only move this site's data to %sNo IDNo commitment for %s - cancel anytimeNo commitment for %s days - cancel anytime!No credit card requiredNo expirationNon-expiringNone of the %s's plans supports a trial period.O.KOnce your license expires you can still use the Free version but you will NOT have access to the %s features.Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license.Opt InOpt OutOpt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info.Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info. This will help us make the %s more compatible with your site and better at doing what you need it to.Opt in to make "%s" better!OtherOwner EmailOwner IDOwner NamePCI compliantPaid add-on must be deployed to Freemius.Part of an activation link message.Click herePart of the message telling the user what they should receive via email.a license keyPart of the message telling the user what they should receive via email.the installation instructionsPart of the message that tells the user to check their spam folder for a specific email.the product's support email addressPayPal account email addressPaymentsPayouts are in USD and processed monthly via PayPal.PlanPlan %s do not exist, therefore, can't start a trial.Plan %s does not support a trial period.Plan IDPlease contact us herePlease contact us with the following message:Please download %s.Please enter the license key that you received in the email right after the purchase:Please enter the license key to enable the debug mode:Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential).Please follow these steps to complete the upgradePlease let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.Please provide details on how you intend to promote %s (please be as specific as possible).Please provide your full name.PluginPlugin HomepagePlugin IDPlugin InstallPlugin installer section titleChangelogPlugin installer section titleDescriptionPlugin installer section titleFAQPlugin installer section titleFeatures & PricingPlugin installer section titleInstallationPlugin installer section titleOther NotesPlugin installer section titleReviewsPlugin is a "Serviceware" which means it does not have a premium code version.PluginsPlugins & Themes SyncPremiumPremium %s version was successfully activated.Premium add-on version already installed.Premium versionPremium version already active.PricingPrivacy PolicyProceedProcess IDProcessingProductsProgram SummaryPromotion methodsProvincePublic KeyPurchase LicensePurchase MoreQuick FeedbackQuotaRe-send activation emailRefer new customers to our %s and earn %s commission on each successful sale you refer!Renew licenseRenew your license nowRequestsRequires PHP VersionRequires WordPress VersionReset Deactivation SnoozingResultSDKSDK PathSave %sSavedScheduled CronsScreenshotsSearch by addressSecret KeySecure HTTPS %s page, running from an external domainSeems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes.Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.Seems like you got the latest release.Select CountrySend License KeySet DB OptionSharing diagnostic data with %s helps to provide functionality that's more relevant to your website, avoid WordPress or PHP version incompatibilities that can break your website, and recognize which languages & regions the plugin should be translated and tailored to.Show error detailsSimulate Network UpgradeSimulate Trial PromotionSingle Site LicenseSite IDSite successfully opted in.SitesSkip & %sSlugSnooze & %sSo you can reuse the license when the %s is no longer active.Social media (Facebook, Twitter, etc.)Sorry for the inconvenience and we are here to help if you give us a chance.Sorry, we could not complete the email update. Another user with the same email is already registered.StartStart DebugStart TrialStart my free %sStateStay ConnectedStop DebugSubmitSubmit & %sSubscriptionSupportSupport ForumSync Data From ServerTax / VAT IDTerms of ServiceThank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days.Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information.Thank you for updating to %1$s v%2$s!Thank you so much for using %s and its add-ons!Thank you so much for using %s!Thank you so much for using our products!Thank you!Thanks %s!Thanks for confirming the ownership change. An email was just sent to %s for final approval.Thanks for upgrading.Thanks!The %1$s will be periodically sending essential license data to %2$s to check for security and feature updates, and verify the validity of your license.The %s broke my siteThe %s didn't workThe %s didn't work as expectedThe %s is great, but I need specific feature that you don't supportThe %s is not workingThe %s suddenly stopped workingThe following products'The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page.The products below have been placed into safe mode because we noticed that %2$s is an exact copy of %3$s:%1$sThe products below have been placed into safe mode because we noticed that %2$s is an exact copy of these sites:%3$s%1$sThe remote plugin package does not contain a folder with the desired slug and renaming did not work.The upgrade of %s was successfully completed.ThemeTheme SwitchThemesThere is a %s of %s available.There is a new version of %s available.There was an unexpected API error while processing your request. Please try again in a few minutes and if it still doesn't work, contact the %s's author with the following:This plugin has not been marked as compatible with your version of WordPress.This plugin has not been tested with your current version of WordPress.This plugin requires a newer version of PHP.This will allow %s toTimestampTitleTo avoid breaking your website due to WordPress or PHP version incompatibilities, and recognize which languages & regions the %s should be translated and tailored to.To ensure compatibility and avoid conflicts with your installed plugins and themes.To enter the debug mode, please enter the secret key of the license owner (UserID = %d), which you can find in your "My Profile" section of your User Dashboard:To let you manage & control where the license is activated and ensure %s security & feature updates are only delivered to websites you authorize.To provide additional functionality that's relevant to your website, avoid WordPress or PHP version incompatibilities that can break your website, and recognize which languages & regions the %s should be translated and tailored to.TotalTownTrialTypeUnable to connect to the filesystem. Please confirm your credentials.Unlimited LicensesUnlimited UpdatesUnlimited commissions.Up to %s SitesUpdateUpdate LicenseUpdates, announcements, marketing, no spamUpgradeUpload and activate the downloaded versionUsed to express elation, enthusiasm, or triumph (especially in electronic communication).W00tUser DashboardUser IDUser keyUsersValueVerification mail was just sent to %s. If you can't find it after 5 min, please check your spam box.VerifiedVerify EmailVersion %s was released.View %s StateView Basic %s InfoView Basic Profile InfoView Basic Website InfoView Diagnostic InfoView License EssentialsView Plugins & Themes ListView detailsView paid featuresWarningWe can't see any active licenses associated with that email address, are you sure it's the right address?We couldn't find your email address in the system, are you sure it's the right address?We couldn't load the add-ons list. It's probably an issue on our side, please try to come back in few minutes.We have introduced this opt-in so you never miss an important update and help us make the %s more compatible with your site and better at doing what you need it to.We made a few tweaks to the %s, %sWe're excited to introduce the Freemius network-level integration.Website, email, and social media statistics (optional)Welcome to %s! To get started, please enter your license key:What did you expect?What feature?What is your %s?What price would you feel comfortable paying?What you've been looking for?What's the %s's name?Where are you going to promote the %s?WordPress & PHP versions, site language & titleWordPress.org Plugin PageWould you like to merge %s into %s?Would you like to proceed with the update?YesYes - %sYes - both addresses are mineYes - move all my data and assets from %s to %sYes, %%2$s is replacing %%4$s. I would like to migrate my %s from %%4$s to %%2$s.Yes, %2$s is a duplicate of %4$s for the purpose of testing, staging, or development.Yes, %2$s is a new and different website that is separate from %4$s.You already utilized a trial before.You are 1-click away from starting your %1$s-day free trial of the %2$s plan.You are all good!You are already running the %s in a trial mode.You are just one step away - %sYou can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.You do not have a valid license to access the premium version.You have a %s license.You have purchased a %s license.You have successfully updated your %s.You marked this website, %s, as a temporary duplicate of %s.You marked this website, %s, as a temporary duplicate of these sitesYou might have missed it, but you don't have to share any data and can just %s the opt-in.You should receive %3$s for %1$s to your mailbox at %2$s in the next 5 minutes.You should receive a confirmation email for %1$s to your mailbox at %2$s. Please make sure you click the button in that email to %3$s.You should receive a confirmation email for %s to your mailbox at %s. Please make sure you click the button in that email to %s.You've already opted-in to our usage-tracking, which helps us keep improving the %s.You've already opted-in to our usage-tracking, which helps us keep improving them.Your %s Add-on plan was successfully upgraded.Your %s free trial was successfully cancelled.Your %s license was flagged as white-labeled to hide sensitive information from the WP Admin (e.g. your email, license key, prices, billing address & invoices). If you ever wish to revert it back, you can easily do it through your %s. If this was a mistake you can also %s.Your %s license was successfully deactivated.Your WordPress user's: first & last name, and email addressYour account was successfully activated with the %s plan.Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s.Your affiliation account was temporarily suspended.Your email has been successfully verified - you are AWESOME!Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your free trial has expired. You can still continue using all our free features.Your license has been cancelled. If you think it's a mistake, please contact support.Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support.Your license has expired. You can still continue using the free %s forever.Your license was successfully activated.Your license was successfully deactivated, you are back to the %s plan.Your name was successfully updated.Your plan was successfully activated.Your plan was successfully changed to %s.Your plan was successfully upgraded.Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist the following domains:%2$sYour subscription was successfully cancelled. Your %s plan license will expire in %s.Your trial has been successfully started.ZIP / Postal Codea positive responseRight onactivate a license hereactive add-onActiveaddonX cannot run without pluginY%s cannot run without %s.addonX cannot run...%s cannot run without the plugin.advance notice of something that will need attention.Heads upallowas 5 licenses left%s leftas activating pluginActivatingas annual periodyearas application program interfaceAPIas close a windowDismissas code debuggingDebuggingas congratulationsCongratsas connection blockedBlockedas connection was successfulConnectedas download latest versionDownload Latestas download latest versionDownload Latest Free Versionas every monthMonthlyas expiration dateExpirationas file/folder pathPathas in the process of sending an emailSending emailas monthly periodmoas once a yearAnnualas once a yearAnnuallyas once a yearOnceas product pricing planPlanas secret encryption key missingNo Secretas software development kit versionsSDK Versionsas software licenseLicenseas synchronizeSyncas synchronize licenseSync Licenseas the plugin authorAuthoras turned offOffas turned onOnbased on %scall to actionStart free trialclose a windowDismissclose windowDismisscomplete the opt-indatadaysdeactivatingdelegatedo %sNOT%s send me security & feature updates, educational content and offers.e.g. Professional Plan%s Plane.g. billed monthlyBilled %se.g. the best productBestexclamationHeyexclamationOopsgreetingHey %s,hourhoursinstalled add-onInstalledinterjection expressing joy or exuberanceYee-hawlicenselike websitesSitesmillisecondsmsnew Beta versionnew versionnot verifiednounPricenounPricingoptionalproduct versionVersionproductsrevert it nowsecondssecseems like the key you entered doesn't match our records.send me security & feature updates, educational content and offers.skipsomething somebody says when they are thinking about what you have just said.Hmmstart the trialsubscriptionswitchingthe above-mentioned sitesthe latest %s version heretrialtrial periodTrialverbDeleteverbDowngradeverbEditverbHideverbOpt InverbOpt OutverbPurchaseverbShowverbSkipverbUpdateverbUpgradex-ago%s agoProject-Id-Version: WordPress SDK Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues PO-Revision-Date: 2015-10-01 21:38+0000 Last-Translator: Oliver Heinrich, 2022-2024 Language-Team: German (Germany) (http://app.transifex.com/freemius/wordpress-sdk/language/de_DE/) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Language: de_DE Plural-Forms: nplurals=2; plural=(n != 1); X-Poedit-Basepath: .. X-Poedit-KeywordsList: get_text_inline;get_text_x_inline:1,2c;$this->get_text_inline;$this->get_text_x_inline:1,2c;$this->_fs->get_text_inline;$this->_fs->get_text_x_inline:1,2c;$this->fs->get_text_inline;$this->fs->get_text_x_inline:1,2c;$fs->get_text_inline;$fs->get_text_x_inline:1,2c;$this->_parent->get_text_inline;$this->_parent->get_text_x_inline:1,2c;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c X-Poedit-SearchPath-0: . X-Poedit-SearchPathExcluded-0: *.js X-Poedit-SourceCharset: UTF-8 %s, um auf die Sicherheits- und Funktionsupdates der Version %s und den Support zuzugreifen.Der %s-%sDownload-Link%s, der Lizenzschlüssel und die Installationsanleitung wurden an %s gesendet. Wenn du die E-Mail nach 5 Minuten nicht finden kannst, überprüfe bitte dein Spam-Postfach. Die kostenpflichtige Version von %1$s ist bereits installiert. Bitte aktiviere sie, um von den %2$s Funktionen zu profitieren. %3$sDie %s%1$s wurde in den abgesicherten Modus versetzt, weil wir festgestellt haben, dass %2$s eine exakte Kopie von %3$s ist.%1$s wird sofort alle zukünftigen wiederkehrenden Zahlungen stoppen und deine %2$s Planz-Lizenz wird in %3$s auslaufen.Schließe jetzt die "%s"-Aktivierung ab%s Add-on wurde erfolgreich erworben.%s Installationen%s Lizenzenvor %s%s und seine Add-ons%s automatische Sicherheits- und Funktionsupdates und kostenpflichtige Funktionen funktionieren bis %s (oder bis zum Ablauf deiner Lizenz, je nachdem, was zuerst eintritt).%s Provision, wenn ein Kunde eine neue Lizenz kauft.Die kostenlose Testversion von %s wurde erfolgreich storniert. Da das Add-on nur Premium ist, wurde es automatisch deaktiviert. Wenn du es in Zukunft nutzen möchtest, musst du eine Lizenz erwerben.%s ist ein reines Premium-Add-on. Du musst zuerst eine Lizenz erwerben, bevor du das Plugin aktivieren kannst.%s ist die E-Mail-Adresse meines Kunden%s ist meine E-Mail-Adresse%s ist der neue Besitzer des Kontos.%s Mindestauszahlungsbetrag.%s Opt-In wurde erfolgreich abgeschlossen.%s oder höher%s Bewertung%s Bewertungen%s s%s Stern%s Sterne%s mal%s mal%s, um auf die Sicherheits- und Funktionsupdates der Version %s und den Support zuzugreifen.%s, um die Lizenz zu aktivieren, sobald du sie erhalten hast.%s Tracking-Cookie nach dem ersten Besuch, um das Ertragspotenzial zu maximieren.Bezahlte Funktionen von %s%sKlicke hier%s um die Webseite auszuwählen, auf denen du die Lizenz aktivieren möchtest.Klicke hier, um mehr über die Aktualisierung von PHP zu erfahren.Eine Bestätigungs-E-Mail wurde gerade an %s gesendet. Der Besitzer der E-Mail-Adresse muss die Aktualisierung innerhalb der nächsten 4 Stunden bestätigen.Eine Bestätigungs-E-Mail wurde gerade an %s gesendet. Du musst die Aktualisierung innerhalb der nächsten 4 Stunden bestätigen. Wenn du die E-Mail nicht findest, überprüfe bitte deinen Spam-Ordner.APIUnbekannt←➤KontoKonto DetailsDie Aktivierung des Kontos steht noch aus. Bitte überprüfe deine E-Mails und klicke auf den Link, um dein Konto zu aktivieren, und sende dann das Affiliate-Formular erneut.AktionenAktivierenAktiviere %sAktiviere %s PlanAktiviere %s FunktionenKostenlose Version freischaltenLizenz freischaltenAktiviere die Lizenz auf allen ausstehenden Seiten.Aktiviere die Lizenz auf allen Seiten im Netzwerk.Aktiviere dieses Add-onAktiviertAdd-Ons für %sAdd-Ons von Modul %sWeitere Domain hinzufügenAdd-onAdd-onsDas Add-on muss auf WordPress.org oder Freemius bereitgestellt werden.AdresseAdresse Zeile %dAffiliateAffiliateNach deinem kostenlosen %s zahlst du so wenig wie %sZustimmen & Lizenz aktivierenAlle AnfragenAlle ArtenErlauben & FortfahrenAlternativ kannst du auch diesen Schritt überspringen und die Lizenz später auf der Netzwerk-Kontoseite deines %s aktivieren.BetragEin automatischer Download und die Installation von %s (kostenpflichtige Version) von %s wird in %s starten. Wenn du es manuell machen möchtest - klicke jetzt auf den Abbruch-Button.Beim Versuch, den Beta-Modus für den Benutzers einzustellen, ist ein unbekannter Fehler aufgetreten.Beim Versuch, auf den White-Label-Modus der Lizenz umzuschalten, ist ein unbekannter Fehler aufgetreten.Ein unbekannter Fehler ist aufgetreten.Ein Update auf eine Beta-Version ersetzt deine installierte Version von %s durch die neueste Beta-Version - verwende sie mit Vorsicht und nur auf Testseiten. Du wurdest gewarnt.Anonymes FeedbackAuf alle ausstehenden Webseiten anwenden.Auf alle Seiten im Netzwerk anwenden.Bewirb dich, um ein Affiliate zu werdenSind %s und %s beide deine E-Mail-Adressen?Bist du sicher, dass du alle Freemius-Daten löschen möchtest?Bist du sicher, dass du fortfahren willst?Möchtest du wirklich mit der Trennung fortfahren?Da wir 30 Tage für mögliche Rückzahlungen reservieren, zahlen wir nur Provisionen aus, die älter als 30 Tage sind.Mit dem Konto des Lizenzinhabers verknüpfen.Die automatische Installation funktioniert nur für eingeloggte Nutzer.Verlängert sich automatisch in %sAutomatische InstallationDurchschnittliche BewertungfantastischenPartner werdenBetaAbrechnungAbrechnung & RechnungenBlockierenBlog IDHauptteilPaketPaket-PlanGeschäftsnameJetzt eine Lizenz kaufenLizenz kaufenIndem du den Benutzer wechselst, stimmst du zu, den Besitz des Accounts zu übertragen:Durch das Trennen der Website werden zuvor geteilte Diagnosedaten über %1$sgelöscht und sind für %2$snicht mehr sichtbar.Du kannst deinen Lizenzschlüssel nicht finden?Abbrechen%s abbrechen & fortfahrenAbbrechen %s - Ich benötige keine Sicherheits- und Funktionsupdates mehr und auch keinen Support für %s, da ich nicht vorhabe, das %s auf dieser oder einer anderen Seite zu verwenden.%s abbrechen?Installation abbrechenAbonnement kündigenTestversion abbrechenAbgebrochenAbbruch von %sStoppe %s...Das Abonnement kündigenWenn du die Testversion abbrichst, wird der Zugang zu allen Premium-Funktionen sofort gesperrt. Bist du sicher?Lizenz ändernEigentümer wechselnPlan ändernBenutzer ändernVerkaufs-StadtAPI Cache löschenLöschen von Aktualisierungen RestenHier klickenKlicke hier, um das Plugin anonym zu nutzenKlicke, um Bewertungen zu sehen, die eine Bewertung von %s abgegeben habenKlicke, um den Screenshot in voller Größe zu sehen %dProdukteCodeKommunikationKompatibel bis zuKontaktKontaktiere SupportKontaktMitwirkendeKonnte %s nicht aktivieren.LandCron TypAktuelle %sund SDK-Versionen und falls aktiv oder deinstalliertDatumDeaktivierenLizenz deaktivierenWenn du das %s deaktivierst oder deinstallierst, wird die Lizenz hier automatisch deaktiviert und du kannst sie auf einer anderen Seite verwenden.Wenn du deine Lizenz deaktivierst, werden alle Premium-Funktionen gesperrt, aber du kannst die Lizenz auf einer anderen Seite aktivieren. Bist du sicher, dass du fortfahren möchtest?DeaktivierungDebug LogDer Debug-Modus wurde erfolgreich aktiviert und wird in 60 Minuten automatisch wieder deaktiviert. Du kannst ihn auch früher deaktivieren, indem du auf den Link "Stop Debug" klickst.An die Website Admins delegierenAlle Konten löschenDetailsDiagnoseinformationenDiagnosedaten werden nicht mehr von %s bis %sgesendet.Deaktivieren des White-Label-ModusDas Trennen der Website wird %sdauerhaft aus dem Konto Ihres Benutzer-Dashboards entfernt.%s bitte nicht stornieren - ich bin immer noch daran interessiert, Sicherheits- und Funktionsupdates zu erhalten, sowie den Support kontaktieren zu können.Du hast keinen Lizenzschlüssel?Für dieses Plugin spendenDowngrade deinen PlanDownloadDownload %s VersionBezahlte Version herunterladenLade die neueste %s Version herunterLade die neueste Version herunterHeruntergeladenAufgrund der neuen %sEU Datenschutzgrundverordnung (DSGVO)%s Compliance-Anforderungen ist es erforderlich, dass du deine ausdrückliche Zustimmung gibst und damit bestätigst, dass du an Bord bist :-)Aufgrund eines Verstoßes gegen unsere Partnerschaftsbedingungen haben wir beschlossen, dein Affiliate-Konto vorübergehend zu sperren. Wenn du Fragen hast, wende dich bitte an den Support.Duplizierte WebseiteWährend des Update-Prozesses haben wir %d Site(s) entdeckt, die noch auf eine Lizenzaktivierung warten.Während des Update-Prozesses haben wir %s Site(s) im Netzwerk entdeckt, die noch deine Aufmerksamkeit benötigen.E-MailE-Mail-AdresseE-Mail-Adresse aktualisierenAktivieren des White-Label-ModusEndeE-Mail-Adresse eingebenGib die Domain deiner Website oder anderer Webseiten an, von denen aus du planst, %s zu bewerben.Gib die E-Mail-Adresse ein, die du beim Kauf verwendet hast, und wir senden Dir den Lizenzschlüssel erneut zu.Gib unten die E-Mail-Adresse ein, die du für das Upgrade verwendet hast und wir senden dir den Lizenzschlüssel erneut zu.Gib die neue E-Mail-Adresse einFehlerFehler vom Server empfangen:AbgelaufenLäuft in %s abErweiterungenExtra DomainsZusätzliche Domains, von denen aus du das Produkt vermarkten wirst.DateiFilterUm die Richtlinien von WordPress.org einzuhalten, bitten wir dich vor dem Start der Testversion um ein Opt-In mit deinen Benutzer- und nicht sensiblen Seite-Informationen, damit %s regelmäßig Daten an %s senden kann, um nach Versions-Updates zu suchen und um deine Testversion zu validieren.Für die Bereitstellung von Sicherheits- und Funktionsupdates sowie die Lizenzverwaltung muss %sKostenlosKostenlose TestversionKostenlose VersionFreemius APIFreemius DebugDas Freemius-SDK konnte die Hauptdatei des Plugins nicht finden. Bitte kontaktiere sdk@freemius.com mit der gezeigten Fehlermeldung.Freemius StatusFreemius ist unser Dienstleister für Lizenzierung und Software-UpdatesVollständiger NamePositionErhalte Provisionen für automatisierte Abonnementverlängerungen.Erhalte Updates für aktuelle Beta-Versionen von %s.Hast du einen Lizenzschlüssel?Hallo, wusstest du, dass %s ein Partnerprogramm hat? Wenn du %s magst, kannst du unser Affiliate-Partner werden und etwas Geld verdienen!URL und Titel der Homepage, WP- und PHP-Versionen und Sprache der WebsiteWie gefällt dir %s bis jetzt? Teste alle %s Premium-Funktionen mit der kostenlosen %d-Tage-Testversion.Wie kann ich es hochladen und aktivieren?Wie wirst du uns bewerben?Ich stimme zu - ändere den BenutzerIch kann es nicht mehr bezahlenIch habe nicht gewusst wie ich es zum Laufen bringeIch möchte meine Informationen nicht mit dir teilenIch habe ein besseres %s gefundenIch habe mein Konto hochgestuft, aber wenn ich versuche, die Lizenz zu synchronisieren, bleibt der Plan %s.Ich brauche das %s nicht mehrIch habe das %s nur für einen kurzen Zeitraum benötigtIDWenn dies ein langfristiges Duplikat ist, dann musst du nach %s bitte %s.Wenn du es anklickst, wird diese Entscheidung an die Administratoren der Seite delegiert.Wenn Du die E-Mail nicht erhalten hast, überprüfe deinen Spam-Ordner oder suche nach E-Mails von %4$s.Wenn du einen Moment Zeit hast, lass uns bitte wissen, warum du %sWenn du das überspringst, ist das in Ordnung! %1$swird immer noch funktionieren.Wenn Du stattdessen das Abonnement deines %1$sPlans kündigen möchtest, navigiere bitte zu %2$sund kündige es dort.Wenn du die Eigentümerschaft des Kontos von %s an %s abgeben möchtest, klicke auf den Button Eigentümer wechseln.Wenn du das %s auf diesen Seiten nutzen möchtest, gib bitte deinen Lizenzschlüssel unten ein und klicke auf den Aktivierungsbutton.Wichtiger Hinweis zum Upgrade:In %sFür den Fall, dass du NICHT vorhast, diesen %s auf dieser Seite (oder einer anderen Seite) zu verwenden - möchtest du den %s auch löschen?Kostenlose Version jetzt installierenUpdate der kostenlosen Version jetzt installierenJetzt installierenUpdate jetzt installierenInstalliere das Plugin: %sUngültige Klon-Auflösungsaktion.Ungültige Modul-ID.Ungültige neue Benutzer-ID oder E-Mail Adresse.Ungültige Website-Detailsammlung.RechnungIst %2$s ein Duplikat von %4$s?Ist %2$s eine neue Website?Wurde %4$s auf %2$s umgezogen?Ist AktivIst aktiv, deaktiviert oder deinstalliertIst dies die Seite deines Kunden? %s wenn du sensible Informationen wie deine E-Mail, Lizenzschlüssel, Preise, Rechnungsadresse & Rechnungen vor dem WP Admin verstecken möchtest.Es sieht so aus, als ob die Lizenz nicht aktiviert werden konnte.Es sieht so aus, als wäre die Lizenzdeaktivierung fehlgeschlagen.Es sieht so aus, als wärst du nicht mehr im Testmodus, also gibt es nichts zu stornieren :)Es sieht so aus, als wärst du immer noch im %s-Plan. Wenn du ein Upgrade oder einen Planwechsel durchgeführt hast, ist das wahrscheinlich ein Problem auf unserer Seite - sorry.Es sieht so aus, als ob deine Seite derzeit keine aktive Lizenz hat.Das erfordert eine Lizenzaktivierung.Es scheint, dass einer der Authentifizierungsparameter falsch ist. Aktualisiere deinen Public Key, Secret Key & User ID und versuche es erneut.Es ist eine temporäre %s - ich behebe ein ProblemEs ist nicht das, wonach ich gesucht habeAm Beta Programm teilnehmenIch wollte dich nur darauf hinweisen, dass die Add-on-Informationen von %s von einem externen Server bezogen werden.Weiter teilenAutomatische Updates beibehaltenSchlüsselBitte teile uns mit, was nicht funktioniert hat, damit wir es für zukünftige Nutzer beheben können.Bitte nenne uns den Grund, damit wir uns verbessern können.LetzteZuletzt aktualisiertLetzte LizenzAktuellste kostenlose Version installiertAktuellste Version installiertMehr erfahrenLängeLizenzLizenzvertragLizenz IDLizenzschlüsselLizenzprobleme?LizenzschlüsselDer Lizenzschlüssel ist leer.LebenslangMagst du %s? Werde unser Botschafter/Affiliate und verdiene Geld ;-)Lade DB EinstellungLocalhostLogLoggerLangfristiges DuplikatNachrichtMethodeMigrierenLizenz migrierenMigriere die Einstellungen ins NetzwerkMobile AppsModulModul PfadModul TypMehr Informationen über %sNameNamen, Slugs, Versionen und ob aktiv oder nichtNetzwerk BlogNetzwerk BenutzerVerpasse nie wieder ein wichtiges UpdateVerpasse keine wichtigen Updates, erhalte Sicherheitswarnungen, bevor sie öffentlich bekannt werden, und erhalte Benachrichtigungen über Sonderangebote und tolle neue Funktionen.NeuNeue Version verfügbarNeue WebsiteNeuere kostenlose Version (%s) InstalliertNeuere Version (%s) InstalliertNewsletterNächsteNeinNein - verschiebe nur die Daten dieser Seite nach %sKeine IDKeine Verpflichtung für %s - jederzeit kündigenKeinerlei Verpflichtung für %s Tage - jederzeit kündbar!Keine Kreditkarte erforderlichKein AblaufdatumNicht auslaufendKeiner der Pläne von %s unterstützt eine Probezeit.OKSobald deine Lizenz abläuft, kannst du die Free Version immer noch nutzen, aber du hast KEINEN Zugriff auf die %s Features.Sobald deine Lizenz abläuft, kannst du das %s nicht mehr nutzen. Es sei denn, du aktivierst ihn erneut mit einer gültigen Premiumlizenz.Opt-InAbmeldenMelden dich an, um E-Mail-Benachrichtigungen für Sicherheits- und Funktionsupdates, Bildungsinhalte und gelegentliche Angebote zu erhalten und einige grundlegende Informationen zur WordPress-Umgebung zu teilen.Melde dich an, um E-Mail-Benachrichtigungen für Sicherheits- und Funktionsupdates, Bildungsinhalte und gelegentliche Angebote zu erhalten und einige grundlegende Informationen zur WordPress-Umgebung zu teilen. Dies wird uns helfen, die Kompatibilität mit deiner Website zu verbessern und uns auf das zu konzentrieren, was du benötigst.Mach mit, um "%s" besser zu machen!AndereBesitzer EmailBesitzer IDName des BesitzersPCI-konformeDas kostenpflichtige Add-on muss auf Freemius veröffentlicht werden.Hier klickenein Lizenzschlüsseldie Installationsanleitungdie Support-E-Mail-Adresse des ProduktsPayPal Konto E-Mail AdresseZahlungenDie Auszahlungen erfolgen in USD und werden monatlich über PayPal abgewickelt.PlanPlan %s existiert nicht, es kann keine Testversion gestartet werden.Der Plan %s unterstützt keine Probezeit.Plan IDBitte kontaktiere uns hierBitte kontaktiere uns mit der folgenden Nachricht:Bitte lade %s herunter.Bitte gib den Lizenzschlüssel ein, den du in der E-Mail direkt nach dem Kauf erhalten hast:Bitte gib den Lizenzschlüssel ein, um den Debug-Modus zu aktivieren:Du kannst uns gerne relevante Statistiken über deine Website oder soziale Medien zur Verfügung stellen, z.B. monatliche Besuche der Website, Anzahl der E-Mail-Abonnenten, Follower, etc. (wir werden diese Informationen vertraulich behandeln).Bitte folge diesen Schritten, um das Upgrade abzuschließenBitte lass uns wissen, wenn du möchtest, dass wir dich für Sicherheits- und Funktionsupdates, Bildungsinhalte und gelegentliche Angebote kontaktieren:Bitte beachte, dass wir nicht in der Lage sind, veraltete Preise für Verlängerungen/neue Abonnements nach einer Kündigung beizubehalten. Wenn du dich dafür entscheidest, das Abonnement in Zukunft nach einer Preiserhöhung, die in der Regel einmal im Jahr stattfindet, manuell zu verlängern, wird dir der aktualisierte Preis berechnet.Bitte gib Details an, wie du beabsichtigst, %s zu fördern (bitte sei so genau wie möglich).Bitte gib deinen vollständigen Namen an.PluginPlugin-HomepagePlugin-IDPlugin installierenChangelogBeschreibungFAQFunktionen & PreiseInstallationWeitere NotizenBewertungenDas Plugin ist eine "Serviceware", was bedeutet, dass es keine Premiumversion hat.PluginsPlugins & Themes SyncPremiumPremium %s Version wurde erfolgreich aktiviert.Premium Add-on Version bereits installiert.Premium VersionPremium Version bereits aktiv.PreisDatenschutzrichtlinieFortfahrenProzess-IDVerarbeitungProdukteProgramm ZusammenfassungPromotion MethodenProvinzÖffentlicher SchlüsselLizenz kaufenMehr kaufenSchnelles FeedbackKontingentAktivierungsmail erneut sendenEmpfehle neue Kunden an unsere %s und verdiene %s Provision für jeden erfolgreichen Verkauf, den du vermittelst!Lizenz erneuernVerlängere deine Lizenz jetztAnfragenBenötigt PHP VersionBenötigt WordPress VersionZurücksetzen des Deaktivierungs-SchlummernsErgebnisSDKSDK Pfad%s speichernGespeichertGeplante CronsScreenshotsSuche über die AdresseGeheimer SchlüsselSichere, verschlüsselte %s Seite, die von einer externen Domain geladen wirdEs scheint, als ob wir ein temporäres Problem mit der Kündigung deines Abonnements haben. Bitte versuche es in ein paar Minuten erneut.Es scheint, als hätten wir ein temporäres Problem mit der Abmeldung deiner Testversion. Bitte versuche es in ein paar Minuten erneut.Sieht so aus, als hättest du die neueste Version.Land auswählenLizenzschlüssel sendenDB Option setzenDas Teilen von Diagnosedaten mit %s hilft dabei, Funktionen bereitzustellen, die für deine Website relevanter sind, Inkompatibilitäten mit WordPress- oder PHP-Versionen zu vermeiden, die deine Website beschädigen können, und zu erkennen, auf welche Sprachen und Regionen das Plugin übersetzt und angepasst werden sollte.Fehlerdetails anzeigenNetzwerk Upgrade simulierenTrial Promotion simulierenEinzelplatzlizenzWebseiten-IDWebseite erfolgreich eingeloggt.WebseitenÜberspringen & %sURLSnooze & %sSo kannst du die Lizenz wiederverwenden, wenn die %s nicht mehr aktiv ist.Soziale Medien (Facebook, Twitter, etc.)Wir entschuldigen uns für die Unannehmlichkeiten und sind hier, um zu helfen, wenn du uns eine Chance gibst.Sorry, wir konnten das E-Mail-Update nicht abschließen. Ein anderer Benutzer mit der gleichen E-Mail ist bereits registriert.StartDebugging startenStarte TestversionStarte mein kostenloses %sStaatBleib in KontaktDebugging stoppenAbsendenAbschicken & %sAbonnementUnterstützungSupport ForumDaten vom Server synchronisierenSteuer-/Umsatzsteuer-IDAllgemeine GeschäftsbedingungenVielen Dank, dass du dich für unser Partnerprogramm beworben hast. Leider haben wir uns an dieser Stelle entschieden, deine Bewerbung abzulehnen. Bitte versuche es in 30 Tagen erneut.Vielen Dank, dass du dich für unser Partnerprogramm beworben hast. Wir werden deine Angaben in den nächsten 14 Tagen überprüfen und uns mit weiteren Informationen bei dir melden.Vielen Dank für die Aktualisierung auf %1$s v %2$s!Vielen Dank, dass du %s und seine Add-ons benutzt!Vielen Dank, dass du %s benutzt!Vielen Dank, dass du unsere Produkte benutzt!Danke dir!Danke %s!Danke für die Bestätigung des Eigentümerwechsels. Eine E-Mail wurde soeben an %s zur endgültigen Genehmigung gesendet.Danke für das Upgrade.Danke!%1$swird regelmäßig wichtige Lizenzdaten an %2$ssenden, um nach Sicherheits- und Funktionsaktualisierungen zu suchen und die Gültigkeit Ihrer Lizenz zu überprüfen.Das %s hat meine Seite beschädigtDas %s hat nicht funktioniertDas %s hat nicht wie erwartet funktioniertDas %s ist toll, aber ich brauche ein bestimmtes Feature, das nicht unterstützt wirdDas %s funktioniert nichtDas %s funktionierte plötzlich nicht mehrDie folgenden Produkte ihreDer Installationsprozess hat begonnen und kann ein paar Minuten dauern. Bitte warte, bis er abgeschlossen ist - lade diese Seite nicht neu.Die folgenden Produkte wurden in den abgesicherten Modus versetzt, weil wir festgestellt haben, dass %2$s eine exakte Kopie von %3$s:%1$s istDie folgenden Produkte wurden in den abgesicherten Modus versetzt, weil wir festgestellt haben, dass %2$s eine exakte Kopie dieser Seiten ist:%3$s%1$sDas Remote-Plugin-Paket enthält keinen Ordner mit dem gewünschten Slug und das Umbenennen hat nicht funktioniert.Das Upgrade von %s wurde erfolgreich abgeschlossen.ThemeTheme wechselnThemesEs ist ein %s von %s verfügbar.Es ist eine neue Version von %s verfügbar.Beim Verarbeiten deiner Anfrage ist ein unerwarteter API-Fehler aufgetreten. Bitte versuche es in ein paar Minuten erneut und wenn es immer noch nicht funktioniert, kontaktiere den Autor von %s mit den folgenden Angaben:Dieses Plugin wurde als nicht kompatibel mit deiner Version von WordPress markiert.Dieses Plugin wurde nicht mit deiner derzeitigen Version von WordPress getestet.Dieses Plugin erfordert eine neuere Version von PHP.Dies wird es %s ermöglichenZeitstempelTitelUm zu vermeiden, dass deine Website aufgrund von WordPress- oder PHP-Versionsinkompatibilitäten beschädigt wird, und um zu erkennen, welche Sprachen und Regionen von %sübersetzt und angepasst werden sollten.Um die Kompatibilität zu gewährleisten und Konflikte mit deinen installierten Plugins und Themes zu vermeiden.Um in den Debug-Modus zu gelangen, gib bitte den geheimen Schlüssel des Lizenzinhabers (UserID = %d) ein, den du in deinem "Mein Profil"-Bereich deines "Benutzer Dashboards" findest:Damit du verwalten und kontrollieren kannst, wo die Lizenz aktiviert wird, und um sicherzustellen, dass %s Sicherheits- und Funktionsupdates nur an von dir autorisierte Websites geliefert werden.Um zusätzliche Funktionen bereitzustellen, die für deine Website relevant sind, Inkompatibilitäten von WordPress- oder PHP-Versionen, die deine Website beschädigen können, und um zu erkennen, welche Sprachen und Regionen für %sübersetzt und angepasst werden sollten.TotalStadtTestversionTypEs kann keine Verbindung zum Dateisystem hergestellt werden. Bitte bestätige deine Anmeldedaten.Unbegrenzt LizenzenUnbegrenzte UpdatesUnbegrenzte Provisionen.Bis zu %s SeitenUpdateLizenz aktualisierenUpdates, Ankündigungen, Marketing, kein SpamUpgradeLade die heruntergeladene Version hoch und aktiviere sieW00tBenutzer DashboardBenutzer-IDBenutzerschlüsselBenutzerWertVerifizierungsmail wurde gerade an %s gesendet. Wenn du sie nach 5 Minuten nicht finden kannst, überprüfe bitte deine Spam-Ordner.VerifiziertE-Mail verifizierenVersion %s wurde freigegeben.%s Status anzeigenGrundlegende %sInformationen anzeigenGrundlegende Profilinformationen anzeigenGrundlegende Website-Informationen anzeigenDiagnoseinformationen anzeigenGrundlegende Lizenzen anzeigenListe der Plugins & Themes anzeigenDetails ansehenBezahlte Funktionen ansehenWarnungWir können keine aktiven Lizenzen sehen, die mit dieser E-Mail-Adresse verbunden sind. Bist du sicher, dass es die richtige Adresse ist?Wir konnten deine E-Mail-Adresse nicht im System finden. Bist du sicher, dass dies die richtige Adresse ist?Wir konnten die Liste der Add-ons nicht laden. Es ist wahrscheinlich ein Problem auf unserer Seite, bitte versuche es in ein paar Minuten wieder.Wir haben dieses Opt-in eingeführt, damit du kein wichtiges Update verpasst und uns hilfst, die Kompatibilität von %smit deiner Website zu verbessern und uns auf das zu konzentrieren, was du benötigst.Wir haben ein paar Anpassungen am %s gemacht. %sWir freuen uns, dir die Freemius Netzwerk-Integration vorstellen zu können.Website-, E-Mail- und Social Media-Statistiken (optional)Willkommen bei %s! Um loszulegen, gib bitte deinen Lizenzschlüssel ein:Was hast du erwartet?Welche Funktion?Wie lautet dein(e) %s?Welchen Preis würdest du als gerechtfertigt erachten?Wonach hast du gesucht?Wie lautet der Name des %s?Wo wirst du %s bewerben?WordPress- und PHP-Versionen, Sprache und Titel der WebsiteWordPress.org-Plugin-SeiteMöchtest du %s in %s zusammenführen?Willst du mit dem Update fortfahren?JaJa - %sJa - beide Adressen sind meineJa - verschiebe alle meine Daten und Vermögenswerte von %s nach %sJa, %%2$s ersetzt %%4$s. Ich möchte meine %s von %%4$s nach %%2$s migrieren.Ja, %2$s ist ein Duplikat von %4$s für Test-, Staging- oder Entwicklungszwecke.Ja, %2$s ist eine neue und andere Website, die von %4$s getrennt ist.Du hast schon einmal einen Testzeitraum in Anspruch genommen.Du bist einen Klick davon entfernt, deinen kostenlosen %1$s-Tage Testzeitraum des %2$s-Planes zu starten.Ihr seid alle gut!Du testest %s bereits.Du bist nur einen Schritt entfernt - %sDu kannst immer noch alle %s-Funktionen nutzen, aber du wirst keinen Zugang zu %s-Sicherheits- und Funktionsupdates und keinen Support bekommen.Du hast keine gültige Lizenz, um auf die Premium-Version zuzugreifen.Du hast eine %s Lizenz.Du hast eine %s-Lizenz erworben.Du hast deine %s erfolgreich aktualisiert.Du hast diese Website, %s, als temporäres Duplikat von %s markiert.Du hast diese Website, %s, als temporäres Duplikat dieser Websites markiertDu hast es vielleicht übersehen: du musst keine Daten teilen und kannst einfach das Opt-in %s.Du solltest in den nächsten 5 Minuten %1$s für %2$s an deine Mailbox um %3$s erhalten.Du solltest eine Bestätigungs-E-Mail für %1$s an dein Postfach unter %2$s erhalten. Bitte stelle sicher, dass du auf die Schaltfläche in dieser E-Mail an %3$s klickst.Du solltest eine Bestätigungs-E-Mail für %s an deine Mailbox unter %s erhalten. Bitte stelle sicher, dass du auf die Schaltfläche in dieser E-Mail an %s klickst.Du hast dich bereits für unser Nutzungs-Tracking entschieden, was uns hilft, %s weiter zu verbessern.Du hast Dich bereits für unser Nutzungs-Tracking entschieden, was uns hilft, die Benutzerfreundlichkeit weiter zu verbessern.Dein %s Add-on Plan wurde erfolgreich upgegradet.Deine %s kostenlose Testversion wurde erfolgreich storniert.Deine %s-Lizenz wurde als White-Label gekennzeichnet, um sensible Informationen vor dem WP Admin zu verbergen (z.B. deine E-Mail, Lizenzschlüssel, Preise, Rechnungsadresse und Rechnungen). Solltest du das wieder rückgängig machen wollen, kannst du das ganz einfach über dein %s tun. Wenn dies ein Fehler war, kannst du es auch %sDeine %s-Lizenz wurde erfolgreich deaktiviert.Dein WordPress-Benutzer: Vor- und Nachname und E-Mail-AdresseDein Konto wurde erfolgreich mit der %s-Lizenz aktiviert.Dein Affiliate-Antrag für %s wurde angenommen! Logge dich in deinen Affiliate-Bereich ein unter: %s.Dein Mitgliedskonto wurde vorübergehend gesperrt.Deine E-Mail wurde erfolgreich verifiziert - Glückwunsch!Deine kostenlose Testversion ist abgelaufen. %1$sUpgrade jetzt%2$s, um das %3$s weiterhin ohne Unterbrechungen zu nutzen.Dein kostenloser Testzeitraum ist abgelaufen. Du kannst aber weiterhin alle unsere kostenlosen Funktionen nutzen.Deine Lizenz wurde gelöscht. Wenn du denkst, dass es ein Fehler ist, kontaktiere bitte den Support.Deine Lizenz ist abgelaufen. %1$sUpgrade jetzt%2$s, um das %3$s weiterhin ohne Unterbrechungen zu nutzen.Deine Lizenz ist abgelaufen. Du kannst weiterhin alle %s Funktionen nutzen, aber du musst deine Lizenz erneuern, um weiterhin Updates und Support zu erhalten.Deine Lizenz ist abgelaufen. Du kannst das kostenlose %s trotzdem für immer weiter nutzen.Deine Lizenz wurde erfolgreich aktiviert.Deine Lizenz wurde erfolgreich deaktiviert, du bist zurück im %s Plan.Dein Name wurde erfolgreich aktualisiert.Dein Plan wurde erfolgreich aktiviert.Dein Plan wurde erfolgreich auf %s geändert.Dein Plan wurde erfolgreich upgegradet.Dein Server blockiert den Zugriff auf die API von Freemius, was für die Synchronisation von %1$s entscheidend ist. Bitte wende dich an deinen Host, um die folgenden Domains auf die Whitelist zu setzen: %2$sDein Abonnement wurde erfolgreich gekündigt. Dein %s Plan wird in %s ablaufen.Dein Testzeitraum wurde erfolgreich gestartet.PLZ / PostleitzahlDirekt anLizenz hier aktivierenAktiv%s kann nicht ohne %s laufen.%s kann ohne das Plugin nicht laufen.Kopf hocherlauben%s übrigAktivieren vonJahrAPIVerwerfenFehlersucheHerzlichen GlückwunschBlockiertVerbundenNeuester DownloadNeueste kostenlose Version herunterladenMonatlichVerfallsPfadE-Mail sendenmtl.JährlichJährlichEinmalPlanKein geheimer SchlüsselSDK VersionenLizenzSyncSynchronisiere LizenzAutorAusAnbasierend auf %sStarte die kostenlose TestversionSchließenSchließenschließe die Anmeldung abDatenTagedeaktivieredelegiereschicke mir %sKEINE%s Sicherheits- und Funktionsupdates, Bildungsinhalte und Angebote.%s PlanAbgerechnet wird %sBesteHalloHopplaHallo %s,StundeStundenInstalliertHurraLizenzWebseitenmsneue Beta Versionneue Versionnicht geprüftPreisPreisoptionalVersionProduktejetzt rückgängig machen.ses scheint, dass der von dir eingegebene Schlüssel nicht mit unseren Aufzeichnungen übereinstimmt.schicke mir Sicherheits- und Funktionsupdates, Bildungsinhalte und Angebote.überspringenHmmdie Testversion startenAbonnementveränderedie oben erwähnten Websitesdie neueste %s Version hiertesteTestLöschenDowngradeBearbeitenVersteckenAnmeldenAbmeldenKaufenAnzeigenÜberspringenUpdateUpgradevor %sPK!v.libraries/freemius/languages/freemius-ru_RU.monu[ S%A g s6_r#   '29AJRH[  *4CXkr5z  '  $.?F2 !H aj 0  !&!5!=!Q!Y!b! g!u!!!! ! !Y!2"A" R"^"g"l"(|"1"%""### +# 6#C#Y# a#k# p#{# # #######$-$ I$T$$ $%Y%a^%%%% % %; &F&K&R&Q' V' a' n'{'j'' ((3(K(~_(U(4)P)i)))-))S)D*'\**7*g*'+ -+9+L+b+u+ }+1+.+O+8,A,y,t-a--B-,=.j. o. |.. ... . ..4./ !/+///6/>/ E/Q/ X/ d/p//// ///%/+/0 00 >0/K0{0m0000 1)1:1W14`1151(112-2F2UZ221y3[34&4-4 =4G4(V4*4"414+4*+5&V5N}5555.5)!6K6[6{66 6 6666 6666W7f7o777777 7 757l8&p888 8888 88&8L9fj99 999 9: :: 2:?:P:: };\;;; <C,<p<<<d,=-== ==M=G'> o>y>>>>>E>>>??&?-?*CC&CZD.rD.D9DZ E3eE<EUE,FKF(GGHII=I$SIxIIII&I*J:JQJoJ3JJJJJK*0K1[KKK#KKKL L+LKLbL wLLLLLLL1M@MTM dM qM |MM MQMM NN4N GNSN bN lN vN N N N N N N NYN61TDhTTTT^TpPUVE\W4WWWW XX2XIX XXcX*Y.Y JYXYjYY"Y;Y)Y5)Z_ZwZ$Z6Z$Z"[]:[ [[[i[DJ\\\*\\\^65^Wl^>^_ `6`0``a#aAa^axa a@a a$b"*b/Mb}bb"c3cc d ddb9dxdee{eee9eef0"f Sfaf uff.ff0f&g;g7NgBgg g3g,.h[h!mh"i"i iiwj mkzk/kBk9 lxCll lYl-o9Co"}o o-oopppyp5Jqq{r6as9s;s^tQmttt,u:uurvtv%w"+w3Nw%w w ww_wTExTxwxlgzz({{ d|n|hH}}(}$}6~J~ d~r~"~!~'~b~4T" ; () R_v' =ƀ*w0zB#9fb4. cpT9քUud !Nk)bFv\A Q/_" ŋ/1(Zn #@3tQJ֍!;> z5 ώ3$$I ]i,A,Őؐ ܐ)' BqNO46K( (ۓ8=ޔ *Õ+ $:N:m5ޘ%ș4 >88*B؜ *)TodnԝC` r }Ğ.\2;!#KEL(:97/q$8Ƣ T1 ХP%X~5/Ԧ IX!0GR8Ө'Y.9`Ǫt(T&|{H˭L<:ưNGP> ױ#;Xj!߲.*Y m T4E`w!, )1[k{  ƵԵܵ 1E N\c*k-޶ &BTp ̷%s - plugin name. As complete "PluginX" activation nowComplete "%s" Activation Now%s Add-on was successfully purchased.%s Installs%s Licenses%s ago%s commission when a customer purchases a new license.%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license.%s is a premium only add-on. You have to purchase a license first before activating the plugin.%s is the new owner of the account.%s minimum payout amount.%s or higher%s rating%s ratings%s sec%s star%s stars%s time%s times%s tracking cookie after the first visit to maximize earnings potential.APIAccountAccount DetailsActionsActivateActivate %sActivate %s PlanActivate Free VersionActivate LicenseActivate this add-onActivatedAdd Ons for %sAdd Ons of module %sAdd another domainAdd-OnAdd-OnsAdd-on must be deployed to WordPress.org or Freemius.Address Line %dAffiliateAffiliationAfter your free %s, pay as little as %sAgree & Activate LicenseAll RequestsAll TypesAllow & ContinueAmountAn automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now.Anonymous feedbackApply to become an affiliateAre you sure you want to delete all Freemius data?Are you sure you want to proceed?As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days.Auto installation only works for opted-in users.Auto renews in %sAutomatic InstallationAverage RatingAwesomeBecome an affiliateBillingBlockingBodyBusiness nameCan't find your license key?CancelCancel InstallationCancel SubscriptionCancel TrialCancelledCancelling the trial will immediately block access to all premium features. Are you sure?Change LicenseChange OwnershipChange PlanCheckoutCityClear API CacheClick here to use the plugin anonymouslyClick to see reviews that provided a rating of %sClick to view full-size screenshot %dCodeCompatible up toContactContact SupportContact UsContributorsCouldn't activate %s.CountryCron TypeDateDeactivateDeactivate LicenseDeactivationDebug LogDelete All AccountsDetailsDon't have a license key?Donate to this pluginDownloadDownload %s VersionDownload the latest %s versionDownload the latest versionDownloadedDue to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.EmailEmail addressEndEnter the domain of your website or other websites from where you plan to promote the %s.Enter the email address you've used for the upgrade below and we will resend you the license key.ErrorError received from the server:ExpiredExpires in %sExtra DomainsExtra domains where you will be marketing the product from.FileFilterFor compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.FreeFree TrialFree versionFreemius APIFreemius DebugFreemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error.Freemius StateFull nameFunctionGet commission for automated subscription renewals.Have a license key?Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!How do you like %s so far? Test all our %s premium features with a %d-day free trial.How to upload and activate?How will you promote us?I can't pay for it anymoreI couldn't understand how to make it workI don't like to share my information with youI found a better %sI have upgraded my account but when I try to Sync the License, the plan remains %s.I no longer need the %sI only needed the %s for a short periodIDIf you have a moment, please let us know why you are %sIf you would like to give up the ownership of the %s's account to %s click the Change Ownership button.In %sInstall NowInstall Update NowInstalling plugin: %sInvalid module ID.InvoiceIs ActiveIt looks like the license could not be activated.It looks like the license deactivation failed.It looks like you are not in trial mode anymore so there's nothing to cancel :)It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry.It looks like your site currently doesn't have an active license.It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.It's not what I was looking forJust letting you know that the add-ons information of %s is being pulled from an external server.KeyKindly share what didn't work so we can fix it for future users...Kindly tell us the reason so we can improve.LastLast UpdatedLast licenseLatest Version InstalledLearn moreLengthLicenseLicense KeyLicense keyLifetimeLike the %s? Become our ambassador and earn cash ;-)Load DB OptionLocalhostLogLoggerMessageMethodMobile appsModuleModule PathModule TypeMore information about %sNameNewNewer Version (%s) InstalledNewsletterNextNo IDNo commitment for %s - cancel anytimeNo commitment for %s days - cancel anytime!No credit card requiredNo expirationNon-expiringNone of the %s's plans supports a trial period.O.KOnce your license expires you can still use the Free version but you will NOT have access to the %s features.Opt InOpt OutOtherPCI compliantPaid add-on must be deployed to Freemius.PayPal account email addressPaymentsPayouts are in USD and processed monthly via PayPal.PlanPlan %s do not exist, therefore, can't start a trial.Plan %s does not support a trial period.Plan IDPlease contact us herePlease contact us with the following message:Please download %s.Please enter the license key that you received in the email right after the purchase:Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential).Please follow these steps to complete the upgradePlease provide details on how you intend to promote %s (please be as specific as possible).Please provide your full name.PluginPlugin HomepagePlugin IDPlugin InstallPlugin installer section titleChangelogPlugin installer section titleDescriptionPlugin installer section titleFAQPlugin installer section titleFeatures & PricingPlugin installer section titleInstallationPlugin installer section titleOther NotesPlugin installer section titleReviewsPlugin is a "Serviceware" which means it does not have a premium code version.PluginsPlugins & Themes SyncPremiumPremium %s version was successfully activated.Premium add-on version already installed.Premium versionPremium version already active.PricingPrivacy PolicyProcess IDProcessingProgram SummaryPromotion methodsProvincePublic KeyPurchase LicenseQuotaRe-send activation emailRefer new customers to our %s and earn %s commission on each successful sale you refer!RequestsRequires WordPress VersionResultSDKSDK PathSave %sScheduled CronsScreenshotsSecret KeySecure HTTPS %s page, running from an external domainSeems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.Seems like you got the latest release.Select CountrySend License KeySet DB OptionSingle Site LicenseSite IDSitesSkip & %sSlugSocial media (Facebook, Twitter, etc.)Sorry for the inconvenience and we are here to help if you give us a chance.Sorry, we could not complete the email update. Another user with the same email is already registered.StartStart TrialStart my free %sStateSubmit & %sSupportSupport ForumSync Data From ServerTax / VAT IDTerms of ServiceThank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days.Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information.Thanks %s!Thanks for confirming the ownership change. An email was just sent to %s for final approval.The %s broke my siteThe %s didn't workThe %s didn't work as expectedThe %s is great, but I need specific feature that you don't supportThe %s is not workingThe %s suddenly stopped workingThe installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page.The remote plugin package does not contain a folder with the desired slug and renaming did not work.The upgrade of %s was successfully completed.ThemeTheme SwitchThemesThis plugin has not been marked as compatible with your version of WordPress.This plugin has not been tested with your current version of WordPress.TimestampTitleTotalTownTrialTypeUnable to connect to the filesystem. Please confirm your credentials.Unlimited LicensesUnlimited UpdatesUnlimited commissions.Up to %s SitesUpdateUpdate LicenseUpdates, announcements, marketing, no spamUpgradeUpload and activate the downloaded versionUsed to express elation, enthusiasm, or triumph (especially in electronic communication).W00tUser IDUsersValueVerification mail was just sent to %s. If you can't find it after 5 min, please check your spam box.VerifiedVerify EmailVersion %s was released.View detailsView paid featuresWarningWe can't see any active licenses associated with that email address, are you sure it's the right address?We couldn't find your email address in the system, are you sure it's the right address?We made a few tweaks to the %s, %sWebsite, email, and social media statistics (optional)What did you expect?What feature?What is your %s?What price would you feel comfortable paying?What you've been looking for?What's the %s's name?Where are you going to promote the %s?WordPress.org Plugin PageYes - %sYou already utilized a trial before.You are 1-click away from starting your %1$s-day free trial of the %2$s plan.You are all good!You are already running the %s in a trial mode.You are just one step away - %sYou do not have a valid license to access the premium version.You have a %s license.You have successfully updated your %s.You might have missed it, but you don't have to share any data and can just %s the opt-in.Your %s Add-on plan was successfully upgraded.Your %s free trial was successfully cancelled.Your account was successfully activated with the %s plan.Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s.Your affiliation account was temporarily suspended.Your email has been successfully verified - you are AWESOME!Your license has been cancelled. If you think it's a mistake, please contact support.Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support.Your license has expired. You can still continue using the free %s forever.Your license was successfully activated.Your license was successfully deactivated, you are back to the %s plan.Your name was successfully updated.Your plan was successfully changed to %s.Your plan was successfully upgraded.Your trial has been successfully started.ZIP / Postal Codea positive responseRight onaddonX cannot run without pluginY%s cannot run without %s.addonX cannot run...%s cannot run without the plugin.advance notice of something that will need attention.Heads upas 5 licenses left%s leftas activating pluginActivatingas annual periodyearas application program interfaceAPIas close a windowDismissas code debuggingDebuggingas congratulationsCongratsas connection blockedBlockedas connection was successfulConnectedas download latest versionDownload Latestas every monthMonthlyas expiration dateExpirationas file/folder pathPathas in the process of sending an emailSending emailas monthly periodmoas once a yearAnnualas once a yearAnnuallyas once a yearOnceas product pricing planPlanas secret encryption key missingNo Secretas software development kit versionsSDK Versionsas software licenseLicenseas synchronizeSyncas synchronize licenseSync Licenseas the plugin authorAuthoras turned offOffas turned onOnbased on %scall to actionStart free trialclose a windowDismissclose windowDismissdeactivatinge.g. Professional Plan%s Plane.g. billed monthlyBilled %se.g. the best productBestexclamationHeyexclamationOopsgreetingHey %s,interjection expressing joy or exuberanceYee-hawlike websitesSitesmillisecondsmsnot verifiednounPricenounPricingproduct versionVersionsecondssecsomething somebody says when they are thinking about what you have just said.Hmmstart the trialswitchingthe latest %s version heretrial periodTrialverbDeleteverbDowngradeverbEditverbHideverbOpt InverbOpt OutverbPurchaseverbShowverbSkipverbUpdateverbUpgradex-ago%s agoProject-Id-Version: WordPress SDK Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues PO-Revision-Date: 2015-10-01 21:38+0000 Last-Translator: Leo Fajardo , 2021 Language-Team: Russian (Russia) (http://app.transifex.com/freemius/wordpress-sdk/language/ru_RU/) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Language: ru_RU Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3); X-Poedit-Basepath: .. X-Poedit-KeywordsList: get_text_inline;get_text_x_inline:1,2c;$this->get_text_inline;$this->get_text_x_inline:1,2c;$this->_fs->get_text_inline;$this->_fs->get_text_x_inline:1,2c;$this->fs->get_text_inline;$this->fs->get_text_x_inline:1,2c;$fs->get_text_inline;$fs->get_text_x_inline:1,2c;$this->_parent->get_text_inline;$this->_parent->get_text_x_inline:1,2c;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c X-Poedit-SearchPath-0: . X-Poedit-SearchPathExcluded-0: *.js X-Poedit-SourceCharset: UTF-8 Закончить активацию %s сейчас Покупка %s плагина успешно состоялась%s установок %s лицензий %s тому назад %s вознаграждения, если клиент купит новую лицензию.Бесплатный период пользования %s закончился. Этот плагин является премиум продуктом и он был деактивирован автоматически. Если Вы планируете дальнейшее его использование, пожалуйста купите лицензию. %s является премиум продуктом. Необходимо купить лицензию перед активацией плагина. %я является новым владельцем аккаунта%s минимальная сумма выплаты %s или выше%s оценка %s оценки %s секунд %звездочка %s звездочки %s время %s раз %s данные cookies предоставляются после первого посещения, чтобы максимально увеличить вероятность заработка. APIЛичный кабинет ДеталиДействия Активировать Активировать %sАктивируйте план %sАктивировать бесплатную версию?Активировать лицензиюАктивируйте этот функционал Активирован Функционал для %sФункционал модуля %sДобавьте другое доменное имя Функционал плагина Настройки плагина Функционал должен быть заявлен на WordPress.org или Freemius Поле для адреса %dПартнерПартнерство После окончания Вашего бесплатного %s, платите всего лиш %sСогласиться и активировать лицензию Все запросы Все типыРазрешить и продолжитьКоличество Автоматическое скачивание и установка %s ( платной версии) %s начнется через %s. Если Вы хотите все сделать в ручном режиме, нажмите на кнопку "Отменить" сейчас. Анонимный отзыв Подать заявку на партнерство Вы уверенны, что хотите удалить все данные Freemius?Вы уверены, что хотите продолжить?Мы выделяем 30 дней для поступления возвратов и поэтому вознаграждения выплачиваются за покупки, которые были совершены более чем 30 дней назад.Авто установка работает только для зарегистрированных пользователей.Автоматическое продление в %sАвтоматическая установка Средний рейтинг Отлично!Стать партнеромСистема оплаты Блокирование Основная часть Название бизнеса Не можете найти лицензионный ключ? Отмена Отменить установку Отменить подписку Отменить тестовый период Аннулирована Отказ от пользования тестовым периодом автоматически блокирует доступ ко всем премиум возможностям. Вы уверены, что хотите отказаться?Изменить лицензию Сменить владельца лицензии Изменить план Оплата Город Очистить кэш APIНажмите здесь, чтобы пользоваться плагином анонимно. Нажмите, чтобы посмотреть отзывы, которые сформировали рейтинг %sКликните, чтобы посмотреть снимок %d на широком экране. КодСовместима с Свяжитесь с намиСвязаться со службой поддержкиКонтакты Контрибьюторы Невозможно активировать %sСтрана Тип задачиДата Деактивировать Деактивировать лицензию ДеактивацияЖурнал устранения ошибок Удалить все аккаунтыДетальнейУ Вас нет лицензионного ключа?Инвестировать в разработку плагина Скачать Скачайте версию %sСкачайте последнюю версию %sСкачай последнюю версиюЗагружен Из-за нарушения условий партнерства мы вынуждены временно заблокировать Ваш аккаунт. Если у Вас возникли вопросы, пожалуйста, обратитесь в службу поддержки. Электронный адрес Электронный адрес Конец Введите домен Вашего сайта или других сайтов на которых Вы намерены продвигать %s.Введите ниже адрес своей электронной почты, которую Вы использовали для обновлений и мы Вам отправим повторно Ваш лицензионный ключ. ОшибкаОшибка сервераСрок действия закончился Окончание срока пользования через %sДополнительные доменные имена Дополнительные доменные имена, где Вы будете продвигать продукт. ФайлФильтр В соответствии с руководством WordPress.org, перед началом тестового периода мы просим, чтобы Вы присоединились к нашему сообществу предоставив информацию о Вашем сайте и также Ваши личные данные, тем самым разрешив %s периодически отправлять сообщения на %s для уведомлений об обновлениях и подтверждения Вашего тестового периода. Бесплатная Бесплатный период пользования Бесплатная версия Freemius APIИсправление ошибок FreemiusFreemius SDK не удалось найти основной файл плагина. Пожалуйста, свяжитесь с sdk@freemius.com с текущей ошибкой.Cостояние Freemius Полное имяФункция Получай вознаграждение за автоматические продления пользования. У Вас есть лицензионный ключ?Привет! Знали ли Вы, что %s предоставляет реферальную программу? Если Вам нравится %s, Вы можете стать нашим представителем и зарабатывать!Тебе нравится пользоваться %s? Воспользуйся всеми нашими премиум возможностями на протяжении %d - дневного тестового периода. Как загрузить и активировать?Как Вы намерены продвигать нас?Я больше не могу оплачивать это. Я не могу понять как сделать так, чтобы оно работалоЯ не хочу делиться личной информацией с ВамиЯ нашел лучший %sЯ провел апгрейд аккаунта, но при попытке синхронизировать лицензию, мой тарифный план не меняется. %s больше не понадобится.%s требовалась на короткое времяIDЕсли у Вас есть время, пожалуйста, сообщите причину почему Вы %sЕсли Вы передаете права пользования аккаунтом %s %s нажмите кнопку " Сменить права использования"В %sУстановить сейчас Провести обновления сейчас Установка плагина: %sНеверный ID модуляСчет активный Вероятно возникли трудности с активацией лицензии. Вероятно деактивация лицензии не состоялась. Возможно, Ваш тестовый период уже закончился. Вероятно Вы все еще пользуетесь сервисом согласно плану %s. Если Вы обновляли или меняли свой тарифный план, то вероятно существуют какие-то трудности связанные с Вашим программным обеспечением. Извините. Вероятно Ваш сайт не использует активную лицензию сейчас. Вероятно один из параметров является неверным. Обновите свой Public Key, Secret Key&User ID и повторите попытку.Это не то, что я искал. Сообщаем, что информация о дополнительных настройках %s предоставляется со стороннего сервера. Ключ Пожалуйста, сообщите о функционале, который не работает, чтобы мы смогли исправить его для дальнейшего использования. Пожалуйста, укажите причину, чтобы мы могли исправиться. Последний Последнее обновление Последняя лицензия Последняя версия установленаУзнать большеДлинна Лицензия Лицензионный ключ Лицензионный ключНа бессрочный период Вам нравится %s? Стань нашим партнером и зарабатывай ;-)Загрузить опцию базы данных Локальный хостинг Журнал изменений Программа сохранения изменений Сообщение Метод Мобильные приложения МодульПуть модуля Тип модуля Больше информации о %sИмяНовое Более новая версия %s установлена Рассылка Следующий No IDБез обязательств платить %s - аннулируй пользование в любое время Бесплатное пользование на протяжении %s дней. Отмена в любое время. Не требуются данные платежной картыБессрочный период пользования Бессрочный Тарифные планы %s не предусматривают тестовый период. O.K.По окончанию срока действия Вашей лицензии, Вы сможете пользоваться бесплатной версией, но у Вас не будет доступа к возможностям %s. ПрисоединитьсяОтказаться от использованияДругиеЖалоба PCIПлатный функционал должен быть заявлен в FreemiusЭлектронный адрес аккаунта PayPalПлатежиВыплаты производятся в долларах США через PayPal.Тарифный план Тарифного плана %s не существует, поэтому Вы не можете начать тестовый период. Тарифный план %s не предусматривает тестового периода. ID тарифного плана Пожалуйста, напишите нам сообщение здесь. Пожалуйста, напишите нам сообщение следующего содержания:Пожалуйста, скачайте %sПожалуйста введите лицензионный ключ, который Вы получили на электронный адрес сразу после покупки. Пожалуйста, предоставьте соответственную статистику вебсайта или страницы социальных сетей, например, количество уникальных посетителей, количество подписчиков, читателей, т. д. ( эта информация останется конфиденциальной). Пожалуйста, пройдите эти шаги для того, чтобы произвести апгрейдПожалуйста, предоставьте максимально детальную информацию о том, как Вы планируете продвигать %s.Пожалуйста, введите Ваше полное имяПлагин Главная страница плагина ID плагина Установка плагина Журнал изменений Описание Часто задаваемые вопросы Функционал&тарифные планы Установка Другие заметки Отзывы Плагин является 'Serviсeware'. Это означает, что он не имеет премиум версию кода. Плагины Синхронизация плагинов и шаблонов Премиум Премиум версия %s была успешно активирована. Премиум версия плагина была установленаПремиум версия Премиум версия уже активированаЦены Политика КонфиденциальностиID процесса Обработка данных Краткое описание программы Методы продвижения Провинция Public Key Купите лицензию Выделенный объем памятиОтправить письмо активации еще раз Порекомендуй %s новым пользователям и зарабатывай %s c каждой успешной продажи. Запросы Необходима версия WordPress РезультатSDKпуть SDKЭкономия %sСпланированные задачиСнимки экрана Secret Key Безопасная страница HTTPS %s воспроизводится с внешнего ресурса К сожалению у нас возникли трудности с отменой Вашего тестового периода. Пожалуйста, повторите попытку через несколько минут.Вероятно, Вы пользуетесь последней версиейВыбрать страну Отправить лицензионный ключУстановить опцию базы данных Лицензия на один сайт Site IDСайтов Пропустить & %sОписательная часть URL Социальные сети ( Facebook, Twitter, etc.)Извините за неудобство. Мы будем рады помочь, если Вы нам предоставите эту возможность. Извините, нам не удалось обновить электронный адрес. Другой пользователь с таким же адресом уже был зарегистрирован. НачалоНачать тестовый периодНачать мой бесплатный %sШтат Отправить&%sПоддержка Форум поддержки Синхронизация данных с сервера ID налога/НДС Пользовательское соглашениеСпасибо за подачу заявки на партнерство. К сожалению, мы приняли решение отказать Вам в этой возможности. Пожалуйста, повторите попытку через 30 дней. Спасибо за подачу заявки на партнерство. Мы рассмотрим Ваши данные на протяжении следующих 14 дней и свяжемся с Вами. Спасибо %sСпасибо, что подтвердили изменение прав использования. Вам отправлено письмо на %s для окончательного подтверждения. %s повредила мой сайт%s не сработала%s не сработала как ожидалось%s отличная возможность, но мне нужен определенный функционал, который вы не поддерживаете. %s не работает%s внезапно перестала работать Процесс установки уже начат и может занять несколько минут. Пожалуйста, подождите окончания процесса и не обновляйте эту страницу. Удаленный пакет плагинов не содержит папку с нужным описанием URL и смена имени не срабатывает. Обновление %s было успешно завершеноШаблон Переключатель шаблона Шаблоны Этот плагин не отмечен как совместимый з Вашей версией WordPress Этот плагин не был тестирован с Вашей текущей версией WordPress. Маркер времени Название ИтогоНаселенный пункт Тестовый период ТипНевозможно присоединиться к системе файлов. Пожалуйста, подтвердите свои данные. Неограниченная лицензия Неограниченные обновления Неограниченное вознаграждение до %s сайтов Обновить Обновить лицензиюНовости, объявления, маркетинг, без спамаСделать апгрейд Загрузите и активируйте скачанную версиюВау!User ID Пользователи Значение Письмо подтверждение было только что отправлено на %s. Если Вы не получите его через 5 минут, пожалуйста, проверьте папку спам.Подтвержден Подтвердите электронный адрес Релиз версии %s состоялся. Смотреть детальней Просмотр платных возможностейПредупреждение Активная лицензия выданная на этот электронный адрес не была найдена. Вы уверены, что предоставили правильный электронный адрес?К сожалению, Ваш почтовый адрес не найден в системе. Вы уверены, что предоставили правильный адрес? Мы усовершенствовали в %s, %s для лучшей работы Вебсайт, электронный адрес и статистика социальных сетей (не обязательно)Каковы были Ваши ожидания? Какой функционал?Какой Ваш %s?Какая стоимость была бы для Вас приемлемой? Что именно Вы ищите? Какое название %s?Где Вы намерены продвигать %s?Страница плагинов WordPress.orgДа - %sВы уже использовали Ваш тестовый периодВы уже на расстоянии одного клика от начала Вашего бесплатного %1$s - дневного тестового периода по тарифному плану %2$s. Все прошло хорошо!Вы уже пользуетесь тестовой версией %s Вам осталось совсем немножко %sУ Вас нет необходимых лицензионных прав для пользования премиум версиейУ Вас есть лицензия %s.Вы успешно обновили Ваш %sВозможно, Вы не обратили внимание, но Вы не обязаны делиться никакими данными и можете просто %s кнопку "Присоединиться". Ваш %s план был успешно обновленВаш бесплатный тестовый период был успешно отменен. Ваша учетная запись была успешно активирована согласно плану %sВаша заявка на партнерство с %s принята! Войдите в Ваш кабинет партнера на %sВаш партнерский аккаунт временно недоступен. Ваш электронный адрес был успешно подтвержден и Вы просто молодец!Ваша лицензия была аннулирована. Если Вы считаете, что это ошибка, пожалуйста свяжитесь с нашей службой поддержки. Срок действия Вашей лицензии закончен. Вы можете продолжать пользоваться всеми возможностями %s продлив Вашу лицензию. Вы также будете получать доступ к обновлениям и поддержке. Срок действия Вашей лицензии закончился. Вы можете продолжать пользоваться бесплатной версией %s на бессрочной основе.Ваша лицензия была успешно активирована. Ваша лицензия была успешно деактивирована и Вы снова пользуетесь планом %s.Ваше имя было успешно обновленоВаш тарифный план был успешно изменен на %s.Ваш тарифный план был успешно изменен. Ваш тестовый период успешно начатИндекс Все верно!%s не работает без %s.%s не может работать без плагина. Внимание!Осталось %s Активация на один год APIЗакрыть Устранение ошибокПоздравления! Заблокировано Соединено Скачать последнюю версиюПомесячно Срок пользования Путь Электронное письмо отправляется Вам на почту на один месяцЕжегодно Один раз в год Один раз Тарифный план Нет секрета Версии SDKЛицензия Синхронизировать Синхронизация лицензии АвторВыключить Включить Основан на %sНачни тестовый период!Закрыть Закрыть Деактивация %s план Оплачивать %sЛучший Привет!Упс!Здравствуйте %sУра!Сайтов мс не подтвержден Стоимость ЦеныВерсия секХм...Начать тестовый периодПереключение Последняя версия %s здесьТестовый периодУдалитьПонизить план Редактировать Спрятать Присоединится Отписаться КупитьПоказать ПропуститьОбновить Сделать апгрейд%s тому назад PK!.,.libraries/freemius/languages/freemius-fr_FR.monu[T A S %#! I! U!a!h!6{!!_g"#"" # # #'#.#6#?#G#@P#H##O#=$A$`$$$$$ $$$$$&%-)%W% l%v%%%%%5%%% & &' &H& a& n&x&o&&'''"''2(!A(ac(0(()).)6)J)R)[)c) h)v) ))))) _*j*~* * * ***Y*>+M+ ^+j+s+x++(+1+%+:",],b,s,{, , ,,, ,, ,,x,g- - ..+.?.tG..../ //>/ Z/e/[/fY00 00Y0a21111 1 1;122&2%3 *3 53 B3O3j^33 33334~34U45$5=5)X5-55S56'06X6M[676g6pI777y7T8m8 8888 88 819.:9Oi99A9:y{::a;w;B{;,;; ; ; <(< A<L<S<[< m< y<<<4<< <<<<= = '=3= := F=R=l= q= ~===!== ====%=+#>O> g> u>/>>m>~$????? ?? ? ?)@,@I@4R@@5@(@@@- A8AULAA1kB~BC[9DDDD DD(D* E"8E1[E+E*E&EN FZFbFxF.F)FFF GG G (G 3G>GGGWGiG rG}GGGGWG H"H9HBH]HdHhHqHyH HH H5HsHl\I&III JJ7JPJdJlJJ JJ&JLJfKxK ~KKK K KK KK KKLL/1MaM)M M M\MN3NFNCeNNNNdeO-OO O PP'1PMYPGP PPPQ QQEQ[QnQQQQQ*QQ*Q^RyRRRdRR RS !S.SASiISWS" TB.T6qTT TT-T U(U&>UeUUU$UMUU/VAVoaV>VW&'WZNWTWRW.QX.X9XZX3DY<xYbYPZUiZ_Z[K[(\G/\#w\)\$\U\)@]j]|];]6]> ^K^Q^l^^$^^^^_&7_*^_7____3`C`X`n```*`1`a0a#Dahaaa aaaa a bNbcbbbbbb1bc'c;c Kc Wc dc oc|c ccQcc d dd9d?d Rd^d md wd d d d d d d d d dhi"Uj!xjj j jj>jkbk*Blml l l ll l lllilZImmWmn n)n2n9nLnTn \ngn}nnn1n1n0oCoKo[opoooBooo o p* p8pUpipxp~p qqq&q- r!;rF]r!r}rXDs#ss s ss ttt#t(t7tVt+jtttt yuuuuuuuunvvvvvvv$v/ w2~M~Og~~~T~ #1 BOxc܀ KOo]!]*0ɂ2-gI1˃P;Qq((;7d$ֆ/ ; C2M9U5 OsM{Ɋъ/*GV_gz?- ,6:AI"Ru#ތ#54 j  6>$AR4b$ %ɏ  57D| PT1S1XX:C~]/n$!( ;H _ iuy  Z)$N7V5ėԗ #3GPf} ˜xY#r ƙʙ *>8w-{0כ"+HW_z *[o  ɝ՝ݝ.!&H-ڟ(" *z5Ƞ&Y c,{}R3Т  (.HawG٣ !+17=NYSǤ /3 c1p ƥ LV%h" æxͦjF0SP6&Ҩ*'Ros.|c0%VuF#j"d<b)/.=^k6D?wgwkY$qP-(4W]( ޲ #* E S]l  "+ &=BIPYay  ĴѴ q Ƶɵ ڵ * 5@_e v  ɶ ж ݶ %s to access version %s security & feature updates, and support.%s - plugin name. As complete "PluginX" activation nowComplete "%s" Activation Now%s Add-on was successfully purchased.%s Installs%s Licenses%s ago%s and its add-ons%s commission when a customer purchases a new license.%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license.%s is a premium only add-on. You have to purchase a license first before activating the plugin.%s is the new owner of the account.%s minimum payout amount.%s or higher%s rating%s ratings%s sec%s star%s stars%s time%s times%s to access version %s security & feature updates, and support.%s tracking cookie after the first visit to maximize earnings potential.%s's paid features%sClick here%s to choose the sites where you'd like to activate the license on.APIASCII arrow left icon←ASCII arrow right icon➤AccountAccount DetailsActionsActivateActivate %sActivate %s PlanActivate %s featuresActivate Free VersionActivate LicenseActivate license on all pending sites.Activate license on all sites in the network.Activate this add-onActivatedAdd Ons for %sAdd Ons of module %sAdd another domainAdd-OnAdd-OnsAdd-on must be deployed to WordPress.org or Freemius.AddressAddress Line %dAffiliateAffiliationAfter your free %s, pay as little as %sAgree & Activate LicenseAll RequestsAll TypesAllow & ContinueAlternatively, you can skip it for now and activate the license later, in your %s's network-level Account page.AmountAn automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now.Anonymous feedbackApply on all pending sites.Apply on all sites in the network.Apply to become an affiliateAre you sure you want to delete all Freemius data?Are you sure you want to proceed?As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days.Auto installation only works for opted-in users.Auto renews in %sAutomatic InstallationAverage RatingAwesomeBecome an affiliateBillingBlockingBlog IDBodyBusiness nameBuy a license nowBuy licenseCan't find your license key?CancelCancel %s & ProceedCancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site.Cancel %s?Cancel InstallationCancel SubscriptionCancel TrialCancelledCancelling %sCancelling %s...Cancelling the subscriptionCancelling the trial will immediately block access to all premium features. Are you sure?Change LicenseChange OwnershipChange PlanCheckoutCityClear API CacheClear Updates TransientsClick here to use the plugin anonymouslyClick to see reviews that provided a rating of %sClick to view full-size screenshot %dClone resolution admin notice products list labelProductsCodeCompatible up toContactContact SupportContact UsContributorsCouldn't activate %s.CountryCron TypeDateDeactivateDeactivate LicenseDeactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site.Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?DeactivationDebug LogDelegate to Site AdminsDelete All AccountsDetailsDon't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support.Don't have a license key?Donate to this pluginDowngrading your planDownloadDownload %s VersionDownload the latest %s versionDownload the latest versionDownloadedDue to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.During the update process we detected %d site(s) that are still pending license activation.During the update process we detected %s site(s) in the network that are still pending your attention.EmailEmail addressEndEnter the domain of your website or other websites from where you plan to promote the %s.Enter the email address you've used for the upgrade below and we will resend you the license key.ErrorError received from the server:ExpiredExpires in %sExtra DomainsExtra domains where you will be marketing the product from.FileFilterFor compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.FreeFree TrialFree versionFreemius APIFreemius DebugFreemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error.Freemius StateFull nameFunctionGet commission for automated subscription renewals.Have a license key?Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!How do you like %s so far? Test all our %s premium features with a %d-day free trial.How to upload and activate?How will you promote us?I can't pay for it anymoreI couldn't understand how to make it workI don't like to share my information with youI found a better %sI have upgraded my account but when I try to Sync the License, the plan remains %s.I no longer need the %sI only needed the %s for a short periodIDIf you click it, this decision will be delegated to the sites administrators.If you have a moment, please let us know why you are %sIf you would like to give up the ownership of the %s's account to %s click the Change Ownership button.If you'd like to use the %s on those sites, please enter your license key below and click the activation button.Important Upgrade Notice:In %sIn case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?Install Free Version NowInstall Free Version Update NowInstall NowInstall Update NowInstalling plugin: %sInvalid module ID.Invalid site details collection.InvoiceIs ActiveIt looks like the license could not be activated.It looks like the license deactivation failed.It looks like you are not in trial mode anymore so there's nothing to cancel :)It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry.It looks like your site currently doesn't have an active license.It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.It's not what I was looking forJust letting you know that the add-ons information of %s is being pulled from an external server.KeyKindly share what didn't work so we can fix it for future users...Kindly tell us the reason so we can improve.LastLast UpdatedLast licenseLatest Free Version InstalledLatest Version InstalledLearn moreLengthLicenseLicense AgreementLicense KeyLicense keyLicense key is empty.LifetimeLike the %s? Become our ambassador and earn cash ;-)Load DB OptionLocalhostLogLoggerMessageMethodMigrate Options to NetworkMobile appsModuleModule PathModule TypeMore information about %sNameNetwork BlogNetwork UserNewNew Version AvailableNewer Free Version (%s) InstalledNewer Version (%s) InstalledNewsletterNextNoNo IDNo commitment for %s - cancel anytimeNo commitment for %s days - cancel anytime!No credit card requiredNo expirationNon-expiringNone of the %s's plans supports a trial period.O.KOnce your license expires you can still use the Free version but you will NOT have access to the %s features.Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license.Opt InOpt OutOpt in to make "%s" better!OtherOwner EmailOwner IDOwner NamePCI compliantPaid add-on must be deployed to Freemius.PayPal account email addressPaymentsPayouts are in USD and processed monthly via PayPal.PlanPlan %s do not exist, therefore, can't start a trial.Plan %s does not support a trial period.Plan IDPlease contact us herePlease contact us with the following message:Please download %s.Please enter the license key that you received in the email right after the purchase:Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential).Please follow these steps to complete the upgradePlease let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.Please provide details on how you intend to promote %s (please be as specific as possible).Please provide your full name.PluginPlugin HomepagePlugin IDPlugin InstallPlugin installer section titleChangelogPlugin installer section titleDescriptionPlugin installer section titleFAQPlugin installer section titleFeatures & PricingPlugin installer section titleInstallationPlugin installer section titleOther NotesPlugin installer section titleReviewsPlugin is a "Serviceware" which means it does not have a premium code version.PluginsPlugins & Themes SyncPremiumPremium %s version was successfully activated.Premium add-on version already installed.Premium versionPremium version already active.PricingPrivacy PolicyProceedProcess IDProcessingProductsProgram SummaryPromotion methodsProvincePublic KeyPurchase LicenseQuick FeedbackQuotaRe-send activation emailRefer new customers to our %s and earn %s commission on each successful sale you refer!Renew licenseRenew your license nowRequestsRequires WordPress VersionResultSDKSDK PathSave %sScheduled CronsScreenshotsSearch by addressSecret KeySecure HTTPS %s page, running from an external domainSeems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes.Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.Seems like you got the latest release.Select CountrySend License KeySet DB OptionSimulate Network UpgradeSimulate Trial PromotionSingle Site LicenseSite IDSite successfully opted in.SitesSkip & %sSlugSocial media (Facebook, Twitter, etc.)Sorry for the inconvenience and we are here to help if you give us a chance.Sorry, we could not complete the email update. Another user with the same email is already registered.StartStart TrialStart my free %sStateSubmit & %sSubscriptionSupportSupport ForumSync Data From ServerTax / VAT IDTerms of ServiceThank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days.Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information.Thank you so much for using %s and its add-ons!Thank you so much for using %s!Thank you so much for using our products!Thank you!Thanks %s!Thanks for confirming the ownership change. An email was just sent to %s for final approval.The %s broke my siteThe %s didn't workThe %s didn't work as expectedThe %s is great, but I need specific feature that you don't supportThe %s is not workingThe %s suddenly stopped workingThe installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page.The remote plugin package does not contain a folder with the desired slug and renaming did not work.The upgrade of %s was successfully completed.ThemeTheme SwitchThemesThere is a %s of %s available.There is a new version of %s available.This plugin has not been marked as compatible with your version of WordPress.This plugin has not been tested with your current version of WordPress.TimestampTitleTotalTownTrialTypeUnable to connect to the filesystem. Please confirm your credentials.Unlimited LicensesUnlimited UpdatesUnlimited commissions.Up to %s SitesUpdateUpdate LicenseUpdates, announcements, marketing, no spamUpgradeUpload and activate the downloaded versionUsed to express elation, enthusiasm, or triumph (especially in electronic communication).W00tUser IDUsersValueVerification mail was just sent to %s. If you can't find it after 5 min, please check your spam box.VerifiedVerify EmailVersion %s was released.View detailsView paid featuresWarningWe can't see any active licenses associated with that email address, are you sure it's the right address?We couldn't find your email address in the system, are you sure it's the right address?We made a few tweaks to the %s, %sWe're excited to introduce the Freemius network-level integration.Website, email, and social media statistics (optional)What did you expect?What feature?What is your %s?What price would you feel comfortable paying?What you've been looking for?What's the %s's name?Where are you going to promote the %s?WordPress.org Plugin PageYesYes - %sYou already utilized a trial before.You are 1-click away from starting your %1$s-day free trial of the %2$s plan.You are all good!You are already running the %s in a trial mode.You are just one step away - %sYou can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.You do not have a valid license to access the premium version.You have a %s license.You have successfully updated your %s.You might have missed it, but you don't have to share any data and can just %s the opt-in.You've already opted-in to our usage-tracking, which helps us keep improving the %s.You've already opted-in to our usage-tracking, which helps us keep improving them.Your %s Add-on plan was successfully upgraded.Your %s free trial was successfully cancelled.Your account was successfully activated with the %s plan.Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s.Your affiliation account was temporarily suspended.Your email has been successfully verified - you are AWESOME!Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your free trial has expired. You can still continue using all our free features.Your license has been cancelled. If you think it's a mistake, please contact support.Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support.Your license has expired. You can still continue using the free %s forever.Your license was successfully activated.Your license was successfully deactivated, you are back to the %s plan.Your name was successfully updated.Your plan was successfully changed to %s.Your plan was successfully upgraded.Your subscription was successfully cancelled. Your %s plan license will expire in %s.Your trial has been successfully started.ZIP / Postal Codea positive responseRight onaddonX cannot run without pluginY%s cannot run without %s.addonX cannot run...%s cannot run without the plugin.advance notice of something that will need attention.Heads upallowas 5 licenses left%s leftas activating pluginActivatingas annual periodyearas application program interfaceAPIas close a windowDismissas code debuggingDebuggingas congratulationsCongratsas connection blockedBlockedas connection was successfulConnectedas download latest versionDownload Latestas download latest versionDownload Latest Free Versionas every monthMonthlyas expiration dateExpirationas file/folder pathPathas in the process of sending an emailSending emailas monthly periodmoas once a yearAnnualas once a yearAnnuallyas once a yearOnceas product pricing planPlanas secret encryption key missingNo Secretas software development kit versionsSDK Versionsas software licenseLicenseas synchronizeSyncas synchronize licenseSync Licenseas the plugin authorAuthoras turned offOffas turned onOnbased on %scall to actionStart free trialclose a windowDismissclose windowDismissdeactivatingdelegatedo %sNOT%s send me security & feature updates, educational content and offers.e.g. Professional Plan%s Plane.g. billed monthlyBilled %se.g. the best productBestexclamationHeyexclamationOopsgreetingHey %s,interjection expressing joy or exuberanceYee-hawlicenselike websitesSitesmillisecondsmsnew versionnot verifiednounPricenounPricingproduct versionVersionsecondssecskipsomething somebody says when they are thinking about what you have just said.Hmmstart the trialsubscriptionswitchingthe latest %s version heretrialtrial periodTrialverbDeleteverbDowngradeverbEditverbHideverbOpt InverbOpt OutverbPurchaseverbShowverbSkipverbUpdateverbUpgradex-ago%s agoProject-Id-Version: WordPress SDK Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues PO-Revision-Date: 2015-10-01 21:38+0000 Last-Translator: Boris Colombier , 2018 Language-Team: French (France) (http://app.transifex.com/freemius/wordpress-sdk/language/fr_FR/) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Language: fr_FR Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2; X-Poedit-Basepath: .. X-Poedit-KeywordsList: get_text_inline;get_text_x_inline:1,2c;$this->get_text_inline;$this->get_text_x_inline:1,2c;$this->_fs->get_text_inline;$this->_fs->get_text_x_inline:1,2c;$this->fs->get_text_inline;$this->fs->get_text_x_inline:1,2c;$fs->get_text_inline;$fs->get_text_x_inline:1,2c;$this->_parent->get_text_inline;$this->_parent->get_text_x_inline:1,2c;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c X-Poedit-SearchPath-0: . X-Poedit-SearchPathExcluded-0: *.js X-Poedit-SourceCharset: UTF-8 %s pour accéder aux mises à jour de sécurité et de fonctionnalités de la version %s, et au support.Compléter "%s" Activer MaintenantL'Add-on %s a bien été acheté.%s Installations%s LicencesIl y a %s%s et ses add-onsCommission de %s quand un client achète une nouvelle licence.La période d'essai du %s a bien été annulé. L'add-on a été désactivé car il ne fonctionne qu'avec la version premium. Si vous souhaitez l'utiliser ultérieurement, vous devrez acheter une licence.%sest un add-on pour la version premium. Vous devez acheter une licence avant d'activer le plugin.%s est le nouveau propriétaire du compte.Montant de paiement minimum %s.%s ou plus%s notation%snotations %s sec%s étoile%s étoiles%s fois%s fois%s pour permettre les mises à jour de sécurité et de fonctionnalités de la version %s, et le support.Cookie de tracking de %s après la première visite pour maximiser les potentiels de gain.Fonctionnalités payantes de %s%sCliquez ici %s pour choisir les sites sur lesquels vous souhaitez activer la licence.API←➤CompteDétails du compteActionsActiverActiver %sActiver la formule %sActiver les fonctionnalités %sActivez la version gratuiteActiver la licenceActiver la licence sur tous les sites en attente.Activer la licence sur tous les sites du réseau.Activer cet add-onActivéAdd Ons pour %sAdd Ons du module %sAjouter une autre adresseAdd-OnAdd-OnsLes add-ons doivent être déposés sur WordPress.org ou Freemius.AdresseAdresse ligne %dAffiliationAffiliationAprès vos %s gratuits, payez seulement %sValider & Activer la licenceToutes les demandesTous les typesAutoriser & ContinuerÉventuellement, vous pouvez l'ignorer pour l'instant et activer la licence plus tard, sur votre page de compte du réseau %s.MontantUn téléchargement et une installation automatique de %s (version premium) de %s va commencer dans %s. Si vous voulez le faire manuellement, cliquez sur le bouton d'annulation maintenant.Commentaire anonymeActiver sur tous les sites en attente.Effectuer sur tous les sites dans le réseau.Postuler pour devenir un affiliéÊtes-vous sûr de vouloir supprimer toutes les données de Freemius ?Êtes-vous de vouloir continuer ?Comme nous bloquons sur 30 jours pour les remboursements éventuels, seules sont payées les commissions de plus de 30 jours.L'installation automatique ne fonctionne que pour les utilisateurs qui se sont inscrits.Renouvellements automatique dans %sInstallation automatiqueNote moyenneFormidableDevenir un affiliéFacturationBloquantBlog IDBodyRaison socialeAcheter une licence maintenantAcheter une licenceVous ne trouvez pas votre clef de licence ?AnnulerAnnuler %s et poursuivreAnnuler %s - Je n'ai plus besoin de mises à jour de sécurité et de fonctionnalités, ni de support pour %s parce que je n'ai pas l'intention d'utiliser le %s sur ce site, ou tout autre site.Annuler %s ?Annuler l'installationAnnuler l'abonnementAnnuler la période d'essaiAnnuléAnnulation de %sAnnulation de %s...Annuler votre abonnementAnnuler la période d'essai va immédiatement bloquer les fonctionnalités premium. Souhaitez-vous continuer ?Changer la licenceChangement De PropriétaireChanger de formulePaiementVilleVider le cache APIVider les transients de mise à jourCliquer ici pour utiliser le plugin anonymementCliquez pour voir les avis avec une notation de %sCliquez pour voir la capture d'écran %d en pleine tailleProduitsCodeCompatible jusqu'àContactContacter l'AssistanceContactez NousContributeursImpossible d'activer %s.PaysType de CronDateDésactiverDésactiver la licenceDésactiver ou désinstaller le %s désactivera automatiquement la licence, que vous pourrez utiliser sur un autre site.Désactiver la licence bloquera toutes les fonctionnalités premium mais vous permettra d'activer la licence sur un autre site. Êtes-vous sûr de vouloir continuer ?DésactivationDebug LogDéléguer aux administrateurs du siteSupprimer tous les comptesDétailsNe pas annuler %s - Je veux toujours recevoir les mises à jour de sécurité et de fonctionnalités, ainsi que d'être en mesure de contacter le support.Vous n'avez pas de clef de licence ?Faire une donation pour ce pluginRétrograder votre formuleTéléchargementTélécharger la version %sTélécharger la dernière version %sTélécharger la dernière versionTéléchargéSuite à une violation de nos conditions d'affiliation, nous avons décidé de bloquer temporairement votre compte d'affilié. Si vous avez la moindre question, merci de contacter le support.Durant le processus de mise à jour nous avons détecté %d site(s) toujours en attente d'activation de la licence.Durant le processus de mise à jour nous avons détecté %s site(s) dans le réseau que vous devez vérifier.EmailAdresse emailFinIndiquez l'adresse de votre site ou d'autres sites sur lesquels vous pensez faire la promotion du %sIndiquez ci-dessous l'adresse email que vous avez utilisez pour la mise à jour et nous allons vous renvoyer le code de la licence.ErreurUne erreur a été reçu depuis le serveur :ExpiréExpire dans %sAdresses supplémentairesAdresses supplémentaires depuis lesquelles vous ferez la promotion du produit.FichierFilterPour être en accord avec les directives de WordPress.org, avant que nous commencions la période d'essai, nous vous demandons de nous permettre de récupérer votre nom d'utilisateur et des informations non sensibles du site afin de permettre au %s de communiquer avec %s pour vérifier les mises à jour et valider votre période d'essai.GratuitEssai gratuitVersion gratuiteAPI FreemiusDébuggage FreemiusLe SDK Freemius ne trouve pas le fichier principal du plugin. Merci de contacter sdk@freemius.com en indiquant l'erreur.État de FreemiusNom completFonctionObtenez des commissions pour les renouvellements automatiques d'abonnement.Vous avez une clef de licence ?Dites, savez-vous que %s propose un système de affiliation ? Si vous aimez le %s vous pouvez devenir notre ambassadeur et gagner de l'argent !Que pensez-vous de %s ? Testez nos %s fonctionnalités premium avec %d jours d'essai gratuit.Comment téléverser et activer ?Comment allez-vous faire de la promotion ?Je ne peux plus payer pour çaJe ne comprends pas comment le faire fonctionnerJe ne veux pas partager mes informations avec vousJ'ai trouvé un meilleur %sJ'ai mis à jour mon compte mais quand j'essaie de synchroniser la licence, la formule est toujours %s.Je n'ai plus besoin du %sJe n'ai besoin de %s que pour une courte périodeIDSi vous cliquez, cette décision sera déléguée aux administrateurs des sites.Si vous avez un instant, merci de nous indiquer pourquoi %sSi vous voulez transférer la propriété du compte de %s à %s cliquez sur le bouton Changement De PropriétaireSi vous voulez utiliser le %s sur ces sites, merci d'indiquer votre clé de licence ci-dessous et de cliquer sur le bouton d'activation.Information importante de mise à jour :Dans %sDans le cas où vous n'avez PAS l'intention d'utiliser ce %s sur ce site (ou tout autre site) - voulez-vous aussi annuler le %s ?Installer la version gratuite maintenantInstaller la dernière mise à jour gratuite maintenantInstaller maintenantInstaller la mise à jour maintenantInstallation du plugin : %sID du module non valide.Récupération des détails du site non valide.FactureEst actifIl semble que la licence ne puisse être activée.Il semble que la désactivation de la licence a échoué.Il semble que vous ne soyez plus en période d'essai donc il n'y a rien à annuler :)Il semble que vous soyez encore sur la formule %s. Si vous avez mis à jour ou changer votre formule, le problème est probablement de votre côté - désolé.Il semble que votre site n'ait pas de licence active.Il semble que l'un des paramètres d'authentification soit faux. Veuillez mettre à jour votre Public Key, votre Secret Key ainsi que vote User ID et essayez à nouveau.Ce n'est pas ce que je rechercheSachez que les informations de l'add-ons de %s sont issus d'un serveur externe.ClefMerci de nous indiquer ce qui ne fonctionne pas afin que nous puissions le corriger pour les futurs utilisateurs...S'il vous plait, dites nous pourquoi afin que nous puissions nous améliorer.DernierDernière mise à jourDernière licenceLa dernière version gratuite a été installéDernière Version InstalléeEn savoir plusLongueurLicenceContrat de licenceClef de licenceClef de licenceLa clé de licence est vide.À vieVous aimez %s ? Devenez notre ambassadeur et gagnez du cash ;-)Chargement des options de la base de donnéesLocalhostLogLoggerMessageMéthodeMigrer les options vers le réseauApplications mobilesModuleChemin d'accès du moduleType de modulePlus d'informations à propos de %sNomRéseau de BlogRéseau d'UtilisateurNouveauUne nouvelle version est disponibleLa nouvelle version gratuite ( %s ) a été installéNouvelle Version (%s) InstalléeNewsletterSuivantNonID manquantPas d'engagement durant %s - annuler quand vous voulezPas d'engagement durant %s jours - annuler quand vous voulez !Pas besoin de carte bancairePas d'expirationSans expirationAucune formule du %s ne propose de période d'essai.O.KUne fois la licence expirée vous pourrez toujours utiliser la version gratuite mais vous n'aurez PAS accès aux fonctionnalités de %s.Une fois votre licence expirée, vous ne pourrez plus utiliser le %s, sauf si vous l'activez à nouveau avec une licence premium valide.InscriptionDésinscriptionInscrivez-vous pour améliorer "%s" !AutreEmail du propriétaireID du propriétaireNom du propriétaireCompatible PCILes add-ons payant doivent être déposés sur FreemiusAdresse email du compte PayPalPaiementsLes paiements se font en Dollars US et sont effectués mensuellement via PayPal.FormuleLa formule %s n'existe pas, il n'est pas possible de commencer une période d'essai.La formule %s ne propose pas de période d'essai.ID de la formuleMerci de nous contacter iciMerci de nous contacter avec le message suivant :Merci de télécharger %s.Merci d'indiquer le code de licence que vous avez reçu par email juste après l'achat :N'hésitez pas à indiquer des statistiques pertinentes concernant votre site ou vos réseaux sociaux telles que le nombre de visiteurs mensuel, le nombre d'abonnés, de followers, etc... (C'est informations resteront confidentielles)Merci de suivre ces étapes pour finaliser la mise à jourMerci de nous indiquer si vous souhaitez que nous vous contactions pour les mises à jour de sécurité et de fonctionnalités, du contenu instructif et des offres spéciales :Veuillez noter que nous ne serons pas en mesure de garantir le maintien des prix actuels pour les renouvellements/nouveaux abonnements après une annulation. Si vous choisissez de renouveler l'abonnement manuellement à l'avenir, après une augmentation de prix, qui se produit généralement une fois par an, le prix mis à jour vous sera facturé.Merci d'indiquer en détail comment vous allez faire la promotion du %s (en étant aussi précis que possible)Merci d'indiquer vos prénom et nom.PluginSite Web du pluginID du pluginInstallation du PluginChangelogDescriptionFAQFonctionnalités & TarifsInstallationAutres InformationsCommentairesLe plugin est un "Serviceware" ce qui veut dire qu'il n'a pas de version premium de code.PluginsSynchronisation des plugin et des thèmesPremiumLa version premium de %s a été activée avec succès.La version premium de l'add-on est déjà installée.Version premiumVersion premium déjà active.TarifsPolitique de confidentialitéPoursuivreID du processusTraitement en coursProduitsSommaire du programmeMéthodes de promotionRégionClef publiqueAcheter une licenceCommentaires rapidesQuotaRenvoyer l'email d'activationParrainez des nouveaux clients pour notre %s et gagnez une commission de %s sur chaque vente réussie que vous affiliez.Renouvelez votre licenceRenouvelez votre licence maintenantDemandesVersion de WordPress requiseRésultatSDKChemin d'accès du SDKÉconomisez %sCrons programmésCaptures d'écranRecherche par adresseClef secrêtePage %s sécurisée HTTPS, s'exécutant sur un domaine externeIl semble que nous ayons un problème temporaire avec l'annulation de votre abonnement. Merci de réessayer dans quelques minutes.Il semble que nous ayons un problème temporaire pour annuler votre période d'essai. Merci de réessayer dans quelques minutes.Il semble que vous ayez la dernière version.Choisir le paysEnvoyer le code de la licenceMise en place des options de la base de donnéesSimuler la mise à jour du réseauSimuler la promotion d'essaiLicence 1 siteSite IDSite ajouté avec succès.SitesPasser & %sSlugRéseaux sociaux (Facebook, Twitter, etc.)Désolé pour le dérangement et nous sommes là pour vous aider si vous nous le permettez.Désolé, nous ne pouvons pas mettre à jour l'email. Il existe déjà un autre utilisateur avec cette adresse.DébutEssai gratuitCommencer ma %s gratuiteÉtatEnvoyer & %sInscriptionSupportForum de SupportSynchronisation des données depuis le serveurCode TVAConditions générales de serviceMerci d'avoir postulé à notre programme d'affiliation, malheureusement, nous avons décidé pour le moment de décliner votre dossier. Merci d'essayer à nouveau d'ici 30 jours.Merci d'avoir postulé à notre programme d'affiliation, nous regarderons votre dossier durant les 14 prochains jours et nous reviendrons vers vous avec d'autres informations.Merci beaucoup d'utiliser %s et ses add-ons !Merci beaucoup d'utiliser %s !Merci beaucoup d'utiliser nos produits !Merci !Merci %s !Merci pour la confirmation du changement de propriétaire. Un email vient d'être envoyé à %s pour la validation finale.Le %s a cassé mon siteLe %s n'a pas fonctionnéLe %s n'a pas fonctionné comme prévuLe %s est bien mais j'ai besoin de fonctionnalités spécifiques que vous ne proposez pasLe %s ne fonctionne pasLe %s a soudainement arrêté de fonctionnerL'installation a commencé et peut prendre quelques minutes pour se finir. Merci de patienter jusqu'à ce qu'elle soit terminée - veuillez ne pas rafraichir cette page.Le package du plugin à télécharger ne contient pas de dossier avec le bon slug et iln'a pas été possible de le renommer.La mise à jour du %s s'est terminée avec succès ThèmeChangement de ThèmeThèmesIl y a une %s de %s disponible.Il y a une nouvelle version disponible de %s. Ce plugin n'a pas été indiqué comme étant compatible avec votre version actuelle de WordPressCe plugin n'a pas été testé avec votre actuelle version de WordPressTimestampTitreTotalVillePériode d'essaiTypeImpossible de se connecter au système de fichiers. Merci de confirmer vos autorisations.Licences sites illimitésMises à jour illimitéesCommissions illimitées.Jusqu'à %s SitesMise à jourMettre à jour la licenceMises à jour, annonces, marketing, pas de spamMise à jourTéléverser et activer la version téléchargéeGénialUser IDUtilisateursValeurUn email de vérification vient d'être envoyé sur %s. Si vous ne le recevez pas d'ici 5 minutes, merci de vérifier dans vos spams.VérifiéVérifier l'emailLa version %s vient d'être publiée.Voir les détailsVoir les fonctionnalités payantesAttentionNous ne trouvons aucune licence active associée avec cette adresse email, êtes-vous qu'il s'agit de la bonne adresse ?Nous ne trouvons pas votre adresse mail dans notre système, êtes-vous qu'il s'agit de la bonne adresse ?Nous avons fait quelques modifications au %s, %sNous sommes impatient de vous présenter l'intégration Freemius au niveau réseau.Statistiques du site web, de l'adresse email et des réseaux sociaux (optionnel)À quoi vous attendiez-vous ?Quelle fonctionnalité ?Quel est votre %s ?Quel prix seriez-vous prêt à payer ?Que recherchez-vous ?Quel est le nom du %s ?Où allez-vous faire la promotion du %s ? Page WordPress.org du pluginOuiOui - %sVous avez déjà utilisé la période d'essai.Vous êtes à 1 clic de commencer votre période d'essai gratuite de %1$s jours de la formule %2$s.Vous êtes tout bon !Vous utilisez déjà le %s en période d'essai. Il ne reste qu'une étape - %sVous pouvez toujours profiter de toutes les fonctionnalités de %s mais vous n'aurez plus accès aux mises à jour de sécurité ou de fonctionnalités de %s, ni au support.Vous n'avez pas de licence valide pour accéder à la version premium.Vous avez une license pour %s.Votre %s a bien été mis à jour.Peut-être que cela vous a échappé mais vous n'êtes pas obligé de partager la moindre information et vous pouvez juste %s l'enregistrement.Vous avez déjà validé notre suivi d'utilisation qui nous permet de continuer à améliorer le %s.Vous avez déjà validé notre suivi d'utilisation qui nous permet de continuer à les améliorer.Votre Add-on %s a bien été mis à jour.Votre période d'essai %s a bien été annulé.Votre compte a été activé avec succès avec la formule %s.Votre dossier d'affiliation pour %s a été accepté ! Identifiez-vous dans votre espace affilié sur : %s.Votre compte affilié a été suspendu temporairement.Votre email a été vérifié avec succès - vous êtes FORMIDABLE !Votre période d'essai gratuite est terminée. %1$sFaites la mise à jour maintenant%2$s pour continuer à utiliser le %3$s sans interruption.Votre période d'essai gratuite est terminée. Vous pouvez continuer à utiliser toutes nos fonctionnalités gratuites.Votre licence a été annulé. Si vous pensez qu'il s'agit d'une erreur, merci de contacter le support.Votre licence a expiré.%1$sFaites la mise à jour maintenant%2$s pour continuer à utiliser le %3$s sans interruption.Votre licence a expiré. Vous pouvez toujours utiliser les fonctionnalités %s mais vous devrez renouveler votre licence pour recevoir les mises à jour et une assistance.Votre licence a expiré. Vous pouvez toujours utiliser la version gratuite indéfiniment.Votre licence a bien été activée.Votre licence a bien été désactivé, vous utilisez à présent la formule %s.Votre nom a été mis à jour.Votre formule a bien été modifié vers %s. Votre formule a bien été mise à jour.Votre abonnement a bien été annulé. Votre licence de la formule %s expirera dans %s.Votre période d'essai a bien démarré.Code postalDirectement%s ne peut pas fonctionner sans %s.%s ne peut pas fonctionner sans le plugin.Avertissementautoriser%s restante(s)Activation en coursannéeAPIFermerDebuggageFélicitationsBloquéConnectéTélécharger la dernière versionTélécharger la dernière version gratuiteMensuelExpirationCheminEmail en cours d'envoimoisAnnuelAnnuelUne foisFormuleClef secrète manquanteVersions du SDKLicenceSynchroniserSynchroniser la licenceAuteurOffOnBasé sur %sCommencer l'essai gratuitFermerFermerDésactivationdéléguerne %sPAS%s m'envoyer de mises à jour de sécurité ou de fonctionnalités, ni de contenu instructif, ni d'offre.Formule %s%s FacturéBestHeyOupsHey %s,YoupilicenceSitesmsNouvelle versionNon vérifiéTarifTarifsVersionsecpasserHmmcommencer la période d'essaiabonnementChangementla dernière version de %s iciessaiPériode d'essaiSupprimerRétrograderÉditerCacherInscriptionDésinscriptionAcheterAfficherPasserMise à jourMise à jourIl y a %sPK!TATT.libraries/freemius/languages/freemius-it_IT.monu[sL'L'AM''n2(2(Z(h/)S)%) * ***1*D*6*!+_+6,V,#m,,%, , , ,,,- --@-+]-H--O-j5..>[?f^?? ???@ @Y @az@@@@A &A 4A;BA~AAAB B B BBjB-C5FMF7Fg,GpGHHy%HHH HHH I.I%AI gIIIII II1J.JOJ3KAKKyL2LLLaLZMB^M,MM M MM N $N/N6N>N PN [NgN wNNN4NN NNNNOOO&O6O QO]O dO pO|OO O OOO O!OO P%P*P%-PSP%YP+PP P P/PQmQ~QQRR*R 0Rpp p&q<FqDqZqT#rRxr.r.r)s-;t9itZt3t<2ubouPuU#v_yvvKtw(wGw#1x%Ux){x$xUx) yJy\yyyy;y6y>zXz^zyzz$zzz {&{&D{*k{7{{{|3|P|e|{|||*|1|!}=}#Q}u}}} }}}} ~~ ~"~N+~z~~~~~~ 1*\dx      9CHQ  $?E Xd s }  ΁ ہKKsgyX":.] :ΊU)%E1kʌ ь ی^0V[B|FÎgkt} ȏ,.;j Ő̐<Ґ  + 5+Bn t3;MޒY,"^"o#"+ٔ7=g\)ĕSB[t  ˖Ԗܖ  $F6#} eq Ә ƙ'ؙ - I9@Ě͚Ԛ  #A IV [e}w >]yA#ĝ|e!ɞ :Dvw ¡JO"Т *7<ty$ Ϥܤp9 ʥ?ӥ;O{jh#Os!)٧^|+¨ŨIL)]l#^!#6 Zg(ƫ-ܫ )1Of 0+<\f=&ծ~A{ ޯJB[I/հܰ# $ E R\d v ٱA) ?IMT nxȲϲ߲ ,2 M'X% * ,C!"e4޴ue ۵##6F X=f Ŷ7 ;IEX-%%4 Z[fC¸5 nS* )3 L Vbf  U7'-_ýڽ  ,6FWk'[  4>!U"w  ¿ʿ ݿ@ |Ln-8fxI-C \h  9(Q ] #!2 8D J Uaj|j7'Jr( z,Cpx&H* K_ u7 *U!Iw8   @+D\kt* %  #"{)gs %M88.g~$"6#Mqt#|*K>2Vj',KE*&==dG}FhC<D0u3w?i@UHhgH\e sW;/J.,=-j*]4!VZ^u |'    %."H kw      #+OV]bi_   *08=@Tcry FRIO#Sw   !*17 @N %s to access version %s security & feature updates, and support. The %s's %sdownload link%s, license key, and installation instructions have been sent to %s. If you can't find the email after 5 min, please check your spam box. The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s"The ", e.g.: "The plugin"The %s's%1$s has been placed into safe mode because we noticed that %2$s is an exact copy of %3$s.%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s.%s - plugin name. As complete "PluginX" activation nowComplete "%s" Activation Now%s Add-on was successfully purchased.%s Installs%s Licenses%s ago%s and its add-ons%s automatic security & feature updates and paid functionality will keep working without interruptions until %s (or when your license expires, whatever comes first).%s commission when a customer purchases a new license.%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license.%s is a premium only add-on. You have to purchase a license first before activating the plugin.%s is my client's email address%s is my email address%s is the new owner of the account.%s minimum payout amount.%s opt-in was successfully completed.%s or higher%s rating%s ratings%s sec%s star%s stars%s time%s times%s to access version %s security & feature updates, and support.%s to activate the license once you get it.%s tracking cookie after the first visit to maximize earnings potential.%s's paid features%sClick here%s to choose the sites where you'd like to activate the license on.A confirmation email was just sent to %s. The email owner must confirm the update within the next 4 hours.A confirmation email was just sent to %s. You must confirm the update within the next 4 hours. If you cannot find the email, please check your spam folder.APIASCII arrow left icon←ASCII arrow right icon➤AccountAccount DetailsActionsActivateActivate %sActivate %s PlanActivate %s featuresActivate Free VersionActivate LicenseActivate license on all pending sites.Activate license on all sites in the network.Activate this add-onActivatedAdd Ons for %sAdd Ons of module %sAdd another domainAdd-OnAdd-OnsAdd-on must be deployed to WordPress.org or Freemius.AddressAddress Line %dAffiliateAffiliationAfter your free %s, pay as little as %sAgree & Activate LicenseAll RequestsAll TypesAllow & ContinueAlternatively, you can skip it for now and activate the license later, in your %s's network-level Account page.AmountAn automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now.An unknown error has occurred while trying to set the user's beta mode.An unknown error has occurred while trying to toggle the license's white-label mode.An unknown error has occurred.An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned.Anonymous feedbackApply on all pending sites.Apply on all sites in the network.Apply to become an affiliateAre both %s and %s your email addresses?Are you sure you want to delete all Freemius data?Are you sure you want to proceed?As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days.Associate with the license owner's account.Auto installation only works for opted-in users.Auto renews in %sAutomatic InstallationAverage RatingAwesomeBecome an affiliateBetaBillingBilling & InvoicesBlockingBlog IDBodyBundleBundle PlanBusiness nameBuy a license nowBuy licenseBy changing the user, you agree to transfer the account ownership to:Can't find your license key?CancelCancel %s & ProceedCancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site.Cancel %s?Cancel InstallationCancel SubscriptionCancel TrialCancelledCancelling %sCancelling %s...Cancelling the subscriptionCancelling the trial will immediately block access to all premium features. Are you sure?Change LicenseChange OwnershipChange PlanChange UserCheckoutClear API CacheClear Updates TransientsClick hereClick here to use the plugin anonymouslyClick to see reviews that provided a rating of %sClick to view full-size screenshot %dClone resolution admin notice products list labelProductsCodeCompatible up toContactContact SupportContact UsContributorsCouldn't activate %s.CountryCron TypeDateDeactivateDeactivate LicenseDeactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site.Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?DeactivationDebug LogDebug mode was successfully enabled and will be automatically disabled in 60 min. You can also disable it earlier by clicking the "Stop Debug" link.Delegate to Site AdminsDelete All AccountsDetailsDiagnostic data will no longer be sent from %s to %s.Disabling white-label modeDon't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support.Don't have a license key?Donate to this pluginDowngrading your planDownloadDownload %s VersionDownload Paid VersionDownload the latest %s versionDownload the latest versionDownloadedDue to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.Duplicate WebsiteDuring the update process we detected %d site(s) that are still pending license activation.During the update process we detected %s site(s) in the network that are still pending your attention.EmailEmail addressEmail address updateEnabling white-label modeEndEnter email addressEnter the domain of your website or other websites from where you plan to promote the %s.Enter the email address you've used for the upgrade below and we will resend you the license key.Enter the new email addressErrorError received from the server:ExpiredExpires in %sExtra DomainsExtra domains where you will be marketing the product from.FileFilterFor compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.FreeFree TrialFree versionFreemius APIFreemius DebugFreemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error.Freemius StateFreemius is our licensing and software updates engineFull nameFunctionGet commission for automated subscription renewals.Get updates for bleeding edge Beta versions of %s.Have a license key?Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!How do you like %s so far? Test all our %s premium features with a %d-day free trial.How to upload and activate?How will you promote us?I Agree - Change UserI can't pay for it anymoreI couldn't understand how to make it workI don't like to share my information with youI found a better %sI have upgraded my account but when I try to Sync the License, the plan remains %s.I no longer need the %sI only needed the %s for a short periodIDIf this is a long term duplicate, to keep automatic updates and paid functionality after %s, please %s.If you click it, this decision will be delegated to the sites administrators.If you have a moment, please let us know why you are %sIf you would like to give up the ownership of the %s's account to %s click the Change Ownership button.If you'd like to use the %s on those sites, please enter your license key below and click the activation button.Important Upgrade Notice:In %sIn case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?Install Free Version NowInstall Free Version Update NowInstall NowInstall Update NowInstalling plugin: %sInvalid clone resolution action.Invalid module ID.Invalid new user ID or email address.Invalid site details collection.InvoiceIs %2$s a duplicate of %4$s?Is %2$s a new website?Is %2$s the new home of %4$s?Is ActiveIs this your client's site? %s if you wish to hide sensitive info like your email, license key, prices, billing address & invoices from the WP Admin.It looks like the license could not be activated.It looks like the license deactivation failed.It looks like you are not in trial mode anymore so there's nothing to cancel :)It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry.It looks like your site currently doesn't have an active license.It requires license activation.It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.It's a temporary %s - I'm troubleshooting an issueIt's not what I was looking forJoin the Beta programJust letting you know that the add-ons information of %s is being pulled from an external server.KeyKindly share what didn't work so we can fix it for future users...Kindly tell us the reason so we can improve.LastLast UpdatedLast licenseLatest Free Version InstalledLatest Version InstalledLearn moreLengthLicenseLicense AgreementLicense IDLicense KeyLicense issues?License keyLicense key is empty.LifetimeLike the %s? Become our ambassador and earn cash ;-)Load DB OptionLocalhostLogLoggerLong-Term DuplicateMessageMethodMigrateMigrate LicenseMigrate Options to NetworkMobile appsModuleModule PathModule TypeMore information about %sNameNetwork BlogNetwork UserNewNew Version AvailableNew WebsiteNewer Free Version (%s) InstalledNewer Version (%s) InstalledNewsletterNextNoNo - only move this site's data to %sNo IDNo commitment for %s - cancel anytimeNo commitment for %s days - cancel anytime!No credit card requiredNo expirationNon-expiringNone of the %s's plans supports a trial period.O.KOnce your license expires you can still use the Free version but you will NOT have access to the %s features.Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license.Opt InOpt OutOpt in to make "%s" better!OtherOwner EmailOwner IDOwner NamePCI compliantPaid add-on must be deployed to Freemius.Part of an activation link message.Click herePart of the message telling the user what they should receive via email.a license keyPart of the message telling the user what they should receive via email.the installation instructionsPart of the message that tells the user to check their spam folder for a specific email.the product's support email addressPayPal account email addressPaymentsPayouts are in USD and processed monthly via PayPal.PlanPlan %s do not exist, therefore, can't start a trial.Plan %s does not support a trial period.Plan IDPlease contact us herePlease contact us with the following message:Please download %s.Please enter the license key that you received in the email right after the purchase:Please enter the license key to enable the debug mode:Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential).Please follow these steps to complete the upgradePlease let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.Please provide details on how you intend to promote %s (please be as specific as possible).Please provide your full name.PluginPlugin HomepagePlugin IDPlugin InstallPlugin installer section titleChangelogPlugin installer section titleDescriptionPlugin installer section titleFAQPlugin installer section titleFeatures & PricingPlugin installer section titleInstallationPlugin installer section titleOther NotesPlugin installer section titleReviewsPlugin is a "Serviceware" which means it does not have a premium code version.PluginsPlugins & Themes SyncPremiumPremium %s version was successfully activated.Premium add-on version already installed.Premium versionPremium version already active.PricingPrivacy PolicyProceedProcess IDProcessingProductsProgram SummaryPromotion methodsProvincePublic KeyPurchase LicensePurchase MoreQuick FeedbackQuotaRe-send activation emailRefer new customers to our %s and earn %s commission on each successful sale you refer!Renew licenseRenew your license nowRequestsRequires PHP VersionRequires WordPress VersionReset Deactivation SnoozingResultSDKSDK PathSave %sSavedScheduled CronsScreenshotsSearch by addressSecret KeySecure HTTPS %s page, running from an external domainSeems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes.Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.Seems like you got the latest release.Select CountrySend License KeySet DB OptionSharing diagnostic data with %s helps to provide functionality that's more relevant to your website, avoid WordPress or PHP version incompatibilities that can break your website, and recognize which languages & regions the plugin should be translated and tailored to.Show error detailsSimulate Network UpgradeSimulate Trial PromotionSingle Site LicenseSite IDSite successfully opted in.SitesSkip & %sSlugSnooze & %sSo you can reuse the license when the %s is no longer active.Social media (Facebook, Twitter, etc.)Sorry for the inconvenience and we are here to help if you give us a chance.Sorry, we could not complete the email update. Another user with the same email is already registered.StartStart DebugStart TrialStart my free %sStateStop DebugSubmitSubmit & %sSubscriptionSupportSupport ForumSync Data From ServerTax / VAT IDTerms of ServiceThank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days.Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information.Thank you for updating to %1$s v%2$s!Thank you so much for using %s and its add-ons!Thank you so much for using %s!Thank you so much for using our products!Thank you!Thanks %s!Thanks for confirming the ownership change. An email was just sent to %s for final approval.Thanks for upgrading.Thanks!The %s broke my siteThe %s didn't workThe %s didn't work as expectedThe %s is great, but I need specific feature that you don't supportThe %s is not workingThe %s suddenly stopped workingThe following products'The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page.The products below have been placed into safe mode because we noticed that %2$s is an exact copy of %3$s:%1$sThe products below have been placed into safe mode because we noticed that %2$s is an exact copy of these sites:%3$s%1$sThe remote plugin package does not contain a folder with the desired slug and renaming did not work.The upgrade of %s was successfully completed.ThemeTheme SwitchThemesThere is a %s of %s available.There is a new version of %s available.This plugin has not been marked as compatible with your version of WordPress.This plugin has not been tested with your current version of WordPress.This plugin requires a newer version of PHP.TimestampTitleTo enter the debug mode, please enter the secret key of the license owner (UserID = %d), which you can find in your "My Profile" section of your User Dashboard:TotalTownTrialTypeUnable to connect to the filesystem. Please confirm your credentials.Unlimited LicensesUnlimited UpdatesUnlimited commissions.Up to %s SitesUpdateUpdate LicenseUpdates, announcements, marketing, no spamUpgradeUpload and activate the downloaded versionUsed to express elation, enthusiasm, or triumph (especially in electronic communication).W00tUser DashboardUser IDUser keyUsersValueVerification mail was just sent to %s. If you can't find it after 5 min, please check your spam box.VerifiedVerify EmailVersion %s was released.View detailsView paid featuresWarningWe can't see any active licenses associated with that email address, are you sure it's the right address?We couldn't find your email address in the system, are you sure it's the right address?We couldn't load the add-ons list. It's probably an issue on our side, please try to come back in few minutes.We made a few tweaks to the %s, %sWe're excited to introduce the Freemius network-level integration.Website, email, and social media statistics (optional)Welcome to %s! To get started, please enter your license key:What did you expect?What feature?What is your %s?What price would you feel comfortable paying?What you've been looking for?What's the %s's name?Where are you going to promote the %s?WordPress.org Plugin PageWould you like to merge %s into %s?Would you like to proceed with the update?YesYes - %sYes - both addresses are mineYes - move all my data and assets from %s to %sYes, %2$s is a duplicate of %4$s for the purpose of testing, staging, or development.Yes, %2$s is a new and different website that is separate from %4$s.You already utilized a trial before.You are 1-click away from starting your %1$s-day free trial of the %2$s plan.You are all good!You are already running the %s in a trial mode.You are just one step away - %sYou can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.You do not have a valid license to access the premium version.You have a %s license.You have purchased a %s license.You have successfully updated your %s.You marked this website, %s, as a temporary duplicate of %s.You marked this website, %s, as a temporary duplicate of these sitesYou might have missed it, but you don't have to share any data and can just %s the opt-in.You've already opted-in to our usage-tracking, which helps us keep improving the %s.You've already opted-in to our usage-tracking, which helps us keep improving them.Your %s Add-on plan was successfully upgraded.Your %s free trial was successfully cancelled.Your %s license was flagged as white-labeled to hide sensitive information from the WP Admin (e.g. your email, license key, prices, billing address & invoices). If you ever wish to revert it back, you can easily do it through your %s. If this was a mistake you can also %s.Your %s license was successfully deactivated.Your account was successfully activated with the %s plan.Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s.Your affiliation account was temporarily suspended.Your email has been successfully verified - you are AWESOME!Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your free trial has expired. You can still continue using all our free features.Your license has been cancelled. If you think it's a mistake, please contact support.Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support.Your license has expired. You can still continue using the free %s forever.Your license was successfully activated.Your license was successfully deactivated, you are back to the %s plan.Your name was successfully updated.Your plan was successfully activated.Your plan was successfully changed to %s.Your plan was successfully upgraded.Your subscription was successfully cancelled. Your %s plan license will expire in %s.Your trial has been successfully started.ZIP / Postal Codea positive responseRight onactivate a license hereactive add-onActiveaddonX cannot run without pluginY%s cannot run without %s.addonX cannot run...%s cannot run without the plugin.advance notice of something that will need attention.Heads upallowas 5 licenses left%s leftas activating pluginActivatingas annual periodyearas application program interfaceAPIas close a windowDismissas code debuggingDebuggingas congratulationsCongratsas connection blockedBlockedas connection was successfulConnectedas download latest versionDownload Latestas download latest versionDownload Latest Free Versionas every monthMonthlyas expiration dateExpirationas file/folder pathPathas in the process of sending an emailSending emailas monthly periodmoas once a yearAnnualas once a yearAnnuallyas once a yearOnceas product pricing planPlanas secret encryption key missingNo Secretas software development kit versionsSDK Versionsas software licenseLicenseas synchronizeSyncas synchronize licenseSync Licenseas the plugin authorAuthoras turned offOffas turned onOnbased on %scall to actionStart free trialclose a windowDismissclose windowDismissdatadaysdeactivatingdelegatedo %sNOT%s send me security & feature updates, educational content and offers.e.g. Professional Plan%s Plane.g. billed monthlyBilled %se.g. the best productBestexclamationHeyexclamationOopsgreetingHey %s,hourhoursinstalled add-onInstalledinterjection expressing joy or exuberanceYee-hawlicenselike websitesSitesmillisecondsmsnew Beta versionnew versionnot verifiednounPricenounPricingoptionalproduct versionVersionproductsrevert it nowsecondssecseems like the key you entered doesn't match our records.send me security & feature updates, educational content and offers.skipsomething somebody says when they are thinking about what you have just said.Hmmstart the trialsubscriptionswitchingthe above-mentioned sitesthe latest %s version heretrialtrial periodTrialverbDeleteverbDowngradeverbEditverbHideverbOpt InverbOpt OutverbPurchaseverbShowverbSkipverbUpdateverbUpgradex-ago%s agoProject-Id-Version: WordPress SDK Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues PO-Revision-Date: 2015-10-01 21:38+0000 Last-Translator: Daniele Scasciafratte Mte90 , 2015-2019,2022,2024 Language-Team: Italian (Italy) (http://app.transifex.com/freemius/wordpress-sdk/language/it_IT/) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Language: it_IT Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2; X-Poedit-Basepath: .. X-Poedit-KeywordsList: get_text_inline;get_text_x_inline:1,2c;$this->get_text_inline;$this->get_text_x_inline:1,2c;$this->_fs->get_text_inline;$this->_fs->get_text_x_inline:1,2c;$this->fs->get_text_inline;$this->fs->get_text_x_inline:1,2c;$fs->get_text_inline;$fs->get_text_x_inline:1,2c;$this->_parent->get_text_inline;$this->_parent->get_text_x_inline:1,2c;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c X-Poedit-SearchPath-0: . X-Poedit-SearchPathExcluded-0: *.js X-Poedit-SourceCharset: UTF-8 %sper accedere alla versione%s per aggiornamenti, funzionalità e supporto.Il %s %slink per scaricare%s, la licenza e le istruzioni d'installazione sono state inviate a %s. Se non trovi l'email entro 5 minuti cerca nella cartella dello spam.La versione a pagamento di %1$sè già installata. Attiva questione versione per iniziare ad usare le funzionalità di %2$s.%3$sIl %s%1$s è entrato in modalità sicurezza perché è stato rilevato che %2$s è una copia esatta di %3$s./%1$sfermerà tutti i pagamenti ricorrenti futuri e il tuo piano %2$sche scadrà in %3$s.Completa l'attivazione di "%s" oraL' add-on %s è stato acquistato con successo.%s Installazioni%s Licenze%s fa%se i suoi addon%s con aggiornamenti automatici di sicurezza e funzionalità con le funzionalità a pagamento manterrà il funzionamento senza interruzioni fino a %s (o quando la licenza scade, oppure quello che avviene prima).%scommissione quando un utente acquista una nuova lcienza.Il periodo di prova gratuito %s è stato annullato con successo. Siccome l'add-on è premium, è stato disattivato automaticamente. Se vorrai usarlo in futuro, dovrai comprare una licenza.%s è un add-on premium. Devi comprare una licenza prima di poter attivare il plugin.%s è il mio indirizzo email%s è il mio indirizzo email%s è il nuovo proprietario dell'account.%s quantità minima per il pagamento.%s l'iscrizione è stata completata con successo.%s o superiore%s valutazione%s valutazioni%s sec%s stella%s stelle%s volta%s volte%sper accedere alla versione %sper aggiornamenti di sicurezza, nuove funzionalità e supporto.%s per attivare la licenza dopo averla ricevuta.%s cookie di tracciamento dopo che la prima visita per massimizzare i margini di guadagno. Funzionalità a pagamento di %s%sClicca qui%s per scegliere i siti dove vuoi attivare la licenza.Un email di conferma è stata inviata a %s. Il proprietario dell'email deve confermare l'aggiornamento nelle prossime 4 ore.Una email di conferma è stata inviata a %s. Puoi confermare l'aggiornamento nelle prossime 4 ore. Se non puoi trovare l'email verifica la tua cartella dello spam.API←➤AccountDettagli dell'accountAzioniAttivaAttiva %sAttivare il piano %sAttiva le funzionalità di %sAttiva versione gratuitaAttiva licenzaAttiva le licenze su tutti i siti in attesa.Attiva la licenza su tutti i siti del network.Attivare questo addonAttivatoAdd-on per %sAddon del modulo %sAggiungi un altro dominioAdd-onAddonL'add-on dev'essere distribuito da WordPress.org o Freemius.IndirizzoRiga indirizzo %dAffiliatiAffiliazioneDopo il tuo %s gratuito, paghi solamente %sAccetta e attiva la licenzaTutte le richiesteTutti i tipiConsenti & ContinuaIn caso puoi saltare per adesso e attivare la licenza successivamente nella tua pagina di attivazione network di %s.ImportoUn download con installazione automatica di %s (versione a pagamento) da %s inizierà in %s. Se preferisci farlo manualmente, fai clic sul pulsante per annullare.Un errore sconosciuto è avvenuto durante l'attivazione della modalità beta.Un errore sconosciuto è avvenuto mentre si passava alla licenza in modalità whitelabel.Un errore sconosciuto è avvenuto.Un aggiornamento per la versione Beta sostituirà la versione installata di %scon l'ultima versione Beta, utilizzare con attenzione e non su siti in produzione. Sei stato avvisato!Feedback anonimoApplica su tutti i siti in attesa.Applica su tutti i siti della rete.Applica per diventare un affiliatoSono entrambi %s e %s tuoi indirizzi email?Sei sicuro di voler eliminare tutti i dati di Freemius?Sei sicuro di voler procedere?Ci riserviamo 30 giorni in caso di rimborsi, paghiamo le commissioni se sono più vecchie di 30 giorni.Associa con il proprietario della licenzaL'installazione automatica funziona solo per gli utenti che hanno dato il consenso.Rinnovo automatico in %sInstallazione automaticaValutazione mediaFantasticoDiventa un affiliatoBetaFatturazioneRicevute e FattureBloccatoBlog IDBodyPacchettoPiano BundleNome della compagniaCompra una licenza oraCompra la licenzaCambiando l'utente accetti di trasferire la proprietà dell'account a:Non trovi la tua chiave di licenza?AnnullaAnnulla %s & ProseguiAnnulla %s, non ho bisogno di aggiornamenti di funzionalità e sicurezza o supporto per %sperché non ho intenzione di usare %ssu questo sito o qualunque altro sito.Annulla %s?Annulla installazioneAnnulla sottoscrizioneAnnulla prova gratuitaAnnullatoCancellazione di %sCancellazione %s...Cancella la sottoscrizioneCancellando il periodo di prova gratuito bloccherai immediatamente l'accesso a tutte le funzionalità premium. Vuoi continuare?Cambia licenzaCambia ProprietarioCambia pianoCambia utenteCassaElimina cache APISvuota le Transient degli aggiornamentiClicca quiFai clic qui per usare il plugin anonimamenteFai clic per vedere le recensioni che hanno fornito una valutazione di %sFare clic per visualizzare lo screenshot in grandi dimensioni %dProdottiCodiceCompatibile fino aContattiContatta il supportoContattaciContributoriNon é stato possibile attivare %s.NazioneTipo di CronDataDisattivaDisattiva licenzaDisattivare o disinstallare %s che disabiliterà automaticamente la licenza, che permetterà di utilizzarla in un altro sito.Disattiva la tua licenza bloccando tutte le funzionalità premium ma potrai attivare la licenza su un altro sito. Sei sicuro di voler continuare?DisattivazioneDebug LogLa modalità Debug è stata attivata con successo e sarà disattivata automaticamente in 60 minuti. Puoi disattivarla prima cliccando sul link "Ferma Debug".Delega ai proprietari del sitoEliminare tutti gli accountDettagliLe informazioni diagnostiche non saranno più inviate da %s a %s.Disabilita la modalità white labelNon annullare %s, sono interessato in ottenere gli aggiornamenti di sicurezza, nuove funzionalità o contattare il supporto.Non hai una chiave di licenza?Fai una donazione a questo pluginTorna al piano precedenteDownloadScarica la versione %sScarica la versione a pagamentoScarica l'ultima versione di %sScarica l'ultima versioneScaricatoCausa la %sDirettiva per la protezione dei Dati Europea (GDPR)%sabbiamo adeguato i requisiti che fornisci per il consenso, confermando che accetti di lasciare i dati.A causa della violazione dei nostri termini di affiliazione abbiamo deciso di bloccare temporaneamente il tuo account affiliativo. Se hai domande contatta il supporto.Sito duplicatoDurante la procedura di aggiornamento abbiamo individuato%d sito/i che sono in attesa della attivazione della licenza.Durante la procedura di aggiornamenti abbiamo individuato %s sito/i del network che sono in attesa di un tuo controllo.EmailIndirizzo emailAggiorna l'indirizzo emailAbilita la modalità white labelFineInserisci l'indirizzo emailInserisci il dominio del tuo sito o altri siti da dove vuoi promuovere %s.Inserisci qui sotto l'indirizzo email che hai usato per registrare l'aggiornamento e ti invieremo di nuovo la chiave di licenza.Inserisci il nuovo indirizzo emailErroreErrore ricevuto dal server:ScadutoScade in %sDomini aggiuntiviDomini aggiuntivi dove ci sarà il modulo promozionale.FileFiltroPer essere accettato del regolamento WordPress.org, prima di attivare il periodo di prova devi accettare di condividere informazioni come il tuo utente e dati non sensibili. Permettendo a %s di inviare dati periodicamente a %s per verificare gli aggiornamenti e approvare il periodo di prova.GratuitoProva gratuitaVersione gratuitaFreemius APIDebug FreemiusL'SDK di Freemius non è riuscito a trovare il file principale del plugin. Per favore contatta sdk@freemius.com riportando l'errore.Stato di FreemiusFreemius è il nostro servizio di licenze e aggiornamentiNome completoFunzioneOttieni delle commissioni dal sistema automatizzato di rinnovo.Ottieni gli aggiornamenti per le nuove versioni Beta di %s.Hai una chiave di licenza?Ciao, sai che %s ha il programma di affiliazione? Se ti piace %s puoi diventare un nostro ambasciatore e guadagnare denaro!Come sta andando con %s? Prova tutte le funzionalità premium di %s con una prova gratuita di %d giorni.Come faccio a caricare ed attivare?Come ci promuoverai?Accetto - Cambia utenteNon posso piú pagarloNon capisco come farlo funzionareNon voglio condividere i miei dati con teHo trovato un migliore %sHo aggiornato il mio account, ma quando cerco di sincronizzare la licenza, il piano rimane %s.Non ho più bisogno di %sHo avuto bisogno di %s per un breve periodoIDSe questo è un duplicato a lungo termine per mantenere gli aggiornamenti automatico e funzionalità a pagamento dopo %s, verifica %s.Se fai clic questa decisione sarà delegata agli amministratori del sito.Se hai un attimo, facci sapere perché %sPuoi abbandonare la proprietà dell'account %s a %scliccando il pulsante Cambia proprietario.Se vuoi utilizzare %s su questi siti, inserisci la tua licenza sotto e fai clic sul pulsante di attivazione.Avviso Importante di aggiornamento:In %sIn caso NON hai pianificato di usare %s su questo sito (o ogni altro sito) vuoi cancellare %s?Installa la versione gratuita oraInstalla l'ultima versione gratuitaInstalla oraInstalla l'aggiornamento oraInstallazione plugin: %sAzione di risoluzione del clone fallita.ID modulo non valida.Nuovo utente ID o indirizzo email non valido.Raccolta dati siti non valida.Fattura%2$s è un duplicato di %4$s?%2$s è un nuovo sito?%2$s è la nuova home di %4$s?è attivaSi tratta di un sito di un cliente?%sse vuoi puoi nascondere informazioni sensibili come la tua email, licenza, prezzi e indirizzi dalla tua bacheca di WP.Sembra che la licenza non possa essere attivata.Sembra che la disattivazione della licenza non sia riuscita.Sembra che tu non stia più usando la prova gratuita, quindi non c'è niente che tu debba annullare :)Sembra che tu sia ancora usando il piano %s. Se hai effettuato un upgrade o cambiato il piano, è probabile che ci sia un problema nei nostri sistemi.Sembra che il tuo sito non disponga di alcuna licenza attiva.Richiede la attivazione della licenza.Sembra che uno dei parametri di autenticazione sia sbagliato. Aggiorna la tua chiave pubblica, Secret Key & User ID e riprova.È una %s temporanea. Sto solo cercando di risolvere un problema.Non é quello che stavo cercandoEntra nel programma BetaLe informazioni sugli add-on di %s vengono scaricate da un server esterno.ChiaveCondividi cosa non ha funzionato in modo da migliorare il prodotto per gli utenti futuri...Spiegandoci il motivo ci aiuterai a migliorare.UltimoUltimo aggiornamentoUltima licenzaUltima versione gratuita installataVersione più recente installataScopri altroLunghezzaLicenzaLicense AgreementLicense IDChiave della licenzaProblemi di licenza?Chiave di licenzaLa chiave licenza è vuota.Tutta la vitaTi piace %s? Diventa il nostro ambasciatore e guadagna denaro ;-)Carica opzioni del DBLocalhostLogLoggerDuplicato a lungo termineMessaggioMetodoSpostaSposta la licenzaMigra le opzioni al NetworkApplicazioni mobileModuloPercorso moduloTipo di moduloUlteriori informazioni su %sNomeNetwork BlogUtente NetworkNuovoNuova versione disponibileNuovo sitoNuova versione gratuita (%s) installataVersione più recente (%s) installataNewsletterSuccessivoNoNo sposta solo i dati di questo sito su %sNessun IDNessun impegno con %s - cancella quando vuoiNessun impegno per %s giorni - puoi annullare in qualsiasi momento!Nessuna carta di credito richiestaNessuna scadenzaNon in scadenzaNessuno dei piani di %ssupporta il periodo di prova.OKQuando la tua licenza scadrà, potrai comunque continuare a usare la versione gratuita, ma NON avrai accesso alle funzionalità %s.Quando la tua licenza scadrà non potrai più usare %s, a meno che lo attivi di nuovo con una licenza premium valida.IscrivitiCancella iscrizioneAbilita "%s" per renderlo migliore!AltroEmail proprietarioID proprietarioNome proprietarioPCI compliantGli add-on a pagamento devono essere distribuiti da Freemius.Clicca quiuna chiave di licenzale istruzioni di installazionel'indirizzo email per il supporto relativo al prodotto.Indirizzo account email PaypalPagamentiI pagamenti sono in Dollari Americani e processati mensilmente da PayPal.PianoIl piano %s non esiste, per questo motivo non è possibile iniziare il periodo di prova.Il piano %s non supporta il periodo di prova.ID PianoContattaci quiContattaci con il seguente messaggio:Scarica %s.Per favore inserisci la chiave di licenza che hai ricevuto via mail subito dopo l'acquisto:Inserisci la chiave della licenza per abilitare la modalità Debug:Facci sapere ogni sito o statistiche social valide, es: visite uniche mensili, numero di sottoscrizioni email, follower ecc (tratteremo queste informazioni come riservate).Segui i passi seguenti per completare l'aggiornamentoFacci sapere se vuoi essere contattato per aggiornamenti di sicurezza e di funzionalità, contenuti formativi e offerte occasionali:Si prega di notare che non saremo in grado di garantire lo stesso prezzo per rinnovi/sottoscrizioni dopo la cancellazione. Se scegli di rinnovare l'abbonamento manualmente in futuro, dopo un aumento del prezzo, che di solito avviene una volta l'anno, ti verrà addebitato il nuovo prezzo.Fornisci i dettagli su come intendi promuovere %s. (sii più esplicativo possibile)Per favore inserisci il tuo nome completo.PluginHomepage del pluginPlugin IDInstallazione del pluginChangelogDescrizioneFAQCaratteristiche & prezziInstallazioneAltre noteRecensioniIl plugin è un "Serviceware", quindi non dispone di una versione del codice Premium.PluginSincronizzazione plugin e temiPremiumLa versione 1%s Permium è stata attivata con successo.Versione Premium dell'add-on già installata.Versione premiumVersione Premium già attiva.PrezziPolitica sulla privacyProseguiID processoElaborazioneProdottiSommario programmaMetodi promozionaliProvinciaChiave pubblicaAcquista licenzaContinua a comprareSuggerimenti rapidiQuotaInvia nuovamente l'email di attivazioneComunica nuovi clienti al nostro %s e guadagna %s di commissione per ogni vendita avvenuta!Rinnova licenzaRinnova la tua licenza oraRichiesteVersione PHP richiestaRichiede la versione di WordPressResetta l'avviso di disattivazioneRisultatoSDKPercorso SDKRisparmia %sSalvatoAzioni programmateScreenshotCerca per indirizzoChiave segretaPagina sicura su protocollo HTTPS %s eseguita su dominio esternoSembra che stai avendo dei problemi temporanei con la cancellazione della sottoscrizione. Prova nuovamente tra pochi minuti.Stiamo avendo qualche problema temporaneo con l'annullamento del periodo di prova. Riprova tra qualche minuto.Sembra che tu abbia la versione più recente.Seleziona NazioneInvia chiave di licenzaImposta opzione del DBCondividere informazioni diagnostiche con %s aiuta a fornire funzionalità mirate al vostro sito web. La condivisione dei dati permette, inoltre, di evitare errori di incompatibilità relative a versioni di WordPress o PHP, e di determinare in quali lingue, e per quali regioni, il plugin necessita di traduzioni e miglioramenti.Mostra i dettagli dell'errore.Simula aggiornamento networkSimula la prova TrialLicenza per sito singoloID del sitoSito accettato con successo.SitiSalta & %sSlugSilenzia e %sPuoi riutilizzare la licenza quando %s non è piu attivo.Social network (Facebook, Twitter, ecc.)Siamo spiacenti per l'inconveniente e siamo qui per aiutarti con il tuo permesso.Siamo spiacenti, non siamo riusciti a completare l'aggiornamento via email. Un altro utente con lo stesso indirizzo email è già registrato.AvviaAvvia DebugInizia il periodo di prova gratuitoInizia la mia %sStatoFerma DebugInviaInvia e %sSottoscriviSupportoForum di supportoSincronizza i dati dal serverNumero Partita Iva o VATTermini del ServizioGrazie per la partecipazione al nostro programma di affiliazione, sfortunatamente abbiamo valutato di rifiutare la tua richiesta. Prova nuovamente fra 30 giorni.Grazie per la partecipazione al nostro programma di affiliazione, valuteremo la tua richiesta durante i prossimi 14 giorni e ti contatteremo per maggiori informazioni.Grazie per avere installato l'aggiornamento %1$s v%2$s.Grazie per utilizzare %se i suoi addon!Grazie per utilizzare %s!Grazie per utilizzare i nostri prodotti!Grazie!Grazie %s!Grazie per aver confermato il cambiamento del proprietario. Un' email è stata appena inviata a %s per la conferma finale.Grazie per avere installato l'aggiornamento.Grazie!%s ha rotto il mio sito%s non funziona%s non ha funzionato come mi aspettavo%s è ottimo ma ho bisogno di una funzionalità specifica non supportata%s non funziona%s ha improvvisamente smesso di funzionareI prodotti seguentiIl processo d'installazione è iniziato e potrebbe impiegare alcuni minuti per completarsi. Attendi finchè non ha finito, assicurandoti di non ricaricare questa pagina.Il prodotto sottostante è stato messo in modalità sicurezza perchè abbiamo notato che %2$s è una copia esatta di %3$s: %1$sIl prodotto sottostante è stato messo in modalità sicurezza perchè abbiamo notato che %2$s è una copia esatta di questi siti %3$s: %1$sIl pacchetto remoto del plugin non contiene una cartella con lo slug desiderato e la rinominazione non ha funzionato.L'aggiornamento di %s è stato completato con successo.TemaCambio temaTemiC'è un %sdi %s disponibile.C'è una nuova versione di %s disponibile.Questo plugin non è stato segnato come compatibile con la tua versione di WordPress.Questo plugin non è stato testato con la versione corrente di WordPress.Questo plugin richiede una versione di PHP più recente.TimestampTitoloAbilita la modalità Debug, inserisci la chiave segreta del proprietario della licenza (UserID = %d), che puoi trovare nella sezione "Profilo" della dashboard utente:TotaleCittadinaProva gratuitaTipoImpossibile accedere al filesystem. Conferma le tue credenziali.Licenze illimitateAggiornamenti IllimitatiCommissioni illimitate.Fino a %s sitiAggiornaAggiorna licenzaAggiornamenti, annunci, marketing, no spamAggiornamentoCarica e attiva la versione scaricataForteBacheca UtenteID utenteChiave utenteUtentiValoreL'email di verifica è stata inviata a %s. Se dopo 5 minuti non è ancora arrivata, per favore controlla nella tua casella di posta indesiderata.VerificatoVerifica emailLa versione %s é stata rilasciata.Visualizza dettagliVedi funzionalità a pagamentoAvvisoNon siamo riusciti a trovare alcuna licenza attiva associata al tuo indirizzo email, sei sicuro che sia l'indirizzo giusto?Non siamo riusciti a trovare il tuo indirizzo email nel sistema, sei sicuro che sia l'indirizzo giusto?Non possiamo caricare la lista degli addon. Probabilmente è un nostro problema, prova di nuovo fra qualche minuto.Abbiamo fatto alcune migliore a %s,%sSiamo felici di presentarvi il supporto al sistema multi network di Freemius.Siti, email e statistiche dei social network (opzionali)Benvenuto nel %s! Per iniziare inserisci la tua licenza:Che cosa ti aspettavi?Quale funzionalitá?Qual è il tuo %s?Che prezzo ritieni opportuno pagare?Che cosa stai cercando?Qual è il nome di %s?Dove vuoi promuovere %s?Pagina dei plugin di WordPress.orgVuoi fondere %s in %s?Vuoi procedere con l'aggiornamento?SiSI - %sSi entrambi gli indirizzi sono mieiSi muovi tutti i miei e risorse da %s a %sSi, %2$s è un duplicato di %4$s per il motivo di test, staging o sviluppo.Si %2$s è un nuovo e sito differente che è separato da %4$s.Hai già utilizzato una prova gratuita in passato.Sei a un clic di distanza dall'iniziare il tuo periodo di prova gratuito di %1$s giorni per il piano %2$s.Sei fantastico!Stai già usando %s in modalità prova.Sei a un passo dalla fine - %sPuoi continuare ad utilizzare le funzionalità%sma non avrai accesso agli aggiornamenti di sicurezza, nuove funzionalità o supporto.Non disponi di una licenza valida per accedere alla versione Premium.Hai la licenza %s.Hai la licenza %s.Hai aggiornato con successo il tuo %s.Hai segnato questo sito, %s, come duplicato temporaneo di %s.Hai marchiato questo sito, %s, come duplicato temporaneo di questi sitiPotresti non averci fatto caso, ma non sei obbligato a condividere i tuoi dati e puoi semplicemente %s la tua partecipazione.Hai già accettato il tracciamento d'uso, ci aiuterà a migliorare %s.Hai già accettato il tracciamento d'uso che ci aiuta a migliorare.Il piano del tuo add-on %s è stato aggiornato con successo.Il tuo periodo di prova gratuito %s è stato annullato con successo.La tua licenza%s è stata segnata come white label per nascondere informazioni sensibili dalla bacheca di WP (es: la tua email, licenza, prezzi e indirizzi). Se vuoi tornare indietro puoi farlo semplicemente tramite%s. Se è stato un errore guarda anche %s.La tua licenza%s è stata disattivata con successo.Il tuo account è stato attivato correttamente con il piano %s.La tua applicazione di affiliazione per %s è stata accettata! Accedi alla tua area di affiliazione a %s.Il tuo account di affiliazione è stato sospeso temporaneamente.Il tuo indirizzo email è stato verificato con successo - SEI UN GRANDE!La tua versione prova è scaduta.%1$s aggiorna ora %2$s per continuare ad usare %3$s senza interruzioni.La tua versione di prova gratuita è scaduta. Puoi continuare ad usare tutte le funzionalità gratuite.La tua licenza è stata cancellata. Se credi sia un errore, per favore contatta il supporto.La tua licenza è scaduta. %1$saggiorna ora %2$sper continuare ad utilizzare %3$s senza interruzioni.La licenza è scaduta. È comunque possibile continuare a utilizzare tutte le funzionalità di %s, ma sarà necessario rinnovare la licenza per continuare a ricevere gli aggiornamenti ed il supporto.La tua licenza è scaduta. Puoi continuare ad usare la versione gratuita %s per sempre.La tua licenza è stata attivata correttamente.La tua licenza é stata disattivata con successo, sei tornato al piano %s.Il tuo nome è stato aggiornato correttamente.Il tuo piano è stato attivato con successo.Il piano è stato cambiato con successo a %s.Il piano è stato aggiornato con successo.La tua sottoscrizione è stata cancellata con successo. La licenza del piano %sscadrà in %s.La versione di prova è stata avviata correttamente.CAPSìattiva una licenza quiAttiva%s non può funzionare senza %s.%s non può funzionare senza il plugin.Attenzionepermetti%s rimanentiAttivazioneannoAPIChiudiDebuggingCongratulazioniBloccatoConnessoScarica l'ultima versioneScarica l'ultima versione gratuitaMensilmenteScadenzaPercorsoInvio emailmeseAnnualeAnnualmenteUna voltaPianoNessuna chiaveVersioni SDKLicenzaSincronizzaSincronizza la licenzaAutoreNon attivoAttivobasato su %sInizia il periodo di prova gratuitoChiudiChiudidatagiornidisattivazione in corsodelega%snon %s mi invierà aggiornamenti di funzionalità e sicurezza, contenuti formativi e offerte.Piano %sFatturato %sMiglioreHeyOpsHey %s,oraoreInstallatoEvvailicenzaSitimsnuova versione Betanuova versionenon verificatoPrezzoPrezziopzionaleVersioneprodottitorna indietrosecsembra che la chiave che hai inserito non risulti nei nostri registri.inviami aggiornamenti di funzionalità e sicurezza, contenuti formativi e offerte.saltaUhmInizia il periodo di prova gratuitosottoscrizionepassa ai siti menzionati sopra stantel'ultima versione %s é quìprovaProva gratuitaEliminaDowngradeModificaNascondiIscrivitiCancella iscrizioneAcquistoMostraSaltaAggiornaAggiornamento%s faPK!q2 !!+libraries/freemius/languages/freemius-ta.monu[!!A!n!h^"S"%# A# M#Y#`#6s##__$#$$ $ % %%&%.%7%?%@H%H%%O%5&9&X&x&&&& &&&&&&&-!'O' d'n'}''''5''' ( ('(@( Y( f(p(o(((G)))**"**2+!J+al+0++,(,7,?,S,X,`,s,|,, , ,, ,,,,, --- - - ---Y.k.z. .....(.1.%)/:O///// / /// // /0x00 )1 61@1X1l1tt1122/282L2k2 22L3[3f@44 44Y4a5{555 5 5;566 6 7 7 7 )767jE77 77372898~M8U8"9>9W9)r9-99S92:'J:r:Mu:7:g:pc;;;y;n<< <<<< <= =1"=.T=O==AS>y>?/?aE??B?,?@ @ -@:@X@ q@|@@@ @ @@@4@ A A"A&A-A5A:[y[&[Z[T\Rg\.\.\9]ZR]3]<]b^P^U^_(__K#`(o`G`#`%a)*a$TaUya)aa b(b;=b6yb>bbbc0c$Fckcccc&c*d7-ded|dd3ddde*e>e*[e1eee#e f(f:f JfVfvff ffNfg&gDg_goggg1gggg h h 'h 4h ?hLh dhCphhQh i i (i2iMiSi firi i i i i i i i i i iindo5dpNqKq'5r]r|r8rr4lstK^uHu+u*v3Jv~v$v-vvvxw|zw:w2xxxxx.y?y$Xy'}y:y:yVz4rzz3{5{9|-N|+||A|||}}} ~'"~J~h~=B11 yt7|A*jl6׆KZF7`:Ӊ"  *00[-(؊A"C.f7݋)c72ōA:!S$u.Ɏ"%ȏ% 'AZp$4#X+n7'ҒJ ER j'w=)ݓX'` VP:V"hika@_M!3oe1/atg ~$37(kY z@4ڦ -KJ!Ψרy>$Y|~YܭfgS>cV İy_޳>GP\{)@EJ Ѷ</ KUuLºVdNZ e&*4"տxYq( 7 C5OA PZ^ex. ;A Q^vGqRF*;R'*y[P_%dC=V!. Q1|rf";.d|o@RORFk4  &$*O+k$!K,m;1qp= $='Xe( 4('1P{F4F{'A,?C$L'q", ma@$E jx#H4Jh>m7MCYw  .\~GyGX7lr1WIA7@yI7 ".g^*" )!6Xk"F&?m"!1$!S1XY6U:G=lm&9Zc#~k`&os9 D u1PC3w~JMR&w6  S 4 { F H P`HOpv  S.$S'\7M !&3H|.'* *5D`c % ;OE' "6I N'[0u1QBq4%;?^'x8% E3 y@   ; !X !z      %!=!S!f!!!!!! %s to access version %s security & feature updates, and support. The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s.%s - plugin name. As complete "PluginX" activation nowComplete "%s" Activation Now%s Add-on was successfully purchased.%s Installs%s Licenses%s ago%s and its add-ons%s commission when a customer purchases a new license.%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license.%s is a premium only add-on. You have to purchase a license first before activating the plugin.%s is the new owner of the account.%s minimum payout amount.%s or higher%s rating%s ratings%s sec%s star%s stars%s time%s times%s to access version %s security & feature updates, and support.%s tracking cookie after the first visit to maximize earnings potential.%s's paid features%sClick here%s to choose the sites where you'd like to activate the license on.APIASCII arrow left icon←ASCII arrow right icon➤AccountAccount DetailsActionsActivateActivate %sActivate %s PlanActivate %s featuresActivate Free VersionActivate LicenseActivate license on all pending sites.Activate license on all sites in the network.Activate this add-onActivatedAdd Ons for %sAdd Ons of module %sAdd another domainAdd-OnAdd-OnsAdd-on must be deployed to WordPress.org or Freemius.AddressAddress Line %dAffiliateAffiliationAfter your free %s, pay as little as %sAgree & Activate LicenseAll RequestsAll TypesAllow & ContinueAlternatively, you can skip it for now and activate the license later, in your %s's network-level Account page.AmountAn automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now.An unknown error has occurred while trying to set the user's beta mode.An unknown error has occurred.An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned.Anonymous feedbackApply on all pending sites.Apply on all sites in the network.Apply to become an affiliateAre you sure you want to delete all Freemius data?Are you sure you want to proceed?As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days.Auto installation only works for opted-in users.Auto renews in %sAutomatic InstallationAverage RatingAwesomeBecome an affiliateBetaBillingBilling & InvoicesBlockingBlog IDBodyBundle PlanBusiness nameBuy a license nowBuy licenseCan't find your license key?CancelCancel %s & ProceedCancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site.Cancel %s?Cancel InstallationCancel SubscriptionCancel TrialCancelledCancelling %sCancelling %s...Cancelling the subscriptionCancelling the trial will immediately block access to all premium features. Are you sure?Change LicenseChange OwnershipChange PlanCheckoutCityClear API CacheClear Updates TransientsClick here to use the plugin anonymouslyClick to see reviews that provided a rating of %sClick to view full-size screenshot %dClone resolution admin notice products list labelProductsCodeCompatible up toContactContact SupportContact UsContributorsCouldn't activate %s.CountryCron TypeDateDeactivateDeactivate LicenseDeactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site.Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?DeactivationDebug LogDelegate to Site AdminsDelete All AccountsDetailsDon't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support.Don't have a license key?Donate to this pluginDowngrading your planDownloadDownload %s VersionDownload the latest %s versionDownload the latest versionDownloadedDue to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.During the update process we detected %d site(s) that are still pending license activation.During the update process we detected %s site(s) in the network that are still pending your attention.EmailEmail addressEndEnter the domain of your website or other websites from where you plan to promote the %s.Enter the email address you've used for the upgrade below and we will resend you the license key.ErrorError received from the server:ExpiredExpires in %sExtra DomainsExtra domains where you will be marketing the product from.FileFilterFor compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.FreeFree TrialFree versionFreemius APIFreemius DebugFreemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error.Freemius StateFull nameFunctionGet commission for automated subscription renewals.Get updates for bleeding edge Beta versions of %s.Have a license key?Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!How do you like %s so far? Test all our %s premium features with a %d-day free trial.How to upload and activate?How will you promote us?I can't pay for it anymoreI couldn't understand how to make it workI don't like to share my information with youI found a better %sI have upgraded my account but when I try to Sync the License, the plan remains %s.I no longer need the %sI only needed the %s for a short periodIDIf you click it, this decision will be delegated to the sites administrators.If you have a moment, please let us know why you are %sIf you would like to give up the ownership of the %s's account to %s click the Change Ownership button.If you'd like to use the %s on those sites, please enter your license key below and click the activation button.Important Upgrade Notice:In %sIn case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?Install Free Version NowInstall Free Version Update NowInstall NowInstall Update NowInstalling plugin: %sInvalid module ID.Invalid site details collection.InvoiceIs ActiveIt looks like the license could not be activated.It looks like the license deactivation failed.It looks like you are not in trial mode anymore so there's nothing to cancel :)It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry.It looks like your site currently doesn't have an active license.It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.It's not what I was looking forJoin the Beta programJust letting you know that the add-ons information of %s is being pulled from an external server.KeyKindly share what didn't work so we can fix it for future users...Kindly tell us the reason so we can improve.LastLast UpdatedLast licenseLatest Free Version InstalledLatest Version InstalledLearn moreLengthLicenseLicense AgreementLicense KeyLicense keyLicense key is empty.LifetimeLike the %s? Become our ambassador and earn cash ;-)Load DB OptionLocalhostLogLoggerMessageMethodMigrate Options to NetworkMobile appsModuleModule PathModule TypeMore information about %sNameNetwork BlogNetwork UserNewNew Version AvailableNewer Free Version (%s) InstalledNewer Version (%s) InstalledNewsletterNextNoNo IDNo commitment for %s - cancel anytimeNo commitment for %s days - cancel anytime!No credit card requiredNo expirationNon-expiringNone of the %s's plans supports a trial period.O.KOnce your license expires you can still use the Free version but you will NOT have access to the %s features.Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license.Opt InOpt OutOpt in to make "%s" better!OtherOwner EmailOwner IDOwner NamePCI compliantPaid add-on must be deployed to Freemius.PayPal account email addressPaymentsPayouts are in USD and processed monthly via PayPal.PlanPlan %s do not exist, therefore, can't start a trial.Plan %s does not support a trial period.Plan IDPlease contact us herePlease contact us with the following message:Please download %s.Please enter the license key that you received in the email right after the purchase:Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential).Please follow these steps to complete the upgradePlease let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.Please provide details on how you intend to promote %s (please be as specific as possible).Please provide your full name.PluginPlugin HomepagePlugin IDPlugin InstallPlugin installer section titleChangelogPlugin installer section titleDescriptionPlugin installer section titleFAQPlugin installer section titleFeatures & PricingPlugin installer section titleInstallationPlugin installer section titleOther NotesPlugin installer section titleReviewsPlugin is a "Serviceware" which means it does not have a premium code version.PluginsPlugins & Themes SyncPremiumPremium %s version was successfully activated.Premium add-on version already installed.Premium versionPremium version already active.PricingPrivacy PolicyProceedProcess IDProcessingProductsProgram SummaryPromotion methodsProvincePublic KeyPurchase LicensePurchase MoreQuick FeedbackQuotaRe-send activation emailRefer new customers to our %s and earn %s commission on each successful sale you refer!Renew licenseRenew your license nowRequestsRequires WordPress VersionResultSDKSDK PathSave %sScheduled CronsScreenshotsSearch by addressSecret KeySecure HTTPS %s page, running from an external domainSeems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes.Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.Seems like you got the latest release.Select CountrySend License KeySet DB OptionSimulate Network UpgradeSimulate Trial PromotionSingle Site LicenseSite IDSite successfully opted in.SitesSkip & %sSlugSocial media (Facebook, Twitter, etc.)Sorry for the inconvenience and we are here to help if you give us a chance.Sorry, we could not complete the email update. Another user with the same email is already registered.StartStart TrialStart my free %sStateSubmit & %sSubscriptionSupportSupport ForumSync Data From ServerTax / VAT IDTerms of ServiceThank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days.Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information.Thank you so much for using %s and its add-ons!Thank you so much for using %s!Thank you so much for using our products!Thank you!Thanks %s!Thanks for confirming the ownership change. An email was just sent to %s for final approval.The %s broke my siteThe %s didn't workThe %s didn't work as expectedThe %s is great, but I need specific feature that you don't supportThe %s is not workingThe %s suddenly stopped workingThe installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page.The remote plugin package does not contain a folder with the desired slug and renaming did not work.The upgrade of %s was successfully completed.ThemeTheme SwitchThemesThere is a %s of %s available.There is a new version of %s available.This plugin has not been marked as compatible with your version of WordPress.This plugin has not been tested with your current version of WordPress.TimestampTitleTotalTownTrialTypeUnable to connect to the filesystem. Please confirm your credentials.Unlimited LicensesUnlimited UpdatesUnlimited commissions.Up to %s SitesUpdateUpdate LicenseUpdates, announcements, marketing, no spamUpgradeUpload and activate the downloaded versionUsed to express elation, enthusiasm, or triumph (especially in electronic communication).W00tUser IDUsersValueVerification mail was just sent to %s. If you can't find it after 5 min, please check your spam box.VerifiedVerify EmailVersion %s was released.View detailsView paid featuresWarningWe can't see any active licenses associated with that email address, are you sure it's the right address?We couldn't find your email address in the system, are you sure it's the right address?We made a few tweaks to the %s, %sWe're excited to introduce the Freemius network-level integration.Website, email, and social media statistics (optional)What did you expect?What feature?What is your %s?What price would you feel comfortable paying?What you've been looking for?What's the %s's name?Where are you going to promote the %s?WordPress.org Plugin PageWould you like to proceed with the update?YesYes - %sYou already utilized a trial before.You are 1-click away from starting your %1$s-day free trial of the %2$s plan.You are all good!You are already running the %s in a trial mode.You are just one step away - %sYou can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.You do not have a valid license to access the premium version.You have a %s license.You have successfully updated your %s.You might have missed it, but you don't have to share any data and can just %s the opt-in.You've already opted-in to our usage-tracking, which helps us keep improving the %s.You've already opted-in to our usage-tracking, which helps us keep improving them.Your %s Add-on plan was successfully upgraded.Your %s free trial was successfully cancelled.Your account was successfully activated with the %s plan.Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s.Your affiliation account was temporarily suspended.Your email has been successfully verified - you are AWESOME!Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your free trial has expired. You can still continue using all our free features.Your license has been cancelled. If you think it's a mistake, please contact support.Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support.Your license has expired. You can still continue using the free %s forever.Your license was successfully activated.Your license was successfully deactivated, you are back to the %s plan.Your name was successfully updated.Your plan was successfully activated.Your plan was successfully changed to %s.Your plan was successfully upgraded.Your subscription was successfully cancelled. Your %s plan license will expire in %s.Your trial has been successfully started.ZIP / Postal Codea positive responseRight onactive add-onActiveaddonX cannot run without pluginY%s cannot run without %s.addonX cannot run...%s cannot run without the plugin.advance notice of something that will need attention.Heads upallowas 5 licenses left%s leftas activating pluginActivatingas annual periodyearas application program interfaceAPIas close a windowDismissas code debuggingDebuggingas congratulationsCongratsas connection blockedBlockedas connection was successfulConnectedas download latest versionDownload Latestas download latest versionDownload Latest Free Versionas every monthMonthlyas expiration dateExpirationas file/folder pathPathas in the process of sending an emailSending emailas monthly periodmoas once a yearAnnualas once a yearAnnuallyas once a yearOnceas product pricing planPlanas secret encryption key missingNo Secretas software development kit versionsSDK Versionsas software licenseLicenseas synchronizeSyncas synchronize licenseSync Licenseas the plugin authorAuthoras turned offOffas turned onOnbased on %scall to actionStart free trialclose a windowDismissclose windowDismissdeactivatingdelegatedo %sNOT%s send me security & feature updates, educational content and offers.e.g. Professional Plan%s Plane.g. billed monthlyBilled %se.g. the best productBestexclamationHeyexclamationOopsgreetingHey %s,installed add-onInstalledinterjection expressing joy or exuberanceYee-hawlicenselike websitesSitesmillisecondsmsnew Beta versionnew versionnot verifiednounPricenounPricingproduct versionVersionsecondssecsend me security & feature updates, educational content and offers.skipsomething somebody says when they are thinking about what you have just said.Hmmstart the trialsubscriptionswitchingthe latest %s version heretrialtrial periodTrialverbDeleteverbDowngradeverbEditverbHideverbOpt InverbOpt OutverbPurchaseverbShowverbSkipverbUpdateverbUpgradex-ago%s agoProject-Id-Version: WordPress SDK Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues PO-Revision-Date: 2015-10-01 21:38+0000 Last-Translator: Sankar Srinivasan , 2019 Language-Team: Tamil (http://app.transifex.com/freemius/wordpress-sdk/language/ta/) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Language: ta Plural-Forms: nplurals=2; plural=(n != 1); X-Poedit-Basepath: .. X-Poedit-KeywordsList: get_text_inline;get_text_x_inline:1,2c;$this->get_text_inline;$this->get_text_x_inline:1,2c;$this->_fs->get_text_inline;$this->_fs->get_text_x_inline:1,2c;$this->fs->get_text_inline;$this->fs->get_text_x_inline:1,2c;$fs->get_text_inline;$fs->get_text_x_inline:1,2c;$this->_parent->get_text_inline;$this->_parent->get_text_x_inline:1,2c;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c X-Poedit-SearchPath-0: . X-Poedit-SearchPathExcluded-0: *.js X-Poedit-SourceCharset: UTF-8 %s பதிப்பின் பாதுகாப்பு & வசதி மேம்படுத்தல் மற்றும் உதவிக்கு%s.%1$sன் விலையுள்ள பதிப்பு ஏற்கனவே நிறுவப்பட்டுள்ளது. %2$s வசதிகளை பயன்படுத்த அதை செயல்படுத்தவும். %3$sவருகின்ற அனைத்து பணம் செலுத்துதல்களையும் %1$s உடன் நிறுத்துகிறது, மற்றும் உங்கள் %2$s திட்ட உரிமம் %3$sல் காலாவதியாகிறது."%s" செயல்படுத்தலை முடியுங்கள்%s ஆட்-ஆனை வாங்கிவிட்டீர்கள்.%s நிறுவுதல்கள்%s உரிமங்கள்%s முன்பு%sம் அதன் ஆட்-ஆன்களும்ஒரு வாடிக்கையாளர் புது உரிமம் வாங்கும்போது %s லாபப் பங்கீடு.ஆட் ஆன் விலையுள்ளது என்பதால் %sன் விலையில்லா முன்னோட்டம் ரத்தானது. நீங்கள் உரிமம் வாங்கிப் பயன்பாட்டைத் தொடரலாம்.%s ஒரு விலையுள்ள ஆட்ஆன். ப்ளக்இன் செயல்பட நீங்கள் உரிமம் வாங்கவேண்டும்கணக்கின் புதிய உரிமையாளர் %s.%s குறைந்தபட்ச பணம் பெறுதல்.%s அல்லது அதிகமாக%s மதிப்பீட்டெண்%s மதிப்பீட்டெண்கள்%s sec%s நட்சத்திரம்%s நட்சத்திரங்கள்%s முறை%s முறைகள்%s பாதுகாப்பு மேம்படுத்தல் மற்றும் உதவிக்கு %sவருமானம் அதிகரிக்க, முதல் தள வருகையில் %s tracking cookie.%sன் விலையுள்ள வசதிகள்உங்களுக்கு வேண்டிய தளங்களில் உரிமத்தை செயல்படுத்த %s கிளிக் செய்க %sAPI←➤கணக்குகணக்கு விபரங்கள்செயல்கள்செயல்படுத்து%s செயல்படுத்து%s திட்டம் செயல்படுத்த%s வசதிகளை செயல்படுத்தவிலையில்லா பதிப்பை செயல்படுத்தஉரிமம் செயல்படுத்தமீதமுள்ள எல்லா தளங்களிலும் உரிமத்தை செயல்படுத்துகவலைப்பின்னலில் உள்ள எல்லா தளங்களிலும் உரிமத்தை செயல்படுத்துகஆட்-ஆன் செயல்படுத்தசெயல்படுத்தப்பட்டது%sக்கான ஆட்ஆன்கள்Module %sன் ஆட்ஆன்கள்அடுத்த தளத்தைச் சேர்க்கஆட் ஆன்ஆட்-ஆன்ஸ்ஆட்ஆன் WordPress.org அல்லது Freemius ஏதாவது ஒன்றில் வரிசைப்படுத்தப் பட்டிருக்க வேண்டும்.முகவரிமுகவரி வரி %dAffiliateபுரிந்துணர்வுவிலையில்லா %sக்குப் பிறகு, குறைந்தளவு %s செலுத்துங்கள்ஒப்புக்கொண்டு உரிமத்தை செயல்படுத்துகஅனைத்து வேண்டுகோள்கள்அனைத்து மாதிரிகள்அனுமதித்து தொடர்கஇல்லாவிட்டால், Network-level கணக்குப் பக்கத்தில் எப்போது வேண்டுமானாலும் உரிமத்தை செயல்படுத்திக் கொள்ளலாம்.தொகை%sலிருந்து %s (பணம் செலுத்திய) பதிப்பின் தானியங்கி பதிவிறக்கமும், நிறுவுதலும் %sல் ஆரம்பமாகும். இதை நீங்களே செய்ய விரும்பினால் ரத்து செய்யும் பட்டனை அழுத்தவும். உபயோகிப்பாளரின் பீட்டாவை செயல்படுத்துகையில், புதிய தவறு உருவாகியுள்ளது.என்னதென்றே தெரியாத ஒரு தவறு நேர்ந்துவிட்டதுஎச்சரிக்கை: %sன் முன்னோட்ட Beta பதிப்பின் மேம்பட்ட வடிவம் பழைய பதிப்பின் மீது நிறுவப்படுகிறது. அனாமதேய பின்னூட்டம்மீதமுள்ள அனைத்து தளங்களின் மீதும் பிரயோகிக்கவலைப்பின்னலின் அனைத்து தளங்களின் மீதும் பிரயோகிக்கAffiliate ஆக விண்ணப்பியுங்கள்அனைத்து Freemius தகவலையும் அழிக்க விருப்பமா?மேலே தொடர விருப்பமா?வாடிக்கையாளர் 30 நாட்களுக்கு பணம் திரும்பப் பெறலாம் Refund என்பதால் உங்களுக்கு லாபப் பங்கீடு 30 நாட்களுக்குப் பிறகே கிடைக்கும்.முன்னரே தெரிவு செய்திருந்தால் மட்டுமே தானியங்கி நிறுவுதல் நடைபெறும்.%sல் தானாக புதுப்பிக்கிறதுதானியங்கி நிறுவுதல்உத்தேச மதிப்பீட்டெண்அடி தூள்Affiliate ஆகுங்கள்பீட்டாவசூல்பில் & இன்வாய்ஸ்தடுக்கப்படுகிறதுBlog IDஅமைப்புகூட்டுத்திட்டம்தொழிலின் பெயர்புது உரிமம் வாங்குங்கள்உரிமம் வாங்கLicense Key காணவில்லையா?ரத்து%s ரத்து செய்க & தொடர்கநான் %sஐ இந்த தளத்திலோ அல்லது பிற தளத்திலோ உபயோகிக்க விரும்பவில்லை என்பதால் %sக்கு உதவியோ, பாதுகாப்பு மேம்படுத்தலோ தேவையில்லை. %sஐ ரத்து செய்யவும்.%s ரத்து செய்யவா?நிறுவுதலை ரத்துசெய்சந்தாவை ரத்து செய்வெள்ளோட்டம் ரத்து செய்கரத்தானது%s ரத்தாகிறது%s ரத்தாகிறது...சந்தா ரத்தாகிறதுவெள்ளோட்டத்தை ரத்து செய்தால் அனைத்து விலையுள்ள வசதிகளும் நிறுத்தப்படும். சரியா?உரிமம் மாற்றஉரிமை மாற்றம்திட்டம் மாற்றCheckoutஊர்API Cache நீக்கClear Updates Transientsபிளக்இன்னை அநாமதேயமாக பயன்படுத்த இங்கே கிளிக் செய்யவும்%s குறித்த மதிப்பீடு & விமர்சனங்களை கிளிக் செய்து காண்ககிளிக் செய்து %dன் முழுஅளவு திரைநகல் காண்கதயாரிப்புகள்Codeஒத்திசைவு உயர்நிலைதொடர்புஉதவியை அணுகவும்தொடர்பு கொள்ளுங்கள்பங்கெடுத்தோர்%sஐ செயல்படுத்த முடியவில்லை.நாடுCron மாதிரிதேதிசெயல்நிறுத்துஉரிமத்தை செயல்நிறுத்த%sஐ செயல்நிறுத்தினாலோ, அகற்றினாலோ பிற தளங்களில் பயன்படுத்தும் வாய்ப்பிருந்தும், உரிமம் தானாகவே செயலிழக்கும்.உரிமத்தை செயல்நிறுத்துவதானது, அனைத்து விலையுள்ள வசதிகளையும் நிறுத்திவிடும். ஆனாலும் பிற தளங்களில் செயல்படுத்தலாம். தொடரலாமா?செயல்நிறுத்துDebug Logதள நிர்வாகிகளுக்கான சிறப்பாளர்அனைத்து கணக்குகளையும் அழிக்கவிபரங்கள்%s ரத்துசெய்ய வேண்டாம் - அதற்கு உதவியை அணுகவும், பாதுகாப்பு மேம்படுத்தல்களைப் பெறவும் விரும்புகிறேன்.License Key இல்லையா?இந்த பிளக்இன்னுக்கு நன்கொடை தாருங்கள்உங்கள் திட்டம் கீழ்ப்படுத்தப்படுகிறதுபதிவிறக்கு%s பதிப்பை பதிவிறக்கலாம்%sன் அண்மைய பதிப்பைப் பதிவிறக்கலாம்புதிய பதிப்பை பதிவிறக்கலாம்பதிவிறக்கப்பட்டதுபுதிய %s EU GDPRன் படி %s மேற்பார்வை விதிகளால், ஆட்சேபகர தகவலுக்கு எதிரான உங்கள் நிலையை உறுதி செய்கிறீர்கள் :)எங்கள் affiliate திட்ட விதிமுறை மீறல் காரணமாக உங்கள் affiliate கணக்கை தற்காலிகமாக தடை செய்கிறோம். கேள்விகள் இருந்தால் உதவியை தொடர்பு கொள்ளவும்.மேம்படுத்தல் நடைபெறும்போதே இன்னும் %d தளங்களில் உரிமம் செயல்பாட்டில் இல்லை என்று அறிகிறோம்.மேம்படுத்தல் நடைபெறும்போதே Networkல்லுள்ள %s தளங்கள் உங்கள் கவனிப்பைக் கோருகின்றன என்றறிகிறோம்.மின்னஞ்சல்மின்னஞ்சல் முகவரிமுடிவு%sஐ எந்தெந்த தளங்களில் முன்னிலைப் படுத்துவீர்களோ அந்தத் தளங்களின் பெயர்களை உள்ளிடுங்கள்.தரம் உயர்த்துதலின் போது நீங்கள் உள்ளிட்ட மின்னஞ்சல் முகவரியைத் தந்தால், license keyஐ மீண்டும் அனுப்புகிறோம்.தவறுசெர்வரிடம் இருந்து தவறுச் செய்தி வந்திருக்கிறது.காலாவதியானது%sல் காலாவதியாகிறதுமேலதிக தளங்கள்தயாரிப்புகளை சந்தைப்படுத்தும் மேலதிக தளங்கள்.FileFilterWordpress.orgயின் வழிகாட்டு நெறிமுறைகள்படி, உங்கள் வெள்ளோட்டம் துவங்கும்முன் நாங்கள் கேட்டுக் கொள்வதெல்லாம், உங்கள் பயன்பாட்டுத் தகவலை நாங்கள் பின்தொடர எங்களை அனுமதிக்கும் தெரிவை தெரிவு செய்யுங்கள் என்பதே. இது %sஐ அனுமதித்து தகவலை %sக்கு அனுப்பச்செய்து மேம்படுத்தலுக்கு உதவும். மற்றும் உங்கள் வெள்ளோட்டத்தை உறுதிசெய்யும்.விலையில்லைவிலையில்லா வெள்ளோட்டம்விலையில்லா பதிப்புFreemius APIFreemius தவறுநீக்கி Debugப்ளக்இன் முக்கிய கோப்பை Freemius SDKவால் கண்டறிய முடியவில்லை. தயவுசெய்து பின்வரும் செய்தியுடன் sdk@freemius.comக்கு மின்னஞ்சல் அனுப்பவும்Freemius நிலைமுழுப்பெயர்Functionதானியங்கி சந்தா புதுப்பித்தலுக்கும் லாபப் பங்கீடு பெறுங்கள்.%sன் பீட்டா பதிப்பு மேம்படுத்தலைப் பெறுங்கள்.License key உள்ளதா?வணக்கம். %sன் முகவர் திட்டம் குறித்து உங்களுக்குத் தெரியுமா? %sஐ நீங்கள் விரும்பினால், நீங்களும் முகவராகி பணம் ஈட்டலாம்!%sஐ எந்தளவு விரும்புகிறீர்கள்? %d-நாள் விலையில்லா வெள்ளோட்டத்தில் %sன் விலையுள்ள வசதிகளை சோதித்துப் பாருங்கள்.பதிவேற்றுதல் மற்றும் செயல்படுத்துதல் எப்படி?எங்களை நீங்கள் எப்படி முன்னிலைப் படுத்துவீர்கள்?இதற்குமேல் பணம் செலுத்தமாட்டேன்இதை எப்படி உபயோகிப்பது என்று எனக்குப் புரியவில்லைஎன் தனிப்பட்ட தகவலை உங்களோடு பகிர விரும்பவில்லை.எனக்கு வேறு ஒரு நல்ல %s கிடைத்துவிட்டதுஎன் திட்டத்தை மேம்படுத்திய பின்னும், என் உரிமம் %s என்பதாகவே காட்டுகிறது.இனி எனக்கு %s தேவையில்லைகுறுகிய காலத்திற்கு மட்டும் %s போதும்ஐடிஇதை கிளிக் செய்தால், இந்த முடிவு தள நிர்வாகிகளுக்கு அனுப்பப்படும்.ஏன் நீங்கள் %s என்பதை எங்களுக்குத் தெரிவியுங்கள்உங்கள் %s கணக்கின் உரிமையை %sக்கு மாற்றிட விரும்பினால் உரிமை மாற்றம் பட்டனை அழுத்தவும்.அந்தத் தளங்களிலும் %sஐ உபயோகிக்க விரும்பினால், கீழே License Key உள்ளிட்டு செயல்படுத்தலை அழுத்தவும்.முக்கியமான மேம்படுத்தல் அறிவிப்பு%sல்ஒருவேளை %s இந்த தளத்திலோ அல்லது பிற தளத்திலோ உபயோகிக்கவில்லை என்றால் %sஐ ரத்து செய்ய விரும்புகிறீர்களா?விலையில்லா பதிப்பை நிறுவலாம்விலையில்லா பதிப்பின் மேம்படுத்தலை நிறுவலாம்நிறுவலாம்மேம்படுத்தலை நிறுவலாம்%s: பிளக்இன் நிறுவப்படுகிறதுmodule ID தவறானது.தவறான தள விவர சேர்ப்புஇன்வாய்ஸ்Is Activeஉங்கள் உரிமம் செயல்பாட்டுக்கு வரவில்லையென தோன்றுகிறது.உரிமத்தின் செயல்நிறுத்தம் தோல்வி அடைந்ததுபோல் தெரிகிறது.வெள்ளோட்டத்தில் நீங்கள் இல்லை என்பதால், அதை ரத்துசெய்யத் தேவையில்லை :)நீங்கள் இன்னும் %s திட்டத்திலேயே இருப்பதாகத் தெரிகிறது. நீங்கள் திட்டத்தை மாற்றிய பின்னர் இப்படி இருந்தால், அது எங்கள் தவறு. மன்னிக்கவும். உங்கள் தளத்திற்கு உரிமம் ஏதும் இல்லை என்பது போல் தெரிகிறது.சரிபார்க்கும் வகையினங்களில் ஏதோ ஒன்று தவறுபோல் தெரிகிறது. உங்கள் Public Key, Secret Key & User ID ஆகியவற்றை சரிபார்த்து மீண்டும் முயற்சிக்கவும்.நான் எதிர்பார்த்தது இதுவல்ல.பீட்டா பதிப்பு சோதனையில் சேரவும்%sன் ஆட்-ஆன் தகவலை வெளியிலுள்ள சர்வர் மூலம் எடுக்கிறோம் என்பதை அறியவும்.Keyஎன்ன வேலை செய்யவில்லை என்பதை விளக்கமாக சொன்னால், அதை நாங்கள் சரி செய்வோம்.காரணம் எதுவென்று சொன்னால் எங்களை மேம்படுத்திக் கொள்வோம்.கடைசிகடைசி மேம்படுத்தல்கடைசி உரிமம்சமீபத்திய விலையில்லா பதிப்பு நிறுவப்பட்டதுசமீபத்திய பதிப்பு நிறுவப்பட்டதுமேலும் அறியநீளம்உரிமம்உரிம ஒப்பந்தம்License KeyLicense keyLicense key காலியாக உள்ளது.வாழ்நாள்%sஐ விரும்புகிறீர்களா? எங்கள் Ambassador ஆக பணியாற்றி பணம் பெறலாம் :-)Load DB OptionLocalhostLogLoggerசெய்திவழிமுறைMigrate Options to Networkஅலைபேசி செயலிகள்ModuleModule PathModule மாதிரி%s குறித்த மேலதிக தகவல்பெயர்Network BlogNetwork பயனர்புதியதுபுதிய பதிப்பு கிடைக்கிறதுபுதிய விலையில்லா பதிப்பு (%s) நிறுவப்பட்டதுபுதிய பதிப்பு (%s) நிறுவப்பட்டதுசெய்திக்கடிதம்அடுத்துஇல்லைஐடி இல்லை%sக்கு எந்தக் கடப்பாடும் இல்லை - எப்போதும் ரத்து செய்யலாம்!%s நாட்களுக்கு எந்தக் கடப்பாடும் இல்லை - எப்போதும் ரத்து செய்யலாம்!கடன் அட்டை தேவையில்லைகாலாவதியாகாதுகாலாவதியாகாதது%sன் எந்தத் திட்டங்களிலும் வெள்ளோட்டம் இல்லை.O.Kஉங்கள் உரிமம் முடிந்ததும் நீங்கள் அனைத்து விலையில்லா வசதிகளையும் பயன்படுத்திக் கொள்ள முடியும். ஆனால் %s வசதிகளை அணுக இயலாது.மீண்டும் உரிமத்தை செயல்படுத்தினால் தவிர, உரிமம் காலாவதியானால் %s பயன்படுத்த முடியாது.தெரிவு செய்தெரிவை அகற்று"%s"ஐ சிறப்பானதாக்க தேர்வு செய்யுங்கள்மற்றவைஉரிமையாளர் மின்னஞ்சல்உரிமையாளர் IDஉரிமையாளர் பெயர்PCI compliantவிலையுள்ள ஆட்ஆன் Freemiusல் வரிசைப்படுத்தப் பட்டிருக்க வேண்டும்.PayPal கணக்கின் மின்னஞ்சல் முகவரிபணம் செலுத்தல்கள்பணம் பெறுதல் USDயில் மாதாமாதம் PayPal மூலம் பெறலாம்.திட்டம்%s திட்டம் இல்லை. வெள்ளோட்டம் துவங்க இயலாது.%s திட்டத்திற்கு வெள்ளோட்டம் கிடையாது.திட்ட IDஎங்களை இங்கு அணுகலாம்பின்வரும் செய்தியோடு எங்களைத் தொடர்பு கொள்ளுங்கள்%sஐ பதிவிறக்கலாம்.உங்கள் மின்னஞ்சலுக்கு வந்த License Keyஐ உள்ளிடுங்கள்:இணைய தள அல்லது சமூக ஊடக வருகையாளர் எண்ணிக்கை, மின்னஞ்சல் சந்தாதாரர்கள், பின்தொடர்வோர் போன்ற புள்ளிவிவரங்கள் தருக. (நாங்கள் ரகசியம் காப்போம்)மேம்படுத்தலை முடித்துவைக்க பின்வரும் வழிமுறையைப் பின்பற்றவும்பாதுகாப்பு & மேம்படுத்தல், விளக்கவுரை மற்றும் தள்ளுபடி விவரங்களை உங்களுக்கு நாங்கள் அனுப்ப விரும்பினால் எங்களைத் தொடர்பு கொள்ளுங்கள்.தயவுசெய்து கவனிக்கவும். ரத்துசெய்த பிறகு மீண்டும் புதிய சந்தா/புதுப்பித்தலுக்கு பழைய விலையை எங்களால் வசூலிக்க முடியாது. விலை ஆண்டுக்கொரு முறை உயரும். நீங்கள் இனி புதுப்பிக்க விரும்பினால் புதிய விலையை செலுத்தவேண்டும்.%sஐ எப்படி முன்னிலைப் படுத்துவீர்கள் என்ற விவரம் தரவும். (தயவுசெய்து குறிப்பிட்டுச் சொல்லவும்)உங்கள் முழுப் பெயரைத் தரவும்.ப்ளக்இன்பிளக்இன் முகப்புப்பக்கம்பிளக்இன் IDபிளக்இன் நிறுவுதல்Changelogவிளக்கம்FAQவசதிகள் & விலைநிறுவுதல்பிற குறிப்புகள்கருத்துரைகள்விலையுள்ள நிரல் இல்லாததால் பிளக்இன் "Serviceware" எனப்படும்.பிளக்இன்கள்பிளக்இன் & தீம் SyncPremium%s விலையுள்ள பதிப்பு வெற்றிகரமாக செயல்பாட்டுக்கு வந்தது.விலையுள்ள ஆட்-ஆன் பதிப்பு ஏற்கனவே நிறுவப்பட்டுள்ளது.விலையுள்ள பதிப்புவிலையுள்ள பதிப்பு ஏற்கனவே செயலில் உள்ளது.விலை விவரம்தனியுரிமைக் கொள்கைகள்தொடர்கProcess IDசெயலில்தயாரிப்புகள்திட்டத்தின் சுருக்கம்முன்னிலைப்படுத்தும் வழிமுறைகள்மாநிலப் பரப்புPublic Keyஉரிமம் வாங்குங்கள்மேலும் வாங்குகஉடனடி பின்னூட்டம்ஒதுக்கீடுசெயல்படுத்தும் மின்னஞ்சலை மீண்டும் அனுப்புகஎங்கள் %sக்கு புதிய வாடிக்கையாளர்களை பரிந்துரை செய்து, ஒவ்வொரு விற்பனைக்கும் %s லாபப் பங்கீடாகப் பெறலாம்.உரிமத்தை புதுப்பியுங்கள்உரிமத்தை புதுப்பியுங்கள்வேண்டுகோள்கள்WordPress பதிப்பை வேண்டுகிறதுமுடிவுSDKSDK Path%s சேமிக்கலாம்பட்டியலிட்ட Cronsதிரை நகல்கள்முகவரி மூலம் தேடSecret Keyபாதுகாப்பான HTTPS %s பக்கம், வெளி முகவரியிலிருந்து இயங்குகிறதுஉங்கள் சந்தா ரத்து செய்வதில் ஒரு தொழில்நுட்பக் கோளாறு. மீண்டும் முயற்சிக்கவும்.94%match உங்கள் வெள்ளோட்டம் ரத்து செய்வதில் ஒரு தொழில்நுட்பக் கோளாறு. மீண்டும் முயற்சிக்கவும்புதிய பதிப்பு உங்களுக்குக் கிடைத்துவிட்டது போல் தெரிகிறது.நாட்டைத் தேர்ந்தெடுக்கLicense key அனுப்புகSet DB OptionSimulate Network UpgradeSimulate Trial Promotionஒரு தள உரிமம்இணையதள ஐடிதளம் தெரிவு செய்யப்பட்டது.தளங்கள்கடந்திடு & %sSlugசமூக ஊடகங்கள் (Facebook, Twitter etc.)தவறுக்கு வருந்துகிறோம். எங்களுக்கு ஒரு வாய்ப்புத் தந்தால் உங்களுக்கு உதவக் காத்திருக்கிறோம்.மன்னிக்கவும்... இன்னொரு பயனாளர் இதே மின்னஞ்சல் முகவரியுடன் ஏற்கனவே பதிவு செய்திருக்கிறார்.துவக்கம்வெள்ளோட்டம் துவக்குஎன் விலையில்லா %sஐ துவக்கவும்மாநிலம்சமர்ப்பி & %sசந்தாஉதவிஉதவி மையம்Sync Data From ServerTax / VAT IDசேவை நிபந்தனைகள்எங்கள் affiliate திட்டத்திற்கு விண்ணப்பித்ததற்காக நன்றி. எதிர்பாரா விதமாக உங்கள் விண்ணப்பம் தள்ளுபடி செய்யப்பட்டது. 30 நாட்களில் மீண்டும் விண்ணப்பிக்கவும்.எங்கள் affiliate திட்டத்திற்கு விண்ணப்பித்ததற்காக நன்றி. உங்கள் விவரங்களை பரிசீலித்து, 14 நாட்களில் மேலதிக தகவலோடு தொடர்பு கொள்கிறோம்.%s மற்றும் அதன் ஆட்-ஆன் பயன்படுத்துவதற்கு நன்றி!%s பயன்படுத்துவதற்கு நன்றி!எங்கள் உருவாக்கங்களைப் பயன்படுத்துவதற்கு நன்றி!நன்றி!நன்றி %s!உரிமை மாற்றத்தை உறுதிப்படுத்தியதற்கு நன்றி. இறுதி ஒப்புதலுக்காக %sக்கு இப்போது ஒரு மின்னஞ்சல் அனுப்பப்பட்டுள்ளது.%s எனது இணையதளத்தை செயலிழக்க வைத்துவிட்டது%s வேலை செய்யவில்லை%s நான் எதிர்பார்த்தது போல் இல்லை%s நல்லதுதான். ஆனால், எனக்கு தேவைப்படும் வசதி இதில் இல்லை%s சரிவர வேலை செய்யவில்லை%s திடீரென நின்றுவிட்டதுநிறுவப்படுகிறது. சில நிமிடங்கள் காத்திருக்கவும். இந்தப் பக்கத்தை Refresh செய்யவேண்டாம்.பெயர் மாற்றமுடியாது. Slug உடனான folder, பிளக்இன் பேக்கில் இல்லை.%sன் மேம்படுத்தல் முடிந்ததுதீம்தீம் மாற்றம்தீம்கள்%sன் %s கிடைக்கிறது.%sன் புதிய பதிப்பு இப்போது கிடைக்கிறது.இந்த பிளக்இன் உங்கள் WordPress பதிப்புடன் ஒத்திசைவானது என்று குறிக்கப்படவில்லை.இந்த பிளக்இன் உங்கள் தற்போதைய WordPress பதிப்புடன் சோதிக்கப்படவில்லை.நேர முத்திரைதலைப்புமொத்தம்நகர்வெள்ளோட்டம்மாதிரிFilesystem அணுக இயலவில்லை. உங்கள் உள்ளீடு சரியா என சோதிக்கவும்.பல்தள உரிமம்அளவில்லா மேம்படுத்தல்கள்அளவில்லா லாபப் பங்கீடு.%s தளங்கள் வரைமேம்படுத்துஉரிமம் மேம்படுத்தமேம்படுத்தல், அறிவிப்புகள், வணிக செய்திகள். Spam இல்லைமேம்படுத்துபதிவிறக்கிய பதிப்பை பதிவேற்றி செயல்படுத்தலாம்W00tஉபயோகிப்பாளர் ஐடிபயனர்கள்Valueஉறுதிப்படுத்தும் மின்னஞ்சல் %sக்கு அனுப்பப்பட்டுள்ளது. பார்க்கவும். 5 நிமிடத்தில் மின்னஞ்சல் வரவில்லை என்றால் Spamல் பார்க்கவும்.உறுதிசெய்யப்பட்டதுமின்னஞ்சல் சரிபார்த்திடுங்கள்%s பதிப்பு வெளியாகிவிட்டது.விபரங்களைப் பாருங்கள்விலையுள்ள வசதிகள் என்னவென்று காணுங்கள்எச்சரிக்கைஇந்த மின்னஞ்சலின் பதிவில் எந்த உரிமமும் இல்லை. தங்கள் மின்னஞ்சல் சரியானதா?உங்கள் மின்னஞ்சல் முகவரியைக் காணவில்லை. நீங்கள் அளித்தது சரியானதா?. %s, %sக்கு சில சுவாரஸ்யங்களை உருவாக்கியிருக்கிறோம்Freemius network-level integrationஐ அறிமுகம் செய்வதில் பேருவகை அடைகிறோம்.இணையதளம், மின்னஞ்சல் மற்றும் சமூக ஊடக புள்ளி விவரங்கள் (விரும்பினால் தரலாம்)நீங்கள் என்ன எதிர்பார்த்தீர்கள்?என்ன வசதி?உங்கள் %s என்ன?என்ன விலை உங்களுக்கு வசதியாக இருக்கும்?நீங்கள் என்ன எதிர்பார்க்கிறீர்கள்?%sன் பெயர் என்ன?%sஐ எங்கு எப்படி முன்னிலைப் படுத்துவீர்கள்?WordPress.org பிளக்இன் பக்கம்மேம்படுத்தப்பட்ட பதிப்பில் தொடர விரும்புகிறீர்களா?ஆம்ஆம் - %sநீங்கள் ஏற்கனவே வெள்ளோட்டம் பார்த்துவிட்டீர்களே.%2$s திட்டத்தின் %1$s-நாள் விலையில்லா வெள்ளோட்டத்தைத் துவக்க இன்னும் 1 கிளிக் மட்டுமே.நல்லது... மகிழ்ச்சிநீங்கள் %sஐ வெள்ளோட்ட நிலையில் உபயோகித்துக் கொண்டிருக்கிறீர்கள்.இன்னும் ஒருபடி அருகில் - %sநீங்கள் %sன் வசதிகளை பயன்படுத்த முடியும். ஆனால் %s பாதுகாப்பு & மேம்படுத்தல் மற்றும் உதவியை அணுக இயலாது.விலையுள்ள பதிப்பை அணுக உங்களிடம் உரிமம் இல்லை.உங்களிடம் %sன் உரிமம் உள்ளதுஉங்கள் %s மேம்படுத்தப்பட்டது.விட்டுவிட்டீர்கள், ஆனாலும் நீங்கள் எந்த தகவலையும் பகிர வேண்டியதில்லை %sதெரிந்தெடுப்பு மட்டுமேஉங்கள் பயன்பாட்டைப் பின்தொடர எங்களுக்கு நீங்கள் அளித்த அனுமதியானது, %sஐ மேம்படுத்த உதவும்.உங்கள் பயன்பாட்டைப் பின்தொடர எங்களுக்கு நீங்கள் அளித்த அனுமதியானது, எங்கள் உருவாக்கத்தை மேம்படுத்த உதவும்.உங்கள் %s ஆட்-ஆன் திட்டம் மேம்படுத்தப்பட்டது.உங்கள் %s விலையில்லா வெள்ளோட்டம் ரத்து செய்யப்பட்டது.%s திட்டத்தில் உங்கள் கணக்கின் செயல்பாடு துவங்கியது.%sக்கான உங்கள் Affiliate விண்ணப்பம் ஏற்கப்பட்டது. உள்நுழைந்து %sல் உங்கள் affiliate areaவை அணுகவும்.உங்கள் affiliate கணக்கு தற்காலிகமாக இடைநிறுத்தப்பட்டுள்ளது.உங்கள் மின்னஞ்சல் சரிபார்க்கப்பட்டது - நன்றி!உங்கள் விலையில்லா வெள்ளோட்டம் முடிந்தது. %3$sஐ தொடர்ந்து பயன்படுத்த %1$s %2$sஇவற்றை மேம்படுத்துங்கள்.உங்கள் வெள்ளோட்டம் முடிந்தது. ஆனாலும் பிற விலையில்லா சேவைகளைத் தொடரலாம்.உங்கள் உரிமம் ரத்தானது. இதில் தவறேதும் உணர்ந்தால் உடனடியாக எங்கள் உதவியை அணுகவும்.உங்கள் உரிமம் முடிந்தது. %3$sஐ தொடர்ந்து பயன்படுத்த %1$s %2$s இவற்றை மேம்படுத்துங்கள்.உங்கள் உரிமம் முடிந்தது. எனினும் நீங்கள் %sன் வசதிகளைத் தொடரலாம். எனினும், தொடர் மேம்படுத்தல் மற்றும் உதவிக்கு உங்கள் உரிமத்தைப் புதுப்பிக்கவும்.உங்கள் உரிமம் முடிந்தது. ஆனாலும் %sன் விலையில்லாப் பதிப்பை என்றும் தொடரலாம். உங்கள் உரிமம் செயல்படுத்தப்பட்டது.உங்கள் உரிமம் செயல்நிறுத்தப்பட்டது, %s திட்டத்திற்கு மாற்றப்பட்டுள்ளீர்கள்.உங்கள் பெயர் ஏற்றப்பட்டது.உங்கள் தேர்ந்தெடுத்த திட்டம் துவங்கியது.உங்கள் தேர்ந்தெடுத்த திட்டம் %sக்கு மாறியது.உங்கள் தேர்ந்தெடுத்த திட்டம் மேம்படுத்தப்பட்டது.உங்கள் சந்தா ரத்து செய்யப்பட்டது. உங்கள் %s திட்டம் %s அன்று காலாவதியாகிறது.உங்கள் வெள்ளோட்டம் துவங்கியதுZIP / தபால் குறியீடுRight onசெயல்பாட்டில்%s இல்லாமல் %s இயங்காதுப்ளக்இன் இல்லாமல் %s இயங்காதுHeads upஅனுமதி%s இருக்கிறதுசெயல்படுத்துகிறதுவருடம்APIபோய்த் தொலைதவறை சோதிக்கிறதுவாழ்த்துக்கள்தடுக்கப்பட்டதுஇணைக்கப்பட்டதுஅண்மைய பதிப்பை பதிவிறக்கஅண்மைய விலையில்லா பதிப்பை பதிவிறக்கமாதாமாதம்காலாவதிவழிமின்னஞ்சல் அனுப்பப்படுகிறதுமாதம்ஆண்டுஆண்டுக்காண்டுஒருமுறைதிட்டம்No SecretSDK பதிப்புகள்உரிமம்SyncSync Licenseஉருவாக்கியவர்OffOn%s அடிப்படையிலானதுவிலையில்லா வெள்ளோட்டம் தொடங்கட்டும்... டும்போய்த் தொலைபோய்த் தொலைசெயல்நிறுத்தப்படுகிறதுdelegateபாதுகாப்பு & மேம்படுத்தல், விளக்கவுரை மற்றும் தள்ளுபடி விவரங்களை எனக்கு அனுப்பவும். %s செய்க, %s தேவையில்லை%s திட்டம்%s பில் எழுதப்பட்டதுசிறப்புHeyஅரே ஓ சம்போ!வணக்கம் %s,நிறுவப்பட்டதுYee-hawஉரிமம்தளங்கள்msபுதிய பீட்டா பதிப்புபுதிய பதிப்புஉறுதிப்படுத்தப்படவில்லைவிலைவிலை விவரம்பதிப்புsecபாதுகாப்பு & மேம்படுத்தல், விளக்கவுரை மற்றும் தள்ளுபடி விவரங்களை எனக்கு அனுப்பவும்.கடந்திடுHmmவெள்ளோட்டம் துவங்கலாம்சந்தாswitching%sன் அண்மைய பதிப்பு இதோவெள்ளோட்டம்வெள்ளோட்டம்அழிதரமிறக்குதிருத்துமறைத்திடுதெரிவு செய்தெரிவை அகற்றுவாங்குககாட்டுகடந்திடுமேம்படுத்துமேம்படுத்து%s முன்புPK!yM|M|.libraries/freemius/languages/freemius-da_DK.monu[lS% 7 COV#i    . 6 F N W c t   & - ! !$!3!H![!b!j!r! ! !'!! ! !!""'":""V"y"2"!"0"#.#E#T#\#p#u#}### ## #### $$$$ 8$ E$ O$]$n$$$ $ $$$$ $($%%::%u%z%%% % %%% %% %% & &&5&I&Q&k&&&&&&& ''"' ('6'a:'''' ' ';'"('(.( 3( >( K(X(g( v(((U(()())C)-m))S)*'*C*7F*~**** *** ++'+ >+1H+.z+O+A+;,[,q,Bu,,,, , ,-"- ;-F-M-U- g- r- ~---- ------- -. . ..9. >. K.X.\. r.!~.. ....%.+.(/ @/ N//[////// // / // 040I05N0(000-00U11d1~1242;2 K2U2(d2*2"212+ 3*93&d3333.3)3 44:4B4Q4 Y4 d4o4x444 44 4444 4 5 5)5D5K5O5X5`5f5 v55 5&555 55 6!6)6E6 K6U6 Z6&f6L6f6A7 G7 S7_7p7 v77 7 77 77 77/78);8 e8 p8\{88889C'9k99-99 99'9M:G_: ::::::::: ;;;*.;Y;*a;^;;;< <<< <-< F<S<f<in<W<"0=6S== ==-== >& >G>*a>>>$>M> ?/?N?>n?? ?&?Z @Tg@R@.A9>A<xAPAUB_\BBKWC(CGC#D%8D)^D$D)DDDEE;3E6oE>EEEF&F$_``6`W=`6````"`a 3a=aEa La Ya ca pa}aaa aaaaaa a aa a abb $b2bBbEb ]b%ibb bbbb.b4b1c Oc ]c1kcccc!cccc cdd 0d:;dvd@{d,ddd)e*e>@e;ee Afbfif xff f fff f f ff#fg'!g,Igvggggg gg ggg hh "h .h9hIhOh lhyhhhhhhhhhhh i0i MiXiji~ii ii iii i'iP'jhxjjjj k"k'k8k =k GkRk Zkgk kk8kk)k l(ld0lllllLl.m#=m"amm mm$mUmKn anmnsnyn |nnnnnnnn2o4o)=ogo lo xo ooo ooo oootp_yp+p>qDqXqiq,|qqqqq# r-r0r(8rOar r/rr> sLsas"sYsSsTQt5t't8uT=u_ueuXvXvHw9hwww"wx x>xOxVxlxrxxxx x xxxx xxx xyy 3y>yGy KyYy\ydy lyzyy yy yy yyyyyz zz z zQ)z{z zzzzzzz zzz zzz zz{ {{{ ${.{L2{ {{{ {{{ { {{ {|||||#| '|3|;|D|%s - plugin name. As complete "PluginX" activation nowComplete "%s" Activation Now%s Add-on was successfully purchased.%s Installs%s Licenses%s ago%s and its add-ons%s is the new owner of the account.%s or higher%s rating%s ratings%s sec%s star%s stars%s time%s times%s's paid featuresAPIASCII arrow left icon←ASCII arrow right icon➤AccountAccount DetailsActionsActivateActivate %sActivate %s PlanActivate %s featuresActivate Free VersionActivate LicenseActivate license on all pending sites.Activate license on all sites in the network.Activate this add-onActivatedAdd Ons for %sAdd Ons of module %sAdd another domainAdd-OnAdd-OnsAddressAddress Line %dAffiliateAffiliationAfter your free %s, pay as little as %sAgree & Activate LicenseAll RequestsAll TypesAllow & ContinueAmountAn unknown error has occurred.Anonymous feedbackApply on all pending sites.Apply on all sites in the network.Apply to become an affiliateAre you sure you want to delete all Freemius data?Are you sure you want to proceed?Auto installation only works for opted-in users.Auto renews in %sAutomatic InstallationAverage RatingAwesomeBecome an affiliateBetaBillingBilling & InvoicesBlockingBlog IDBusiness nameBuy a license nowBuy licenseCan't find your license key?CancelCancel %s & ProceedCancel %s?Cancel InstallationCancel SubscriptionCancel TrialCancelledCancelling %sCancelling %s...Cancelling the subscriptionChange LicenseChange OwnershipChange PlanChange UserCheckoutCityClear API CacheClick hereClick here to use the plugin anonymouslyClick to view full-size screenshot %dClone resolution admin notice products list labelProductsCodeCompatible up toContactContact SupportContact UsContributorsCouldn't activate %s.CountryCron TypeDateDeactivateDeactivate LicenseDeactivationDebug LogDelegate to Site AdminsDelete All AccountsDetailsDon't have a license key?Donate to this pluginDowngrading your planDownloadDownload %s VersionDownload Paid VersionDownload the latest %s versionDownload the latest versionDownloadedDuplicate WebsiteEmailEmail addressEndEnter the email address you've used for the upgrade below and we will resend you the license key.ErrorError received from the server:ExpiredExpires in %sExtra DomainsExtra domains where you will be marketing the product from.FileFilterFreeFree TrialFree versionFreemius APIFreemius DebugFreemius StateFull nameFunctionHave a license key?How do you like %s so far? Test all our %s premium features with a %d-day free trial.How to upload and activate?How will you promote us?I can't pay for it anymoreI couldn't understand how to make it workI don't like to share my information with youI found a better %sI have upgraded my account but when I try to Sync the License, the plan remains %s.I no longer need the %sI only needed the %s for a short periodIDIf you have a moment, please let us know why you are %sImportant Upgrade Notice:In %sInstall Free Version NowInstall Free Version Update NowInstall NowInstall Update NowInstalling plugin: %sInvalid module ID.InvoiceIs %2$s a new website?Is ActiveIt looks like the license could not be activated.It looks like the license deactivation failed.It looks like you are not in trial mode anymore so there's nothing to cancel :)It looks like your site currently doesn't have an active license.It's not what I was looking forJoin the Beta programKeyKindly share what didn't work so we can fix it for future users...Kindly tell us the reason so we can improve.LastLast UpdatedLast licenseLatest Free Version InstalledLatest Version InstalledLearn moreLengthLicenseLicense AgreementLicense IDLicense KeyLicense keyLicense key is empty.LifetimeLoad DB OptionLocalhostLogLoggerMessageMethodMigrateMigrate LicenseMobile appsModuleModule PathModule TypeMore information about %sNameNetwork BlogNetwork UserNewNew Version AvailableNew WebsiteNewer Free Version (%s) InstalledNewer Version (%s) InstalledNewsletterNextNoNo IDNo commitment for %s - cancel anytimeNo commitment for %s days - cancel anytime!No credit card requiredNo expirationNon-expiringNone of the %s's plans supports a trial period.O.KOpt InOpt OutOpt in to make "%s" better!OtherOwner EmailOwner IDOwner NamePCI compliantPayPal account email addressPaymentsPayouts are in USD and processed monthly via PayPal.PlanPlan %s do not exist, therefore, can't start a trial.Plan %s does not support a trial period.Plan IDPlease contact us herePlease contact us with the following message:Please download %s.Please enter the license key that you received in the email right after the purchase:Please follow these steps to complete the upgradePlease let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:Please provide your full name.PluginPlugin HomepagePlugin IDPlugin InstallPlugin installer section titleChangelogPlugin installer section titleDescriptionPlugin installer section titleFAQPlugin installer section titleFeatures & PricingPlugin installer section titleInstallationPlugin installer section titleOther NotesPlugin installer section titleReviewsPluginsPlugins & Themes SyncPremiumPremium %s version was successfully activated.Premium add-on version already installed.Premium versionPremium version already active.PricingPrivacy PolicyProceedProcess IDProcessingProductsProgram SummaryPromotion methodsProvincePublic KeyPurchase LicensePurchase MoreQuick FeedbackQuotaRe-send activation emailRenew licenseRenew your license nowRequestsRequires WordPress VersionResultSDKSDK PathSave %sSavedScheduled CronsScreenshotsSearch by addressSecret KeySeems like you got the latest release.Select CountrySend License KeySet DB OptionSimulate Network UpgradeSingle Site LicenseSite IDSite successfully opted in.SitesSkip & %sSlugSnooze & %sSocial media (Facebook, Twitter, etc.)Sorry for the inconvenience and we are here to help if you give us a chance.Sorry, we could not complete the email update. Another user with the same email is already registered.StartStart DebugStart TrialStart my free %sStateStop DebugSubmitSubmit & %sSubscriptionSupportSupport ForumSync Data From ServerTax / VAT IDTerms of ServiceThank you so much for using %s and its add-ons!Thank you so much for using %s!Thank you so much for using our products!Thank you!Thanks %s!Thanks for confirming the ownership change. An email was just sent to %s for final approval.Thanks!The %s broke my siteThe %s didn't workThe %s didn't work as expectedThe %s is great, but I need specific feature that you don't supportThe %s is not workingThe %s suddenly stopped workingThe upgrade of %s was successfully completed.ThemeTheme SwitchThemesThere is a new version of %s available.This plugin has not been marked as compatible with your version of WordPress.This plugin has not been tested with your current version of WordPress.TimestampTitleTotalTownTrialTypeUnlimited LicensesUnlimited UpdatesUnlimited commissions.Up to %s SitesUpdateUpdate LicenseUpdates, announcements, marketing, no spamUpgradeUpload and activate the downloaded versionUsed to express elation, enthusiasm, or triumph (especially in electronic communication).W00tUser DashboardUser IDUser keyUsersValueVerifiedVerify EmailVersion %s was released.View detailsView paid featuresWarningWe can't see any active licenses associated with that email address, are you sure it's the right address?We couldn't find your email address in the system, are you sure it's the right address?We made a few tweaks to the %s, %sWebsite, email, and social media statistics (optional)What did you expect?What feature?What is your %s?What price would you feel comfortable paying?What you've been looking for?What's the %s's name?Where are you going to promote the %s?WordPress.org Plugin PageWould you like to proceed with the update?YesYes - %sYou already utilized a trial before.You are 1-click away from starting your %1$s-day free trial of the %2$s plan.You are all good!You are already running the %s in a trial mode.You are just one step away - %sYou do not have a valid license to access the premium version.You have a %s license.You have purchased a %s license.You have successfully updated your %s.You might have missed it, but you don't have to share any data and can just %s the opt-in.You've already opted-in to our usage-tracking, which helps us keep improving the %s.You've already opted-in to our usage-tracking, which helps us keep improving them.Your %s free trial was successfully cancelled.Your account was successfully activated with the %s plan.Your email has been successfully verified - you are AWESOME!Your free trial has expired. You can still continue using all our free features.Your license has been cancelled. If you think it's a mistake, please contact support.Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support.Your license has expired. You can still continue using the free %s forever.Your license was successfully activated.Your license was successfully deactivated, you are back to the %s plan.Your name was successfully updated.Your plan was successfully activated.Your plan was successfully changed to %s.Your plan was successfully upgraded.Your trial has been successfully started.ZIP / Postal Codea positive responseRight onactivate a license hereactive add-onActiveaddonX cannot run without pluginY%s cannot run without %s.addonX cannot run...%s cannot run without the plugin.advance notice of something that will need attention.Heads upallowas 5 licenses left%s leftas activating pluginActivatingas annual periodyearas application program interfaceAPIas close a windowDismissas code debuggingDebuggingas congratulationsCongratsas connection blockedBlockedas connection was successfulConnectedas download latest versionDownload Latestas download latest versionDownload Latest Free Versionas every monthMonthlyas expiration dateExpirationas file/folder pathPathas in the process of sending an emailSending emailas monthly periodmoas once a yearAnnualas once a yearAnnuallyas once a yearOnceas product pricing planPlanas secret encryption key missingNo Secretas software development kit versionsSDK Versionsas software licenseLicenseas synchronizeSyncas synchronize licenseSync Licenseas the plugin authorAuthoras turned offOffas turned onOnbased on %scall to actionStart free trialclose a windowDismissclose windowDismissdaysdeactivatingdelegatedo %sNOT%s send me security & feature updates, educational content and offers.e.g. Professional Plan%s Plane.g. billed monthlyBilled %se.g. the best productBestexclamationHeyexclamationOopsgreetingHey %s,hourhoursinstalled add-onInstalledinterjection expressing joy or exuberanceYee-hawlicenselike websitesSitesmillisecondsmsnew Beta versionnew versionnot verifiednounPricenounPricingoptionalproduct versionVersionproductssecondssecsend me security & feature updates, educational content and offers.skipsomething somebody says when they are thinking about what you have just said.Hmmstart the trialsubscriptionswitchingthe latest %s version heretrialtrial periodTrialverbDeleteverbDowngradeverbEditverbHideverbOpt InverbOpt OutverbPurchaseverbShowverbSkipverbUpdateverbUpgradex-ago%s agoProject-Id-Version: WordPress SDK Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues PO-Revision-Date: 2015-10-01 21:38+0000 Last-Translator: Joachim Jensen, 2019-2020,2022-2023 Language-Team: Danish (Denmark) (http://app.transifex.com/freemius/wordpress-sdk/language/da_DK/) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Language: da_DK Plural-Forms: nplurals=2; plural=(n != 1); X-Poedit-Basepath: .. X-Poedit-KeywordsList: get_text_inline;get_text_x_inline:1,2c;$this->get_text_inline;$this->get_text_x_inline:1,2c;$this->_fs->get_text_inline;$this->_fs->get_text_x_inline:1,2c;$this->fs->get_text_inline;$this->fs->get_text_x_inline:1,2c;$fs->get_text_inline;$fs->get_text_x_inline:1,2c;$this->_parent->get_text_inline;$this->_parent->get_text_x_inline:1,2c;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c X-Poedit-SearchPath-0: . X-Poedit-SearchPathExcluded-0: *.js X-Poedit-SourceCharset: UTF-8 Færdiggør aktivering af "%s" nuBetalingen for tilføjelsen %s blev gennemført.%s installeringer1%s licenser%s siden%s og tilføjelser%s er den nye ejer af kontoen.%s eller højere%s vurdering%s vurderinger1%s sek%s stjerne%s stjerner%s gang%s gange%s's betalte featuresAPI←➤KontoKontodetaljerHandlingerAktiverAktiver %sAktiver %s planAktiver funktioner i %sAktiver gratis versionAktiver licensAkiver licens på alle afventende websteder.Aktiver licens på alle websteder i netværket.Aktiver denne tilføjelseAktiveretTilføjelser til %sTilføjelser til modul %sTilføj andet domæneTilføjelseTilføjelserAdresseAdresselinje %dAffiliateAffiliationEfter dine gratis %s er prisen kun %sAccepter & aktiver licensAlle forespørgslerAlle typerTillad & FortsætBeløbDer skete en ukendt fejl.Anonym feedbackAnvend på alle afventende websteder.Anvend på alle websteder i netværket.Ansøg om at blive en affiliateEr du sikker på, du vil slette al Freemius data?Er du sikker på, du vil fortsætte?Auto-installation fungerer kun for tilmeldte brugere.Auto-fornyer om %sAutomatisk installeringGennemsnitlig vurderingSejtBliv en affiliateBetaBetalingFaktureringBlokererBlog-IDFirmanavnKøb en licens nuKøb licensKan du ikke finde din licensnøgle?AnnullerAnnuller %s og fortsætAnnuller %s?Annuller installeringAnnuller abonnementAnnuller prøveperiodeAnnulleretAnnullerer %sAnnullerer %s...Annullerer abonnementetSkift licensSkift ejerskabSkift planSkift brugerUdtjekningByRyd API-cacheKlik herKlik her for at benytte pluginnet anonymtKlik for at vise skærmbillede %d i fuld skærmProdukterKodeKompatibel op tilKontaktKontakt supportKontakt osBidragsydereKunne ikke aktivere %s.LandCron TypeDatoDeaktiverDeaktiver licensDeaktiveringFejlfindingslogUddeleger til webstedsadministratorerSlet alle kontiDetaljerHar du ikke en licensnøgle?Donér til dette pluginNedgraderer din planDownloadDownload 1%s versionHent betalt versionDownload den seneste version af %sDownload den seneste versionDownloadetKopier webstedE-mailE-mailadresseSlutIndtast e-mailadressen, som du benyttede ved opgraderingen, nedenfor og vi vil gensende licensnøglen til dig.FejlFejl modtager fra serveren:UdløbetUdløber om %sEkstra domænerAndre domæner du vil markedsføre produktet fra.FilFilterGratisGratis prøveperiodeGratis versionFreemius APIFreemius DebugFreemius tilstandFulde navnFunktionHar du en licensnøgle?Hvad synes du om %s indtil videre? Test alle vores premium funktioner i %s med en %d-dags gratis prøveperiode.Upload og aktivering, hvordan?Hvordan vil du promovere os?Jeg kan ikke længere betale for detJeg forstod ikke, hvordan jeg skulle få det til at fungere.Jeg har ikke lyst til at dele mine informationer med jerJeg fandt et bedre %sJeg har opgraderet min konto, men når jeg forsøger at synkronisere licensen, forbliver planen %s.Jeg har ikke længere brug for %sJeg behøvede kun %s i en kort periodeIDHvis du har tid, så lad os venligst vide hvorfor du %sVigtig meddelelse til opgradering:Om %sInstaller gratis version nuInstaller opdatering til gratis version nuInstaller nuInstaller opdatering nuInstallerer plugin: %sUgyldigt modul-ID.FakturaEr %2$s et nyt websted?Er aktivDet ser ud til, at licensen ikke kunne aktiveres.Det ser ud til, at licens-deaktiveringen mislykkedes.Det lader ikke til du er i en prøveperiode længere, så der er ikke noget at annullere :-)Det ser ud til, at dit websted endnu ikke har en aktiv licens.Det er ikke, hvad jeg søgteDeltag i beta-programmetNøgleVær venlig at dele hvad der ikke virkede så vi kan rette det for kommende brugere....Fortæl os venligst årsagen, så vi kan forbedre det.SidsteSenest opdateretSeneste licenseSeneste gratis version installeretSeneste version installeretLæs mereLængdeLicensLicensaftaleLicens-IDLicensnøgleLicensnøgleLicensnøglen er tom.LivstidHent DB-indstillingLocalhostLogLoggerBeskedMetodeFlytFlyt licensMobil-appsModulModul-stiModultypeMere information om %sNavnNetværksblogNetværksbrugerNyNy version tilgængeligNyt webstedNyere gratis version (%s) installeretNyere version (%s) installeretNyhedsbrevNæsteNejIntet IDIngen bindinger i %s - annuller når som helstIngen bindinger i %s dage - annuller når som helst!Betalingskort ikke påkrævetUdløber ikkeUdløber ikkeIngen af %s's planer understøtter prøveperiode.O.KTilmeldFrameldAccepter for at gøre "%s" bedre!AndetE-mailadresse for ejerEjer-IDEjer-navnPCI-kompatibelE-mailadresse til PayPal-kontoBetalingerUdbetalinger er i USD og behandles hver måned via PayPal.PlanPlan %s eksisterer ikke og kan derfor ikke starte prøveperiode.Plan %s understøtter ikke en prøveperiode.Plan-IDKontakt os herKontakt os venligst med følgende besked:Download venligst %s.Indtast licensnøglen, du modtog i e-mailen lige efter købet:Følg venligst disse trin for at færdiggøre opgraderingenLad os vide, om vi har lov til at kontakte dig med sikkerheds- og feature-opdateringer, informativt indhold og lejlighedsvise tilbud:Indtast venligst dit fulde navn.PluginPlugin-webstedPlugin-IDPlugin-installeringÆndringslogBeskrivelseFAQFunktioner og priserInstalleringAndre noterAnmeldelserPluginsSynkronisering af plugins og temaerPremiumPremium-versionen af %s blev aktiveret.Premium tilføjelse er allerede installeret.Premium versionPremium version allerede aktiv.PriserPrivatlivspolitikFortsætProces-IDArbejderProdukterProgramoversigtPromoveringsmetoderProvinsOffentlig nøgleKøb licensKøb flereHurtig feedbackKvoteGensend e-mail om aktiveringForny licensForny din licens nuForespørgslerKræver WordPress-versionResultatSDKSDK-stiSpar %sGemtPlanlagte cron jobsSkærmbillederSøg efter adressePrivat nøgleDet ser ud til, at du har den seneste udgivelse.Vælg landSend licensnøgleSæt DB-indstillingSimuler netværksopgraderingEnkelt site licensWebsteds-IDWebsted er tilmeldt.WebstederSpring over & %sKortnavnUdsæt & %sSociale medier (Facebook, Twitter osv.)Vi beklager ulejligheden, og vi er her for at hjælpe, hvis du giver os chancen.Beklager, vi kunne ikke opdatere e-mailen. Der er allerede registreret en anden bruger med samme e-mail.StartStart fejlfindingStart prøveperiodeStart mine gratis %sStatStop fejlfindingSendSend & %sAbonnementSupportSupportforumSynkroniser data fra serverMoms / VAT IDServicevilkårMange tak for, at du benytter %s og tilhørende add-ons!Tak fordi du benytter %s!Mange tak for at benytte vores produkter!Mange tak!Tak %s!Tak fordi du bekræftede skift af ejerskab. En e-mail er blevet sendt til %s for sidste godkendelse.Tak!%s ødelagde min webside%s virkede ikke%s virkede ikke som forventet%s er godt, men jeg har brug for en specifik feature, som ikke understøttes%s virker ikke%s stoppede pludseligt med at virkeOpgraderingen af %s blev fuldendt.TemaTemaskiftTemaerEn ny version af %s er tilgængelig.Dette plugin er ikke markeret som kompatibel med din nuværende version af WordPress.Dette plugin er ikke blevet testet med din nuværende version af WordPress.TidsstempelTitelTotalByPrøveperiodeTypeUbegrænsede licenserUbegrænsede opdateringerUbegrænset provision.Op til %s webstederOpdaterOpdater licensOpdateringer, annonceringer, marketing, ingen spamOpgraderUpload og aktiver den downloadede versionW00tBrugerpanelBruger-IDBrugernøgleBrugereVærdiVerificeretVerificer e-mailVersion %s er blevet udgivet.Vis detaljerVis betalte featuresAdvarselVi kan ikke finde nogen aktive licenser knyttet til den e-mailadresse, er du sikker på, det er den rigtige adresse?Vi kunne ikke finde din e-mailadresse i systemet, er du sikker på, det er den rigtige adresse?Vi har foretaget nogle rettelser til %s, %sWebsted, e-mail, og statistikker for sociale medier (valgfrit)Hvad forventede du?Hvilken feature?Angiv venligst %s?Hvilken pris ville du foretrække at betale?Hvad ledte du efter?Hvad er navnet på %s?Hvor vil du promovere %s?WordPress.org Plugin-sideVil du fortsætte med opdateringen?JaJa - %sDu har allerede brugt din prøveperiode.Du er 1 klik fra at begynde din %1$s dages gratis prøveperiode af planen %2$s.Det var det!Du benytter allerede %s under en prøveperiode.Du mangler kun ét skridt - %sDu har ikke en gyldig licens til at benytte premium-versionen.Du har en %s licens.Du har købt en licens til %s.Opdatering af %s blev gennemført.Du har måske overset det, men du behøver ikke at dele data og kan blot %s tilmeldingen.Du er allerede tilmeldt vores brugssporing, hvilket hjælper os med at forbedre %s.Du er allerede tilmeldt vores brugssporing, hvilket hjælper os med at forbedre dem.Din gratis prøveperiode for %s er blevet annulleret.Din konto blev aktiveret med planen %s.Din e-mailadresse er blevet verificeret - du er FOR SEJ!Din gratis prøveperiode er udløbet. Du kan stadig benytte alle de gratis features.Din licens er blevet annulleret. Hvis du mener, dette er en fejl, så kontakt venligst support.Din licens er udløbet. %1$sOpgrader nu%2$s for at fortsætte med at benytte %3$s uden forstyrrelser.Din licens er udløbet. Du kan stadig benytte alle funktionerne i %s, men du bliver nødt til at fornye din licens for at få opdateringer og support.Din licens er udløbet. Du kan stadig fortsætte med at benytte den gratis udgave af %s.Din licens er blevet aktiveret.Din licens blev deaktiveret, du er tilbage på planen %s.Dit navn er blevet opdateret.Din plan er blevet aktiveret.Din plan er blevet ændret til %s.Din plan er blevet opgraderet.Din prøveperiode er begyndt.ZIP / PostnummerSådanaktiver en licens herAktiv%s virker ikke uden %s.%s virker ikke uden pluginnet.Se hertillad%s tilbageAktivererårAPIFjernFejlfindingTillykkeBlokeretForbundetDownload senesteDownload seneste gratis versionMånedligtUdløberStiSender e-mailmdÅrligtÅrligtEngangsbeløbPlanIngen privat nøgleSDK-versionerLicensSynkroniserSynkroniser licensForfatterFraTilbaseret på %sStart gratis prøveperiodeFjernFjerndagedeaktivererdelegérsend %sIKKE%s sikkerheds- og feature-opdateringer, informativt indhold og tilbud.%s PlanFaktureret %sBedsteHeyUpsHey %s,timetimerInstalleretYee-hawlicensWebstedermsny beta-versionny versionikke verificeretPrisPriservalgfritVersionprodukterseksend mig sikkerheds- og feature-opdateringer, informativt indhold og tilbud.spring overHmmstart prøveperiodenabonnementskifterden seneste version af %s herprøveperiodePrøveperiodeSletNedgraderRedigerSkjulTilmeldFrameldKøbVisSpring overOpdaterOpgrader%s sidenPK!sX.libraries/freemius/languages/freemius-nl_NL.monu[T A S %#! I! U!a!h!6{!!_g"#"" # # #'#.#6#?#G#@P#H##O#=$A$`$$$$ $$$$$&$-!%O% d%n%}%%%%5%%% & &'&@& Y& f&p&o&&&''"''2(!9(a[(0(())&).)B)J)S)[) `)n) ))))) W*b*v* * * ***Y*6+E+ V+b+k+p++(+1+%+:,U,Z,k,s, , ,,, ,, ,,x,_- - . .#.7.t?.....//6/ R/]/[/fQ00 00Y0a*1111 1 1;12223 "3 -3 :3G3jV33 33334~+4U45555)P5-z55S56'(6P6MS676g6pA777y7L8e8 8888 88 819.29Oa99A1:ys::a ;o;Bs;,;; ; ;< < 9<D<K<S< e< q<}<<4<< <<<<<= =+= 2= >=J=d= i= v===!== ====%=+>G> _> m>/z>>m>~????? ?? ? ?)?$@A@4J@@5@(@@@-A0AUDAA1cB~BC[1DDDD DD(D*E"0E1SE+E*E&ENFRFZFpF.xF)FFFG GG G+G4GDGVG _GjG{GGGWG HH&H/HJHQHUH^HfH vHH H5HsHlII&III I J$J=JQJYJuJ {JJ&JLJfJeK kKwKK K KK KK KKKL/MNM)nM M M\M N N3NCRNNNNdRO-OO OOO'PMFPGP PPPPPPEQHQ[QmQQQQ*QQ*Q^RfRnRtRdzRR RR SS.Si6SWS"SBT6^TT TT-TTU&+URUlUpU$yUMUU/U.VoNV>VV&WZ;WTWRW.>X.mX9XZX31Y<eYbYPZUVZ_Z [K[([G\#d\)\$\U\)-]W]i]];]6]>^M^S^n^^$^^^^_&9_*`_7____3`E`Z`p```*`1`a2a#Fajaaa aaaa b bNbebbbbbb1b!c)c=c Mc Yc fc qc~c cCccQc=d Md Zddddd dd d d d d d d d e e e(eFiBj$\jj j jj6jkqk)Ull lllll ll lGlM>mmJmmmmnnn 'n3nDnZnqn6n/nn n oo7o Oo [o:ioo o o o"oo p p%pn:pppTq,lq%q.q7q#&r{Jr?rss6sMsVs js vsss ss s&ssst tttt u u u8uuQuuu u uvvv,9v5fv?v vvvvww $w0wGw LwVw \wgw{ww x xxxxyyyyyy$y!z ;zFza{^f{{ {{j{~I||$||| }7}S}[}!b}~~ ~ ~~{~EUd>lfKЀ'3?DY1CZFjh |u z" Մ'(P X;b>`݅>Aۆ ]ׇ5=l-5G%X#~ щ B X fpt{  Ȋӊ */#Z ~/7ϋ/4?ty ƍ ҍ 3 ;EFB&Ԏ#8'`YvЏ8_#*2 CNb r “Nʓ!"D+L.xӔ ۔  /9JYoum -7 Q[_ gr 8}3 =Ldvؘ ޘ&Sxl  ' 2 @M mxBH*78b "5X jZ\(/ 5B J+kPA *6<C JWX\ʠݠ  -I(Qz  } *;Ud ~d^6OF8ͣ *'8`zͤФ(ؤbd!s(5V#WȦ_ a-14B\w,Ԩ9k;`j`sԪRn%E -'N!vV%%. G hu~  ­ έح #'8 < FPY ^ jv  ˮ ծ ߮ UKSdjns{ ȯL̯ #' = HV v °ǰ̰ հ߰ %s to access version %s security & feature updates, and support.%s - plugin name. As complete "PluginX" activation nowComplete "%s" Activation Now%s Add-on was successfully purchased.%s Installs%s Licenses%s ago%s and its add-ons%s commission when a customer purchases a new license.%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license.%s is a premium only add-on. You have to purchase a license first before activating the plugin.%s is the new owner of the account.%s minimum payout amount.%s or higher%s rating%s ratings%s sec%s star%s stars%s time%s times%s to access version %s security & feature updates, and support.%s tracking cookie after the first visit to maximize earnings potential.%s's paid features%sClick here%s to choose the sites where you'd like to activate the license on.APIASCII arrow left icon←ASCII arrow right icon➤Account DetailsActionsActivateActivate %sActivate %s PlanActivate %s featuresActivate Free VersionActivate LicenseActivate license on all pending sites.Activate license on all sites in the network.Activate this add-onActivatedAdd Ons for %sAdd Ons of module %sAdd another domainAdd-OnAdd-OnsAdd-on must be deployed to WordPress.org or Freemius.AddressAddress Line %dAffiliateAffiliationAfter your free %s, pay as little as %sAgree & Activate LicenseAll RequestsAll TypesAllow & ContinueAlternatively, you can skip it for now and activate the license later, in your %s's network-level Account page.AmountAn automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now.Anonymous feedbackApply on all pending sites.Apply on all sites in the network.Apply to become an affiliateAre you sure you want to delete all Freemius data?Are you sure you want to proceed?As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days.Auto installation only works for opted-in users.Auto renews in %sAutomatic InstallationAverage RatingAwesomeBecome an affiliateBillingBlockingBlog IDBodyBusiness nameBuy a license nowBuy licenseCan't find your license key?CancelCancel %s & ProceedCancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site.Cancel %s?Cancel InstallationCancel SubscriptionCancel TrialCancelledCancelling %sCancelling %s...Cancelling the subscriptionCancelling the trial will immediately block access to all premium features. Are you sure?Change LicenseChange OwnershipChange PlanCheckoutCityClear API CacheClear Updates TransientsClick here to use the plugin anonymouslyClick to see reviews that provided a rating of %sClick to view full-size screenshot %dClone resolution admin notice products list labelProductsCodeCompatible up toContactContact SupportContact UsContributorsCouldn't activate %s.CountryCron TypeDateDeactivateDeactivate LicenseDeactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site.Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?DeactivationDebug LogDelegate to Site AdminsDelete All AccountsDetailsDon't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support.Don't have a license key?Donate to this pluginDowngrading your planDownloadDownload %s VersionDownload the latest %s versionDownload the latest versionDownloadedDue to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.During the update process we detected %d site(s) that are still pending license activation.During the update process we detected %s site(s) in the network that are still pending your attention.EmailEmail addressEndEnter the domain of your website or other websites from where you plan to promote the %s.Enter the email address you've used for the upgrade below and we will resend you the license key.ErrorError received from the server:ExpiredExpires in %sExtra DomainsExtra domains where you will be marketing the product from.FileFilterFor compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.FreeFree TrialFree versionFreemius APIFreemius DebugFreemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error.Freemius StateFull nameFunctionGet commission for automated subscription renewals.Have a license key?Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!How do you like %s so far? Test all our %s premium features with a %d-day free trial.How to upload and activate?How will you promote us?I can't pay for it anymoreI couldn't understand how to make it workI don't like to share my information with youI found a better %sI have upgraded my account but when I try to Sync the License, the plan remains %s.I no longer need the %sI only needed the %s for a short periodIDIf you click it, this decision will be delegated to the sites administrators.If you have a moment, please let us know why you are %sIf you would like to give up the ownership of the %s's account to %s click the Change Ownership button.If you'd like to use the %s on those sites, please enter your license key below and click the activation button.Important Upgrade Notice:In %sIn case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?Install Free Version NowInstall Free Version Update NowInstall NowInstall Update NowInstalling plugin: %sInvalid module ID.Invalid site details collection.InvoiceIs ActiveIt looks like the license could not be activated.It looks like the license deactivation failed.It looks like you are not in trial mode anymore so there's nothing to cancel :)It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry.It looks like your site currently doesn't have an active license.It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.It's not what I was looking forJust letting you know that the add-ons information of %s is being pulled from an external server.KeyKindly share what didn't work so we can fix it for future users...Kindly tell us the reason so we can improve.LastLast UpdatedLast licenseLatest Free Version InstalledLatest Version InstalledLearn moreLengthLicenseLicense AgreementLicense KeyLicense keyLicense key is empty.LifetimeLike the %s? Become our ambassador and earn cash ;-)Load DB OptionLocalhostLogLoggerMessageMethodMigrate Options to NetworkMobile appsModuleModule PathModule TypeMore information about %sNameNetwork BlogNetwork UserNewNew Version AvailableNewer Free Version (%s) InstalledNewer Version (%s) InstalledNewsletterNextNoNo IDNo commitment for %s - cancel anytimeNo commitment for %s days - cancel anytime!No credit card requiredNo expirationNon-expiringNone of the %s's plans supports a trial period.O.KOnce your license expires you can still use the Free version but you will NOT have access to the %s features.Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license.Opt InOpt OutOpt in to make "%s" better!OtherOwner EmailOwner IDOwner NamePCI compliantPaid add-on must be deployed to Freemius.PayPal account email addressPaymentsPayouts are in USD and processed monthly via PayPal.PlanPlan %s do not exist, therefore, can't start a trial.Plan %s does not support a trial period.Plan IDPlease contact us herePlease contact us with the following message:Please download %s.Please enter the license key that you received in the email right after the purchase:Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential).Please follow these steps to complete the upgradePlease let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.Please provide details on how you intend to promote %s (please be as specific as possible).Please provide your full name.PluginPlugin HomepagePlugin IDPlugin InstallPlugin installer section titleChangelogPlugin installer section titleDescriptionPlugin installer section titleFAQPlugin installer section titleFeatures & PricingPlugin installer section titleInstallationPlugin installer section titleOther NotesPlugin installer section titleReviewsPlugin is a "Serviceware" which means it does not have a premium code version.PluginsPlugins & Themes SyncPremiumPremium %s version was successfully activated.Premium add-on version already installed.Premium versionPremium version already active.PricingPrivacy PolicyProceedProcess IDProductsProgram SummaryPromotion methodsProvincePublic KeyPurchase LicenseQuick FeedbackQuotaRe-send activation emailRefer new customers to our %s and earn %s commission on each successful sale you refer!Renew licenseRenew your license nowRequestsRequires WordPress VersionResultSDKSDK PathSave %sScheduled CronsScreenshotsSearch by addressSecret KeySecure HTTPS %s page, running from an external domainSeems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes.Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.Seems like you got the latest release.Select CountrySend License KeySet DB OptionSimulate Network UpgradeSimulate Trial PromotionSingle Site LicenseSite IDSite successfully opted in.SitesSkip & %sSlugSocial media (Facebook, Twitter, etc.)Sorry for the inconvenience and we are here to help if you give us a chance.Sorry, we could not complete the email update. Another user with the same email is already registered.StartStart TrialStart my free %sStateSubmit & %sSubscriptionSupportSupport ForumSync Data From ServerTax / VAT IDTerms of ServiceThank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days.Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information.Thank you so much for using %s and its add-ons!Thank you so much for using %s!Thank you so much for using our products!Thank you!Thanks %s!Thanks for confirming the ownership change. An email was just sent to %s for final approval.The %s broke my siteThe %s didn't workThe %s didn't work as expectedThe %s is great, but I need specific feature that you don't supportThe %s is not workingThe %s suddenly stopped workingThe installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page.The remote plugin package does not contain a folder with the desired slug and renaming did not work.The upgrade of %s was successfully completed.ThemeTheme SwitchThemesThere is a %s of %s available.There is a new version of %s available.This plugin has not been marked as compatible with your version of WordPress.This plugin has not been tested with your current version of WordPress.TimestampTitleTotalTownTrialTypeUnable to connect to the filesystem. Please confirm your credentials.Unlimited LicensesUnlimited UpdatesUnlimited commissions.Up to %s SitesUpdateUpdate LicenseUpdates, announcements, marketing, no spamUpgradeUpload and activate the downloaded versionUsed to express elation, enthusiasm, or triumph (especially in electronic communication).W00tUser IDUsersValueVerification mail was just sent to %s. If you can't find it after 5 min, please check your spam box.VerifiedVerify EmailVersion %s was released.View detailsView paid featuresWarningWe can't see any active licenses associated with that email address, are you sure it's the right address?We couldn't find your email address in the system, are you sure it's the right address?We made a few tweaks to the %s, %sWe're excited to introduce the Freemius network-level integration.Website, email, and social media statistics (optional)What did you expect?What feature?What is your %s?What price would you feel comfortable paying?What you've been looking for?What's the %s's name?Where are you going to promote the %s?WordPress.org Plugin PageYesYes - %sYou already utilized a trial before.You are 1-click away from starting your %1$s-day free trial of the %2$s plan.You are all good!You are already running the %s in a trial mode.You are just one step away - %sYou can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.You do not have a valid license to access the premium version.You have a %s license.You have successfully updated your %s.You might have missed it, but you don't have to share any data and can just %s the opt-in.You've already opted-in to our usage-tracking, which helps us keep improving the %s.You've already opted-in to our usage-tracking, which helps us keep improving them.Your %s Add-on plan was successfully upgraded.Your %s free trial was successfully cancelled.Your account was successfully activated with the %s plan.Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s.Your affiliation account was temporarily suspended.Your email has been successfully verified - you are AWESOME!Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your free trial has expired. You can still continue using all our free features.Your license has been cancelled. If you think it's a mistake, please contact support.Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support.Your license has expired. You can still continue using the free %s forever.Your license was successfully activated.Your license was successfully deactivated, you are back to the %s plan.Your name was successfully updated.Your plan was successfully changed to %s.Your plan was successfully upgraded.Your subscription was successfully cancelled. Your %s plan license will expire in %s.Your trial has been successfully started.ZIP / Postal Codea positive responseRight onactive add-onActiveaddonX cannot run without pluginY%s cannot run without %s.addonX cannot run...%s cannot run without the plugin.advance notice of something that will need attention.Heads upallowas 5 licenses left%s leftas activating pluginActivatingas annual periodyearas application program interfaceAPIas close a windowDismissas code debuggingDebuggingas congratulationsCongratsas connection blockedBlockedas connection was successfulConnectedas download latest versionDownload Latestas download latest versionDownload Latest Free Versionas every monthMonthlyas expiration dateExpirationas file/folder pathPathas in the process of sending an emailSending emailas monthly periodmoas once a yearAnnualas once a yearAnnuallyas once a yearOnceas product pricing planPlanas secret encryption key missingNo Secretas software development kit versionsSDK Versionsas software licenseLicenseas synchronizeSyncas synchronize licenseSync Licenseas the plugin authorAuthoras turned offOffas turned onOnbased on %scall to actionStart free trialclose a windowDismissclose windowDismissdeactivatingdelegatedo %sNOT%s send me security & feature updates, educational content and offers.e.g. Professional Plan%s Plane.g. billed monthlyBilled %se.g. the best productBestexclamationHeyexclamationOopsgreetingHey %s,interjection expressing joy or exuberanceYee-hawlicenselike websitesSitesmillisecondsmsnew versionnot verifiednounPricenounPricingproduct versionVersionsecondssecsend me security & feature updates, educational content and offers.skipsomething somebody says when they are thinking about what you have just said.Hmmstart the trialsubscriptionswitchingthe latest %s version heretrialtrial periodTrialverbDeleteverbDowngradeverbEditverbHideverbOpt InverbOpt OutverbPurchaseverbShowverbSkipverbUpdateverbUpgradex-ago%s agoProject-Id-Version: WordPress SDK Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues PO-Revision-Date: 2015-10-01 21:38+0000 Last-Translator: Leo Fajardo , 2022 Language-Team: Dutch (Netherlands) (http://app.transifex.com/freemius/wordpress-sdk/language/nl_NL/) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Language: nl_NL Plural-Forms: nplurals=2; plural=(n != 1); X-Poedit-Basepath: .. X-Poedit-KeywordsList: get_text_inline;get_text_x_inline:1,2c;$this->get_text_inline;$this->get_text_x_inline:1,2c;$this->_fs->get_text_inline;$this->_fs->get_text_x_inline:1,2c;$this->fs->get_text_inline;$this->fs->get_text_x_inline:1,2c;$fs->get_text_inline;$fs->get_text_x_inline:1,2c;$this->_parent->get_text_inline;$this->_parent->get_text_x_inline:1,2c;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c X-Poedit-SearchPath-0: . X-Poedit-SearchPathExcluded-0: *.js X-Poedit-SourceCharset: UTF-8 %svoor toegang to versie %s beveiliging & features updates en support.Voltooi "%s" Activatie Nu%s Add-on werd succesvol aangekocht.%s Installaties%s Licenties%s geleden%sen bijbehorende uitbreidingen%s commissie als een klant een nieuwe licentie koopt. %s gratis proefperiode werd succesvol stop gezet. Daar de add-on alleen als premium versie beschikbaar is werd deze automatisch gedeactiveerd. Als u de add-on in de toekomst wilt gebruiken dient u een licentie aan te schaffen.%s is uitsluitend beschikbaar als een premium add-on. Je moet een licentie kopen voordat je de plug-in activeert.%s is de nieuwe eigenaar van het account.%s minimum uitbetalingsbedrag.%s of hoger%s beoordeling%s beoordelingen%s sec%s ster%s sterren%s tijd%s tijden%svoor toegang tot versie %s beveiliging en feature updates en support.%s tracking cookie na eerste bezoek om je verdienpotentieel te maximaliseren.%s betaalde mogelijkheden%sKlik hier%s om de sites te kiezen waar op je de licentie wilt activeren.API←➤AccountgegevensActiesActiveerActiveer %sActiveer %s PlanActiveer %s features.Activeer Gratis VersieActiveer LicentieActiveer licentie op alle in behandeling zijnde sites.Activeer licentie op alle sites in het netwerk.Activeer deze add-onGeactiveerdAdd-ons voor %sUitbreidingen van module %sVoeg nog een domein toeUitbreidingUitbreidingenAdd-on moet op WordPress.org of Freemius geplaatst worden.AdresAdresregel %dAffiliateAffiliatieNa uw gratis %s, betaal slechts %sAkkoord & Activeer LicentieAlle RequestsAlle TypesToestaan & Ga VerderJe kunt dat eventueel ook nu overslaan en de licentie later in je %s netwerk-niveau Account pagina activeren. BedragEen geautomatiseerde download en installatie van %s (betaalde versie) van %s zal starten binnen %s. Als je dit handmatig wil doen, klik dan nu op de annuleer knop.Anonieme terugkoppelingPas toe op alle in behandeling zijnde sites.Pas toe op alle sites in het netwerk.Meld je aan om een affiliate partner te wordenWeet u zeker dat u alle Freemius data wilt verwijderen?Weet je zeker dat je wilt doorgaan?Omdat wij 30 dagen reserveren voor eventuele terugstortingen, betalen we alleen commissies uit die ouder dan 30 dagen zijn.Automatische installatie werkt alleen voor opted-in gebruikers.Auto hernieuwd over %sAutomatische InstallatieGemiddelde BeoordelingGeweldigWordt een affiliateFactureringGeblokkeerdBlog IDBodyBedrijfsnaamKoop nu een licentieKoop licentieKan je je licentiesleutel niet vinden?AnnuleerAnnuleer %s & Ga DoorAnnuleer %s - Ik heb niet meer enige beveiligings- en uitbreidingsupdates of ondersteuning voor %s nodig, omdat ik niet van plan ben de %sop deze of enige andere site te gebruiken.%s annuleren?Annuleer InstallatieAbonnement OpzeggenProefperiode OpzeggenGeannuleerdAnnuleren %s%s wordt geannuleerd...Het abonnement annulerenHet stopzetten van de proefperiode zal de toegang tot de premium features onmiddellijk blokkeren. Weet je dat zeker?Verander LicentieEigendom OverdragenWijzig PlanAfrekenenStadAPI-Cache LeegmakenUpdates Transients OpschonenKlik hier om de plug-in anoniem te gebruikenKlik om reviews te bekijken met een beoordeling van%sKlik voor het op volle-grootte bekijken van schermafbeelding %dProductenCodeCompatible totContactContacteer SupportContacteer OnsMedewerkersKon %s niet activeren.LandCron TypeDatumDeactiveerDeactiveer LicentieHet deactiveren en deïnstalleren van de %s zal de licentie automatisch uitschakelen, die je dan kan gebruiken op een andere site.Deactiveren van je licentie zal alle premium features blokkeren, maar geeft je de mogelijkheid de licentie op een andere site te activeren. Weet je zeker dat je wilt doorgaan?DeactivatieDebug LogDelegeren aan Site BeheerdersVerwijder All AccountsDetailsAnnuleer %s niet - Ik wil nog steeds zowel beveiligings- en uitbreidingsupdates ontvangen als contact kunnen opnemen met Support.Heb je geen licentiesleutel?Doneer aan deze plug-inJe plan naar beneden bijstellenDownloadDownload %s VersieDownload de meeste recente %s versieDownload de meeste recente versieGedownloadAls gevolg van het overtreden van onze affiliate voorwaarden, hebben we besloten je affiliate account tijdelijk te blokkeren. Neem voor eventuele vragen alsjeblieft contact op met support.Tijdens het update proces detecteerden we %dsite(s) waarvoor de licentie nog niet geactiveerd is.Tijdens het update proces detecteerden we %dsite(s) in het netwerk die jouw aandacht vereisen.E-mailE-mailadresEindeVoer de domeinnaam in van je website of andere websites waar vanaf je van plan bent de %ste gaan promoten.Voer hieronder het e-mailadres in dat je gebruikt hebt voor de upgrade en we zullen je jouw licentiesleutel opnieuw toesturen.FoutFoutmelding ontvangen van de server:VerlopenVerloopt over %sExtra DomeinenExtra domeinen vanaf waar je het product gaat promoten.BestandFilterVoordat we de proefperiode kunnen starten, vragen we je, in overeenstemming met de Wordpress.org-richtlijnen, in te stemmen je gebruikers- en niet-sensitieve site informatie door de %s periodiek te laten verzenden naar %s om te controleren op nieuwe versies en je proefversie te valideren.GratisGratis ProefperiodeGratis versieFreemius APIFreemius DebugFreemius SDK kon het hoofdbestand van de plug-in niet vinden. Neem a.j.b. contact op met sdk@freemius.com m.b.t. deze fout.Freemius StatusVolledige naamFunctieKrijg een commissie voor automatische abonnementsverlengingen.Heb je een licentiesleutel?Hey, wist je dat %s een samenwerkingsprogramma heeft? Als je de %s goedvindt, kun je onze ambassadeur worden en wat geld verdienen!Hoe bevalt %s tot dusver? Test al onze %s premium features gedurende een%d-daagse gratis proefperiode.Hoe te uploaden en activeren?Hoe ga je ons promoten?Ik kan er niet langer meer voor betalenIk snapte niet hoe ik het aan het werk kon krijgen.Ik vind het niet prettig om mijn informatie met jullie te delenIk vond een beter %sIk heb mijn account geüpgraded maar als ik probeer te Synchroniseren blijft het plan %s.Ik heb de %s niet meer nodig Ik had de %s alleen nodig voor een korte periode.IDAl je er op klikt, zal deze beslissing gedelegeerd worden aan de beheerders van de sites. We zouden het zeer op prijs stellen, als je even hebt, om ons alsjeblieft te laten weten waarom je gaat %sAls je het eigendom van het %s account wilt overdragen aan %s, klik dan op de Eigendom Overdragen knop. Als je de %s op deze sites wil gebruiken, voer dan alsjeblieft de licentiesleutel hieronder in en klik op de activatie-knop.Belangrijke Upgrade Mededeling:Binnen %sMocht je NIET van plan zijn om deze %s te gebruiken op deze site (of op een andere site) - wil je dan het %s ook opzeggen?Installer Gratis Versie NuInstalleer Gratis Versie Update NuInstalleer NuInstalleer Update NuInstalleren van plug-in: %sOngeldige Module-IDOngeldige verzameling van Site Details.FactuurIs ActiefHet lijkt erop dat de licentie niet geactiveerd kon worden.Het lijkt erop dat het deactiveren van je licentie mislukt is.Het lijkt er op dat u niet langer meer in de proefperiode zit, dus er valt niets stop te zetten.Het lijkt erop dat u nog steeds op het %s plan zit. Als u uw plan geüpgraded of veranderd heeft, dan is het waarschijnlijk een fout aan onze kant - sorry.Het lijkt erop dat je site momenteel geen actieve licentie heeft.Het lijkt erop dat een van de authenticatie parameters niet klopt. Update je Publieke Sleutel, Geheime Sleutel & Gebruikers ID en probeer het nogmaals. Het is niet waarna ik opzoek wasVoor alle duidelijkheid, de add-ons informatie van %s wordt opgehaald van een externe server.SleutelWil je alsjeblieft zo vriendelijk zijn om te delen wat niet werkte, zodat we dat kunnen verbeteren voor toekomstige gebruikers ...Wilt je alsjeblieft zo vriendelijk zijn om de reden te vermelden, zodat wij verbeteringen kunnen doorvoeren.LaatsteLaatst GeüpdatetLaatste licentieNieuwste Gratis Versie GeïnstalleerdMeest Recente Versie GeïnstalleerdLees meerLengteLicentieLicentieovereenkomstLicentiesleutelLicentiesleutelLicentiesleutel is leeg.LevenslangVind je de %s goed? Word dan onze ambassadeur en verdien cash ;-)Laad DB-optieLocalhostLogLoggerBerichtMethodesZet Opties over naar NetwerkMobiele appsModuleModule PadModuletypeMeer informatie over %sNaamNetwerk BlogNetwerk GebruikerNieuwNieuwe Versie BeschikbaarNieuwere Gratis Versie (%s) GeïnstalleerdNieuwere Versie (%s) GeïnstalleerdNieuwsbriefVolgendeNeeGeen IDGeen verplichting voor %s - opzeggen kan altijdGeen verplichting voor %s dagen - elk moment opzeggen!Geen creditcard nodigGeen verloopdatumNiet-verlopendeGeen van de %s plannen ondersteunt een proefperiode.OkéAls je licentie verloopt kan je nog steeds gebruik maken van de Gratis versie, maar je zal GEEN toegang meer hebben tot de %sfeatures.Als je licentie afloopt, zul je %s niet meer kunnen gebruiken, tenzij je het opnieuw activeert met een geldige Premium-licentie.Opt InOpt OutOpt-in om "%s" te verbeteren!OverigeE-mail EigenaarID EigenaarNaam EigenaarPCI-comformBetaalde add-on moet op Freemius geplaatst worden.PayPal account e-mailadresBetalingenUitbetalingen zijn in USD en worden maandelijks uitgevoerd via PayPalPlanPlan %s bestaat niet, daarom kan proefperiode niet gestart worden.Plan %s ondersteunt geen proefperiode.Plan IDNeem hier a.u.b. contact met ons opNeem a.u.b. contact met ons op met het volgende bericht:A.u.b. %s downloaden.Voer aalsjeblieft de licentiesleutel in die je ontving in de e-mail direct na de aankoop:Voel je alsjeblieft vrij om elke relevante website of social media statistieken met ons te delen, bijvoorbeeld maandelijkse unieke bezoekers, aantal e-mail abonnees , volgers, etc. (we zullen deze informatie vertrouwelijk houden).Volg alsjeblieft deze stappen om de upgrade te voltooienLaat ons alsjeblieft weten als je op de hoogte gehouden wilt worden van beveiliging & feature updates, educatieve content en zo nu en dan aanbiedingen:Onthou alsjeblieft dat we geen oude prijzen voor verlengingen/nieuwe abonnementen na een annulering kunnen aanhouden. Als je in de toekomst besluit om een abonnement handmatig te vernieuwen, zal de nieuwe prijs (na een prijsverhoging die meestal jaarlijks plaatsvindt) worden berekend.Geef alsjeblieft zo gedetailleerd als mogelijk aan hoe je van plan bent om %s te gaan promoten.Geef alsjeblieft je volledige naam.Plug-inPlug-in HomepagePlug-in IDPlug-in InstallatieWijzigingen LogBeschrijvingVeelgestelde VragenFeatures & PrijzenInstallatieAndere NotitiesReviewsPlug-in is 'Serviceware' wat betekent dat het geen premium code versie bevat. Plug-insSynchronisatie Plug-ins & Thema'sPremiumPremium %s versie is succesvol geactiveerd.Premium add-on versie is reeds geïnstalleerd.Premium versiePremium versie reeds actief.PrijzenPrivacybeleidDoorgaanProces-IDProductenProgramma SamenvattingPromotie methodesProvinciePublieke SleutelLicentie KopenSnelle terugkoppelingQuotaActivatiemail opnieuw versturenVerwijs nieuwe klanten naar onze %s en krijg %s commissie op iedere door jou doorverwezen, geslaagde verkoop!Vernieuw licentieVernieuw je licentie nuAanvragenVereiste WordPress-versieResultaatSDKSDK PadBespaar %sGeplande CronsSchermafbeeldingenZoek op adresGeheime SleutelBeveiligde HTTPS %s pagina, loopt via een extern domeinHet lijkt erop, dat we een tijdelijk probleem hebben met het annuleren van je abonnement. Probeer het alsjeblieft over een paar minuten nog eens.Het lijkt er op dat we een tijdelijk probleem hebben met het opzeggen van uw proefperiode. Probeer het a.u.b. over enkele minuten nog eens.Het lijkt erop dat je de meest recente versie hebt.Selecteer LandVerzend LicentiesleutelActiveer DB-OptieSimuleer Netwerk UpgradeSimuleer Trial ActieEnkele Site LicentieSite IDSite opt-in geslaagd. SitesSla over & %sSlugSocial media (Facebook, Twitter, etc.)Sorry voor het ongemak en we zijn er om je te helpen als je daartoe de kans geeft..Sorry, we konden de e-mail update niet voltooien. Een andere gebruiker met hetzelfde e-mailadres is reeds geregistreerd.StartStart ProefperiodeStart mijn gratis %sStaatVerstuur & %sAbonnementOndersteuningSupportforumSynchroniseer Data Vanaf ServerBtw-nummerServicevoorwaardenBedankt voor je aanvraag voor deelname aan ons affiliate programma, helaas, op dit moment hebben we besloten je aanvraag af te wijzen. Probeer het alsjeblieft over 30 dagen nog eens.Bedankt voor je aanvraag voor deelname aan ons samenwerkingsprogramma. We zullen binnen 14 dagen je gegevens doornemen, waarna we je aanvullende informatie zullen sturen.Hartelijk bedankt voor het gebruik van %s en bijbehorende uitbreidingen!Hartelijk bedankt voor het gebruik van %s!Hartelijk bedankt voor het gebruiken van onze producten!Bedankt!Bedankt %s!Bedankt voor het bevestigen van de eigendomsoverdracht. Zojuist is er een e-mail verstuurd naar %s voor de definitieve goedkeuring. De %s maakte mijn site onbruikbaarDe %s werkte nietDe %s werkte niet zoals verwachtDe %s is uitstekend, maar ik heb een specifieke feature nodig die jullie niet ondersteunenDe %s werkt nietDe %s werkte opeens niet meerHet installatieproces is gestart en kan enkele minuten duren om te voltooien. Wacht alsjeblieft totdat dat gebeurt is - deze pagina niet verversen.Het remote plug-in pakket bevat geen folder met de verwachte slug en hernoemen werkte niet. De upgrade van %s is succesvol voltooid.ThemaThema WisselThema'sEr is een %s van %s beschikbaar.Er is een nieuwe versie van %s beschikbaar.Deze plug-in is niet als compatibel aangemerkt voor je huidige WordPress versie.Deze plug-in is nog niet getest met je huidige WordPress versie. TijdstempelTitelTotaalPlaatsProefperiodeTypeToegang tot het bestandssysteem is niet mogelijk. Bevestig alsjeblieft je inloggegevens.Onbeperkte LicentiesOnbeperkte UpdatesOnbeperkte commissies.Tot %s SitesBijwerkenUpdate LicentieUpdates, aankondigingen, marketing, geen spamUpgradeUpload en activeer de gedownloade versieW00tGebruikers IDGebruikersWaardeVerificatiemail zojuist verstuurd naar %s. Als je deze niet binnen 5 min. hebt ontvangen, kijk dan alsjeblieft in je spambox.GeverifieerdVerifieer E-mailVersie %s is vrijgegeven.Bekijk detailsBekijk betaalde kenmerkenWaarschuwingEr is geen actieve licentie gekoppeld aan dat e-mailadres, ben je zeker dat dat het juiste adres is?We konden je e-mailadres niet vinden in het systeem, ben je zeker dat dat het juiste adres is?We hebben een aantal aanpassingen gedaan op de %s, %s We zijn verheugd om Freemius network-level integratie te introduceren.Website, mail, and social media statistieken (optioneel)Wat had je verwacht?Welke feature?Wat is je %s?Welke bedrag zou je ervoor over hebben?Waar was je naar op zoek?Wat is de naam van het %s?Waar ga je de %s promoten?WordPress.org Plug-in PaginaJaJa - %sU heeft reeds een proefperiode gebruikt.U bent 1-klik verwijderd van het starten van uw %1$s-daagse gratis proefperiode van het %2$s plan.Alles is goed!Je draait de %s al in proefmodus.Je bent slechts een stap verwijderd - %sJe kunt nog steeds van alle %s-mogelijkheden genieten, maar je zult geen toegang hebben tot %s veiligheids- en uitbreidingsupdates, noch ondersteuning.Je hebt geen geldige licentie voor de premium versie.Je hebt een %s licentieJe hebt je %s succesvol geüpdatet.Misschien heb je het gemist, maar je hoeft geen gegevens te delen en kunt de opt-in %s.Je hebt reeds ingestemd met onze gebruiks-tracking, wat ons helpt om %s te blijven verbeteren.Je hebt reeds ingestemd met onze gebruiks-tracking, wat ons helpt om deze te blijven verbeteren.Uw %sAdd-on plan werd succesvol geüpgraded. Uw gratis %s proefperiode is succesvol opgezegd. Je account is succesvol geactiveerd met het %s plan.Je samenwerkingsaanvraag voor %s is geaccepteerd! Log in op je samenwerkingsomgeving op: %s.Je affiliate account is tijdelijk geschorst.Je e-mail werd succesvol geverifieerd - je bent GEWELDIG!Je gratis proefperiode is verlopen. %1$sUpgrade nu%2$som de %3$s zonder interrupties te blijven gebruiken. Je gratis proefperiode is verlopen. Je kan nog steeds al onze gratis features blijven gebruiken.Je licentie is geannuleerd. Als je denkt dat dat een fout is, neem dan alsjeblieft contact op met support.Je licentie is verlopen. %1$sUpgrade nu%2$s om de %3$s zonder interrupties te blijven gebruiken.Je licentie is verlopen. Je kan nog steeds alle %s features gebruiken, maar je zal je licentie moeten vernieuwen om weer updates en support te ontvangen.Je licentie is verlopen. Je kan echter de gratis %s voor altijd blijven gebruiken.Je licentie is succesvol geactiveerd.Je licentie is succesvol gedeactiveerd, je bent terug op het %s plan.Je naam is succesvol bijgewerkt.Je plan is succesvol veranderd naar %s.Je plan is succesvol geüpgraded.Je abonnement is succesvol geannuleerd. De licentie van je %s-plan al over %s aflopen.U proefperiode is met succes gestart.PostcodeToppieActiveer%s werkt niet zonder %s.%s werkt niet zonder de plug-in.Aankondigingtoestaan%s beschikbaarActiverenjaarAPIAfsluitenDebuggingGefeliciteerdGeblokkeerdVerbondenDownload NieuwsteDownload Nieuwste Gratis VersieMaandelijksVerloopdatumPadE-mail versturenmndJaarlijksJaarlijksEenmaligPlanGeen GeheimSDK VersiesLicentieSyncSync LicentieAuteurUitAangebaseerd op %sStart gratis proefperidoeAfsluitenAfsluitendeactiverendeligerenstuur mij %sGEEN%s beveiliging & feature updates, educatieve content of aanbiedingen.%s Plan%s gefactureerd BesteHoiOepsHoi %s,HoeralicentieSitesmsnieuwe versieniet geverifieerdPrijsPrijzenVersiesecstuur mij beveiliging & feature updates, educatieve content en aanbiedingen.overslaanHmmstart de proefperiodeabonnementoverschakelende meest recente %s versie hierproefperiodeProefperiodeVerwijderDowngradeBewerkVerbergOpt InOpt OutKoopToonSla OverBijwerkenUpgrade%s geledenPK!c<<)libraries/freemius/languages/freemius.potnu[# Copyright (C) 2024 freemius # This file is distributed under the same license as the freemius package. msgid "" msgstr "" "Project-Id-Version: freemius\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language-Team: Freemius Team \n" "Last-Translator: Vova Feldman \n" "POT-Creation-Date: 2024-10-20 12:05+0000\n" "Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues\n" "X-Poedit-Basepath: ..\n" "X-Poedit-KeywordsList: get_text_inline;get_text_x_inline:1,2c;$this->get_text_inline;$this->get_text_x_inline:1,2c;$this->_fs->get_text_inline;$this->_fs->get_text_x_inline:1,2c;$this->fs->get_text_inline;$this->fs->get_text_x_inline:1,2c;$fs->get_text_inline;$fs->get_text_x_inline:1,2c;$this->_parent->get_text_inline;$this->_parent->get_text_x_inline:1,2c;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c\n" "X-Poedit-SearchPath-0: .\n" "X-Poedit-SearchPathExcluded-0: *.js\n" "X-Poedit-SourceCharset: UTF-8\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: includes/class-freemius.php:1790, templates/account.php:943 msgid "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." msgstr "" #: includes/class-freemius.php:1797 msgid "Would you like to proceed with the update?" msgstr "" #: includes/class-freemius.php:2022 msgid "Freemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error." msgstr "" #: includes/class-freemius.php:2024, includes/fs-plugin-info-dialog.php:1513 msgid "Error" msgstr "" #: includes/class-freemius.php:2470 msgid "I found a better %s" msgstr "" #: includes/class-freemius.php:2472 msgid "What's the %s's name?" msgstr "" #: includes/class-freemius.php:2478 msgid "It's a temporary %s - I'm troubleshooting an issue" msgstr "" #: includes/class-freemius.php:2480 msgid "Deactivation" msgstr "" #: includes/class-freemius.php:2481 msgid "Theme Switch" msgstr "" #: includes/class-freemius.php:2490, templates/forms/resend-key.php:24, templates/forms/user-change.php:29 msgid "Other" msgstr "" #: includes/class-freemius.php:2498 msgid "I no longer need the %s" msgstr "" #: includes/class-freemius.php:2505 msgid "I only needed the %s for a short period" msgstr "" #: includes/class-freemius.php:2511 msgid "The %s broke my site" msgstr "" #: includes/class-freemius.php:2518 msgid "The %s suddenly stopped working" msgstr "" #: includes/class-freemius.php:2528 msgid "I can't pay for it anymore" msgstr "" #: includes/class-freemius.php:2530 msgid "What price would you feel comfortable paying?" msgstr "" #: includes/class-freemius.php:2536 msgid "I don't like to share my information with you" msgstr "" #: includes/class-freemius.php:2557 msgid "The %s didn't work" msgstr "" #: includes/class-freemius.php:2567 msgid "I couldn't understand how to make it work" msgstr "" #: includes/class-freemius.php:2575 msgid "The %s is great, but I need specific feature that you don't support" msgstr "" #: includes/class-freemius.php:2577 msgid "What feature?" msgstr "" #: includes/class-freemius.php:2581 msgid "The %s is not working" msgstr "" #: includes/class-freemius.php:2583 msgid "Kindly share what didn't work so we can fix it for future users..." msgstr "" #: includes/class-freemius.php:2587 msgid "It's not what I was looking for" msgstr "" #: includes/class-freemius.php:2589 msgid "What you've been looking for?" msgstr "" #: includes/class-freemius.php:2593 msgid "The %s didn't work as expected" msgstr "" #: includes/class-freemius.php:2595 msgid "What did you expect?" msgstr "" #. translators: %s: License type (e.g. you have a professional license) #: includes/class-freemius.php:4517 msgid "You have purchased a %s license." msgstr "" #: includes/class-freemius.php:4521 msgid " The %s's %sdownload link%s, license key, and installation instructions have been sent to %s. If you can't find the email after 5 min, please check your spam box." msgstr "" #: includes/class-freemius.php:4531, includes/class-freemius.php:20889, includes/class-freemius.php:24610 msgctxt "interjection expressing joy or exuberance" msgid "Yee-haw" msgstr "" #: includes/class-freemius.php:4545 msgctxt "addonX cannot run without pluginY" msgid "%s cannot run without %s." msgstr "" #: includes/class-freemius.php:4546 msgctxt "addonX cannot run..." msgid "%s cannot run without the plugin." msgstr "" #: includes/class-freemius.php:4548, includes/class-freemius.php:5745, includes/class-freemius.php:13477, includes/class-freemius.php:14228, includes/class-freemius.php:17997, includes/class-freemius.php:18117, includes/class-freemius.php:18294, includes/class-freemius.php:20620, includes/class-freemius.php:21736, includes/class-freemius.php:22772, includes/class-freemius.php:22902, includes/class-freemius.php:23045, templates/add-ons.php:57 msgctxt "exclamation" msgid "Oops" msgstr "" #: includes/class-freemius.php:4827 msgid "There was an unexpected API error while processing your request. Please try again in a few minutes and if it still doesn't work, contact the %s's author with the following:" msgstr "" #. translators: %s: License type (e.g. you have a professional license) #: includes/class-freemius.php:5437 msgid "You have a %s license." msgstr "" #: includes/class-freemius.php:5410 msgid "Premium %s version was successfully activated." msgstr "" #: includes/class-freemius.php:5422, includes/class-freemius.php:7434 msgctxt "Used to express elation, enthusiasm, or triumph (especially in electronic communication)." msgid "W00t" msgstr "" #: includes/class-freemius.php:5728 msgid "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license." msgstr "" #: includes/class-freemius.php:5732 msgid "%s is a premium only add-on. You have to purchase a license first before activating the plugin." msgstr "" #: includes/class-freemius.php:5741, templates/add-ons.php:186, templates/account/partials/addon.php:386 msgid "More information about %s" msgstr "" #: includes/class-freemius.php:5742 msgid "Purchase License" msgstr "" #. translators: %3$s: What the user is expected to receive via email (e.g.: "the installation instructions" or "a license key") #: includes/class-freemius.php:6747 msgid "You should receive %3$s for %1$s to your mailbox at %2$s in the next 5 minutes." msgstr "" #: includes/class-freemius.php:6756 msgctxt "Part of the message telling the user what they should receive via email." msgid "a license key" msgstr "" #. translators: %s: activation link (e.g.: Click here) #: includes/class-freemius.php:6764 msgid "%s to activate the license once you get it." msgstr "" #: includes/class-freemius.php:6772 msgctxt "Part of an activation link message." msgid "Click here" msgstr "" #: includes/class-freemius.php:6750 msgctxt "Part of the message telling the user what they should receive via email." msgid "the installation instructions" msgstr "" #: includes/class-freemius.php:6779 msgctxt "Part of the message that tells the user to check their spam folder for a specific email." msgid "the product's support email address" msgstr "" #: includes/class-freemius.php:6785 msgid "If you didn't get the email, try checking your spam folder or search for emails from %4$s." msgstr "" #: includes/class-freemius.php:6787 msgid "Thanks for upgrading." msgstr "" #: includes/class-freemius.php:6738 msgid "You should receive a confirmation email for %1$s to your mailbox at %2$s. Please make sure you click the button in that email to %3$s." msgstr "" #: includes/class-freemius.php:6741 msgid "start the trial" msgstr "" #: includes/class-freemius.php:6742, templates/connect.php:208 msgid "complete the opt-in" msgstr "" #: includes/class-freemius.php:6744 msgid "Thanks!" msgstr "" #: includes/class-freemius.php:6923 msgid "You are just one step away - %s" msgstr "" #: includes/class-freemius.php:6926 msgctxt "%s - plugin name. As complete \"PluginX\" activation now" msgid "Complete \"%s\" Activation Now" msgstr "" #: includes/class-freemius.php:7008 msgid "We made a few tweaks to the %s, %s" msgstr "" #: includes/class-freemius.php:7012 msgid "Opt in to make \"%s\" better!" msgstr "" #: includes/class-freemius.php:7433 msgid "The upgrade of %s was successfully completed." msgstr "" #: includes/class-freemius.php:10196, includes/class-fs-plugin-updater.php:1142, includes/class-fs-plugin-updater.php:1364, includes/class-fs-plugin-updater.php:1357, templates/auto-installation.php:32 msgid "Add-On" msgstr "" #: includes/class-freemius.php:10198, templates/account.php:407, templates/account.php:415, templates/debug.php:458, templates/debug.php:678 msgid "Plugin" msgstr "" #: includes/class-freemius.php:10199, templates/account.php:408, templates/account.php:416, templates/debug.php:458, templates/debug.php:678, templates/forms/deactivation/form.php:107 msgid "Theme" msgstr "" #: includes/class-freemius.php:13284 msgid "An unknown error has occurred while trying to toggle the license's white-label mode." msgstr "" #: includes/class-freemius.php:13298 msgid "Your %s license was flagged as white-labeled to hide sensitive information from the WP Admin (e.g. your email, license key, prices, billing address & invoices). If you ever wish to revert it back, you can easily do it through your %s. If this was a mistake you can also %s." msgstr "" #: includes/class-freemius.php:13303, templates/account/partials/disconnect-button.php:84 msgid "User Dashboard" msgstr "" #: includes/class-freemius.php:13304 msgid "revert it now" msgstr "" #: includes/class-freemius.php:13362 msgid "An unknown error has occurred while trying to set the user's beta mode." msgstr "" #: includes/class-freemius.php:13448 msgid "Invalid new user ID or email address." msgstr "" #: includes/class-freemius.php:13478 msgid "Sorry, we could not complete the email update. Another user with the same email is already registered." msgstr "" #: includes/class-freemius.php:13479 msgid "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button." msgstr "" #: includes/class-freemius.php:13486 msgid "Change Ownership" msgstr "" #: includes/class-freemius.php:14095 msgid "Invalid site details collection." msgstr "" #: includes/class-freemius.php:14217 msgid "We can't see any active licenses associated with that email address, are you sure it's the right address?" msgstr "" #: includes/class-freemius.php:14215 msgid "We couldn't find your email address in the system, are you sure it's the right address?" msgstr "" #: includes/class-freemius.php:14521 msgid "Account is pending activation. Please check your email and click the link to activate your account and then submit the affiliate form again." msgstr "" #: includes/class-freemius.php:14647, templates/forms/premium-versions-upgrade-handler.php:46 msgid "Renew your license now" msgstr "" #: includes/class-freemius.php:14635, templates/forms/premium-versions-upgrade-handler.php:47 msgid "Buy a license now" msgstr "" #: includes/class-freemius.php:14651 msgid "%s to access version %s security & feature updates, and support." msgstr "" #: includes/class-freemius.php:17337 msgid "%s opt-in was successfully completed." msgstr "" #: includes/class-freemius.php:17361, includes/class-freemius.php:21346 msgid "Your trial has been successfully started." msgstr "" #: includes/class-freemius.php:17351 msgid "Your account was successfully activated with the %s plan." msgstr "" #: includes/class-freemius.php:17995, includes/class-freemius.php:18115, includes/class-freemius.php:18292 msgid "Couldn't activate %s." msgstr "" #: includes/class-freemius.php:17996, includes/class-freemius.php:18116, includes/class-freemius.php:18293 msgid "Please contact us with the following message:" msgstr "" #: includes/class-freemius.php:18112, templates/forms/data-debug-mode.php:162 msgid "An unknown error has occurred." msgstr "" #: includes/class-freemius.php:18654, includes/class-freemius.php:24166 msgid "Upgrade" msgstr "" #: includes/class-freemius.php:18662 msgid "Pricing" msgstr "" #: includes/class-freemius.php:18660 msgid "Start Trial" msgstr "" #: includes/class-freemius.php:18742, includes/class-freemius.php:18744 msgid "Affiliation" msgstr "" #: includes/class-freemius.php:18772, includes/class-freemius.php:18774, templates/account.php:260, templates/debug.php:425 msgid "Account" msgstr "" #: includes/class-freemius.php:18800, includes/class-freemius.php:18789, includes/class-freemius.php:18791, includes/customizer/class-fs-customizer-support-section.php:60 msgid "Contact Us" msgstr "" #: includes/class-freemius.php:18814, includes/class-freemius.php:18816, includes/class-freemius.php:24180, templates/account.php:130, templates/account/partials/addon.php:49 msgid "Add-Ons" msgstr "" #: includes/class-freemius.php:18849 msgctxt "ASCII arrow left icon" msgid "←" msgstr "" #: includes/class-freemius.php:18849 msgctxt "ASCII arrow right icon" msgid "➤" msgstr "" #: includes/class-freemius.php:18867 msgctxt "noun" msgid "Pricing" msgstr "" #: includes/class-freemius.php:19083, includes/customizer/class-fs-customizer-support-section.php:67 msgid "Support Forum" msgstr "" #: includes/class-freemius.php:20114 msgid "Your email has been successfully verified - you are AWESOME!" msgstr "" #: includes/class-freemius.php:20115 msgctxt "a positive response" msgid "Right on" msgstr "" #: includes/class-freemius.php:20621 msgid "seems like the key you entered doesn't match our records." msgstr "" #: includes/class-freemius.php:20645 msgid "Debug mode was successfully enabled and will be automatically disabled in 60 min. You can also disable it earlier by clicking the \"Stop Debug\" link." msgstr "" #: includes/class-freemius.php:20880 msgid "Your %s Add-on plan was successfully upgraded." msgstr "" #. translators: %s:product name, e.g. Facebook add-on was successfully... #: includes/class-freemius.php:20882 msgid "%s Add-on was successfully purchased." msgstr "" #: includes/class-freemius.php:20885 msgid "Download the latest version" msgstr "" #: includes/class-freemius.php:21003 msgid "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again." msgstr "" #: includes/class-freemius.php:21003, includes/class-freemius.php:21416, includes/class-freemius.php:21517, includes/class-freemius.php:21604 msgid "Error received from the server:" msgstr "" #: includes/class-freemius.php:21244, includes/class-freemius.php:21522, includes/class-freemius.php:21575, includes/class-freemius.php:21682 msgctxt "something somebody says when they are thinking about what you have just said." msgid "Hmm" msgstr "" #: includes/class-freemius.php:21257 msgid "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry." msgstr "" #: includes/class-freemius.php:21258, templates/account.php:132, templates/add-ons.php:250, templates/account/partials/addon.php:51 msgctxt "trial period" msgid "Trial" msgstr "" #: includes/class-freemius.php:21263 msgid "I have upgraded my account but when I try to Sync the License, the plan remains %s." msgstr "" #: includes/class-freemius.php:21267, includes/class-freemius.php:21325 msgid "Please contact us here" msgstr "" #: includes/class-freemius.php:21295 msgid "Your plan was successfully changed to %s." msgstr "" #: includes/class-freemius.php:21311 msgid "Your license has expired. You can still continue using the free %s forever." msgstr "" #. translators: %1$s: product title; %2$s, %3$s: wrapping HTML anchor element; %4$s: 'plugin', 'theme', or 'add-on'. #: includes/class-freemius.php:21313 msgid "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." msgstr "" #: includes/class-freemius.php:21321 msgid "Your license has been cancelled. If you think it's a mistake, please contact support." msgstr "" #: includes/class-freemius.php:21334 msgid "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support." msgstr "" #: includes/class-freemius.php:21360 msgid "Your free trial has expired. You can still continue using all our free features." msgstr "" #. translators: %1$s: product title; %2$s, %3$s: wrapping HTML anchor element; %4$s: 'plugin', 'theme', or 'add-on'. #: includes/class-freemius.php:21362 msgid "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." msgstr "" #: includes/class-freemius.php:21408 msgid "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist the following domains:%2$s" msgstr "" #: includes/class-freemius.php:21410 msgid "Show error details" msgstr "" #: includes/class-freemius.php:21513 msgid "It looks like the license could not be activated." msgstr "" #: includes/class-freemius.php:21555 msgid "Your license was successfully activated." msgstr "" #: includes/class-freemius.php:21579 msgid "It looks like your site currently doesn't have an active license." msgstr "" #: includes/class-freemius.php:21603 msgid "It looks like the license deactivation failed." msgstr "" #: includes/class-freemius.php:21632 msgid "Your %s license was successfully deactivated." msgstr "" #: includes/class-freemius.php:21633 msgid "Your license was successfully deactivated, you are back to the %s plan." msgstr "" #: includes/class-freemius.php:21636 msgid "O.K" msgstr "" #: includes/class-freemius.php:21689 msgid "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." msgstr "" #: includes/class-freemius.php:21698 msgid "Your subscription was successfully cancelled. Your %s plan license will expire in %s." msgstr "" #: includes/class-freemius.php:21741 msgid "You are already running the %s in a trial mode." msgstr "" #: includes/class-freemius.php:21753 msgid "You already utilized a trial before." msgstr "" #: includes/class-freemius.php:21792 msgid "None of the %s's plans supports a trial period." msgstr "" #: includes/class-freemius.php:21768 msgid "Plan %s do not exist, therefore, can't start a trial." msgstr "" #: includes/class-freemius.php:21780 msgid "Plan %s does not support a trial period." msgstr "" #: includes/class-freemius.php:21854 msgid "It looks like you are not in trial mode anymore so there's nothing to cancel :)" msgstr "" #: includes/class-freemius.php:21890 msgid "Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes." msgstr "" #: includes/class-freemius.php:21909 msgid "Your %s free trial was successfully cancelled." msgstr "" #: includes/class-freemius.php:22256 msgid "Seems like you got the latest release." msgstr "" #: includes/class-freemius.php:22257 msgid "You are all good!" msgstr "" #: includes/class-freemius.php:22239 msgid "Version %s was released." msgstr "" #: includes/class-freemius.php:22239 msgid "Please download %s." msgstr "" #: includes/class-freemius.php:22246 msgid "the latest %s version here" msgstr "" #: includes/class-freemius.php:22251 msgid "New" msgstr "" #: includes/class-freemius.php:22660 msgid "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box." msgstr "" #: includes/class-freemius.php:22800 msgid "Site successfully opted in." msgstr "" #: includes/class-freemius.php:22801, includes/class-freemius.php:23876 msgid "Awesome" msgstr "" #: includes/class-freemius.php:22827 msgid "Diagnostic data will no longer be sent from %s to %s." msgstr "" #: includes/class-freemius.php:22817 msgid "Sharing diagnostic data with %s helps to provide functionality that's more relevant to your website, avoid WordPress or PHP version incompatibilities that can break your website, and recognize which languages & regions the plugin should be translated and tailored to." msgstr "" #: includes/class-freemius.php:22818 msgid "Thank you!" msgstr "" #: includes/class-freemius.php:22987 msgid "A confirmation email was just sent to %s. You must confirm the update within the next 4 hours. If you cannot find the email, please check your spam folder." msgstr "" #: includes/class-freemius.php:22985 msgid "A confirmation email was just sent to %s. The email owner must confirm the update within the next 4 hours." msgstr "" #: includes/class-freemius.php:22999 msgid "Thanks for confirming the ownership change. An email was just sent to %s for final approval." msgstr "" #: includes/class-freemius.php:23005 msgid "%s is the new owner of the account." msgstr "" #: includes/class-freemius.php:23007 msgctxt "as congratulations" msgid "Congrats" msgstr "" #: includes/class-freemius.php:23029 msgid "Your name was successfully updated." msgstr "" #: includes/class-freemius.php:23024 msgid "Please provide your full name." msgstr "" #. translators: %s: User's account property (e.g. email address, name) #: includes/class-freemius.php:23094 msgid "You have successfully updated your %s." msgstr "" #: includes/class-freemius.php:23158 msgid "Is this your client's site? %s if you wish to hide sensitive info like your email, license key, prices, billing address & invoices from the WP Admin." msgstr "" #: includes/class-freemius.php:23161 msgid "Click here" msgstr "" #: includes/class-freemius.php:23198 msgid "Bundle" msgstr "" #: includes/class-freemius.php:23271 msgid "Just letting you know that the add-ons information of %s is being pulled from an external server." msgstr "" #: includes/class-freemius.php:23272 msgctxt "advance notice of something that will need attention." msgid "Heads up" msgstr "" #: includes/class-freemius.php:23916 msgctxt "exclamation" msgid "Hey" msgstr "" #: includes/class-freemius.php:23916 msgid "How do you like %s so far? Test all our %s premium features with a %d-day free trial." msgstr "" #: includes/class-freemius.php:23924 msgid "No commitment for %s days - cancel anytime!" msgstr "" #: includes/class-freemius.php:23925 msgid "No credit card required" msgstr "" #: includes/class-freemius.php:23932, templates/forms/trial-start.php:53 msgctxt "call to action" msgid "Start free trial" msgstr "" #: includes/class-freemius.php:24009 msgid "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!" msgstr "" #: includes/class-freemius.php:24018 msgid "Learn more" msgstr "" #: includes/class-freemius.php:24204, templates/account.php:569, templates/account.php:721, templates/connect.php:211, templates/connect.php:439, includes/managers/class-fs-clone-manager.php:1295, templates/forms/license-activation.php:27, templates/account/partials/addon.php:326 msgid "Activate License" msgstr "" #: includes/class-freemius.php:24205, templates/account.php:663, templates/account.php:720, templates/account/partials/addon.php:327, templates/account/partials/site.php:273 msgid "Change License" msgstr "" #: includes/class-freemius.php:24320, includes/class-freemius.php:24314, templates/account/partials/site.php:49, templates/account/partials/site.php:170 msgid "Opt In" msgstr "" #: includes/class-freemius.php:24312, templates/account/partials/site.php:170 msgid "Opt Out" msgstr "" #: includes/class-freemius.php:24578 msgid "Please follow these steps to complete the upgrade" msgstr "" #. translators: %s: Plan title #: includes/class-freemius.php:24582 msgid "Download the latest %s version" msgstr "" #: includes/class-freemius.php:24586 msgid "Upload and activate the downloaded version" msgstr "" #: includes/class-freemius.php:24588 msgid "How to upload and activate?" msgstr "" #: includes/class-freemius.php:24555 msgid " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" msgstr "" #: includes/class-freemius.php:24565 msgid "Activate %s features" msgstr "" #: includes/class-freemius.php:24623 msgid "Your plan was successfully upgraded." msgstr "" #: includes/class-freemius.php:24624 msgid "Your plan was successfully activated." msgstr "" #: includes/class-freemius.php:24733 msgid "%sClick here%s to choose the sites where you'd like to activate the license on." msgstr "" #: includes/class-freemius.php:24902 msgid "Auto installation only works for opted-in users." msgstr "" #: includes/class-freemius.php:24912, includes/class-freemius.php:24945, includes/class-fs-plugin-updater.php:1336, includes/class-fs-plugin-updater.php:1350 msgid "Invalid module ID." msgstr "" #: includes/class-freemius.php:24953, includes/class-fs-plugin-updater.php:1371 msgid "Premium add-on version already installed." msgstr "" #: includes/class-freemius.php:24921, includes/class-fs-plugin-updater.php:1372 msgid "Premium version already active." msgstr "" #: includes/class-freemius.php:24928 msgid "You do not have a valid license to access the premium version." msgstr "" #: includes/class-freemius.php:24935 msgid "Plugin is a \"Serviceware\" which means it does not have a premium code version." msgstr "" #: includes/class-freemius.php:25313 msgid "View paid features" msgstr "" #: includes/class-freemius.php:25628 msgid "Thank you so much for using our products!" msgstr "" #: includes/class-freemius.php:25629 msgid "You've already opted-in to our usage-tracking, which helps us keep improving them." msgstr "" #: includes/class-freemius.php:25648 msgid "%s and its add-ons" msgstr "" #: includes/class-freemius.php:25657 msgid "Products" msgstr "" #: includes/class-freemius.php:25617 msgid "Thank you so much for using %s and its add-ons!" msgstr "" #: includes/class-freemius.php:25618 msgid "Thank you so much for using %s!" msgstr "" #: includes/class-freemius.php:25624 msgid "You've already opted-in to our usage-tracking, which helps us keep improving the %s." msgstr "" #: includes/class-freemius.php:25664, templates/connect.php:312 msgid "Yes" msgstr "" #: includes/class-freemius.php:25665, templates/connect.php:313 msgid "send me security & feature updates, educational content and offers." msgstr "" #: includes/class-freemius.php:25666, templates/connect.php:318 msgid "No" msgstr "" #: includes/class-freemius.php:25668, templates/connect.php:320 msgid "do %sNOT%s send me security & feature updates, educational content and offers." msgstr "" #: includes/class-freemius.php:25678 msgid "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" msgstr "" #: includes/class-freemius.php:25680, templates/connect.php:327 msgid "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" msgstr "" #: includes/class-freemius.php:25970 msgid "License key is empty." msgstr "" #: includes/class-fs-plugin-updater.php:247, templates/forms/premium-versions-upgrade-handler.php:57 msgid "Renew license" msgstr "" #: includes/class-fs-plugin-updater.php:252, templates/forms/premium-versions-upgrade-handler.php:58 msgid "Buy license" msgstr "" #: includes/class-fs-plugin-updater.php:405, includes/class-fs-plugin-updater.php:372 msgid "There is a %s of %s available." msgstr "" #: includes/class-fs-plugin-updater.php:410, includes/class-fs-plugin-updater.php:374 msgid "new Beta version" msgstr "" #: includes/class-fs-plugin-updater.php:411, includes/class-fs-plugin-updater.php:375 msgid "new version" msgstr "" #: includes/class-fs-plugin-updater.php:434 msgid "Important Upgrade Notice:" msgstr "" #: includes/class-fs-plugin-updater.php:1401 msgid "Installing plugin: %s" msgstr "" #: includes/class-fs-plugin-updater.php:1442 msgid "Unable to connect to the filesystem. Please confirm your credentials." msgstr "" #: includes/class-fs-plugin-updater.php:1624 msgid "The remote plugin package does not contain a folder with the desired slug and renaming did not work." msgstr "" #: includes/fs-plugin-info-dialog.php:542 msgid "Purchase More" msgstr "" #: includes/fs-plugin-info-dialog.php:543, templates/account/partials/addon.php:390 msgctxt "verb" msgid "Purchase" msgstr "" #. translators: %s: N-days trial #: includes/fs-plugin-info-dialog.php:547 msgid "Start my free %s" msgstr "" #: includes/fs-plugin-info-dialog.php:755 msgid "Install Free Version Now" msgstr "" #: includes/fs-plugin-info-dialog.php:756, templates/add-ons.php:323, templates/auto-installation.php:111, templates/account/partials/addon.php:423, templates/account/partials/addon.php:370 msgid "Install Now" msgstr "" #: includes/fs-plugin-info-dialog.php:745 msgid "Install Free Version Update Now" msgstr "" #: includes/fs-plugin-info-dialog.php:746, templates/account.php:652 msgid "Install Update Now" msgstr "" #: includes/fs-plugin-info-dialog.php:772 msgctxt "as download latest version" msgid "Download Latest Free Version" msgstr "" #: includes/fs-plugin-info-dialog.php:773, templates/account.php:110, templates/add-ons.php:37, templates/account/partials/addon.php:30 msgctxt "as download latest version" msgid "Download Latest" msgstr "" #: includes/fs-plugin-info-dialog.php:788, templates/add-ons.php:329, templates/account/partials/addon.php:417, templates/account/partials/addon.php:361 msgid "Activate this add-on" msgstr "" #: includes/fs-plugin-info-dialog.php:790, templates/connect.php:436 msgid "Activate Free Version" msgstr "" #: includes/fs-plugin-info-dialog.php:791, templates/account.php:134, templates/add-ons.php:330, templates/account/partials/addon.php:53 msgid "Activate" msgstr "" #: includes/fs-plugin-info-dialog.php:999 msgctxt "Plugin installer section title" msgid "Description" msgstr "" #: includes/fs-plugin-info-dialog.php:1000 msgctxt "Plugin installer section title" msgid "Installation" msgstr "" #: includes/fs-plugin-info-dialog.php:1001 msgctxt "Plugin installer section title" msgid "FAQ" msgstr "" #: includes/fs-plugin-info-dialog.php:1002, templates/plugin-info/description.php:55 msgid "Screenshots" msgstr "" #: includes/fs-plugin-info-dialog.php:1003 msgctxt "Plugin installer section title" msgid "Changelog" msgstr "" #: includes/fs-plugin-info-dialog.php:1004 msgctxt "Plugin installer section title" msgid "Reviews" msgstr "" #: includes/fs-plugin-info-dialog.php:1005 msgctxt "Plugin installer section title" msgid "Other Notes" msgstr "" #: includes/fs-plugin-info-dialog.php:1020 msgctxt "Plugin installer section title" msgid "Features & Pricing" msgstr "" #: includes/fs-plugin-info-dialog.php:1030 msgid "Plugin Install" msgstr "" #: includes/fs-plugin-info-dialog.php:1102 msgctxt "e.g. Professional Plan" msgid "%s Plan" msgstr "" #: includes/fs-plugin-info-dialog.php:1128 msgctxt "e.g. the best product" msgid "Best" msgstr "" #: includes/fs-plugin-info-dialog.php:1134, includes/fs-plugin-info-dialog.php:1154 msgctxt "as every month" msgid "Monthly" msgstr "" #: includes/fs-plugin-info-dialog.php:1137 msgctxt "as once a year" msgid "Annual" msgstr "" #: includes/fs-plugin-info-dialog.php:1140 msgid "Lifetime" msgstr "" #: includes/fs-plugin-info-dialog.php:1154, includes/fs-plugin-info-dialog.php:1156, includes/fs-plugin-info-dialog.php:1158 msgctxt "e.g. billed monthly" msgid "Billed %s" msgstr "" #: includes/fs-plugin-info-dialog.php:1156 msgctxt "as once a year" msgid "Annually" msgstr "" #: includes/fs-plugin-info-dialog.php:1158 msgctxt "as once a year" msgid "Once" msgstr "" #: includes/fs-plugin-info-dialog.php:1164 msgid "Single Site License" msgstr "" #: includes/fs-plugin-info-dialog.php:1166 msgid "Unlimited Licenses" msgstr "" #: includes/fs-plugin-info-dialog.php:1168 msgid "Up to %s Sites" msgstr "" #: includes/fs-plugin-info-dialog.php:1178, templates/plugin-info/features.php:82 msgctxt "as monthly period" msgid "mo" msgstr "" #: includes/fs-plugin-info-dialog.php:1185, templates/plugin-info/features.php:80 msgctxt "as annual period" msgid "year" msgstr "" #: includes/fs-plugin-info-dialog.php:1239 msgctxt "noun" msgid "Price" msgstr "" #. translators: %s: Discount (e.g. discount of $5 or 10%) #: includes/fs-plugin-info-dialog.php:1287 msgid "Save %s" msgstr "" #: includes/fs-plugin-info-dialog.php:1297 msgid "No commitment for %s - cancel anytime" msgstr "" #: includes/fs-plugin-info-dialog.php:1300 msgid "After your free %s, pay as little as %s" msgstr "" #: includes/fs-plugin-info-dialog.php:1311 msgid "Details" msgstr "" #: includes/fs-plugin-info-dialog.php:1315, templates/account.php:121, templates/debug.php:291, templates/debug.php:328, templates/debug.php:577, templates/account/partials/addon.php:41 msgctxt "product version" msgid "Version" msgstr "" #: includes/fs-plugin-info-dialog.php:1322 msgctxt "as the plugin author" msgid "Author" msgstr "" #: includes/fs-plugin-info-dialog.php:1329 msgid "Last Updated" msgstr "" #. translators: %s: time period (e.g. "2 hours" ago) #: includes/fs-plugin-info-dialog.php:1334, templates/account.php:540 msgctxt "x-ago" msgid "%s ago" msgstr "" #: includes/fs-plugin-info-dialog.php:1343 msgid "Requires WordPress Version" msgstr "" #. translators: %s: Version number. #: includes/fs-plugin-info-dialog.php:1346, includes/fs-plugin-info-dialog.php:1366 msgid "%s or higher" msgstr "" #: includes/fs-plugin-info-dialog.php:1354 msgid "Compatible up to" msgstr "" #: includes/fs-plugin-info-dialog.php:1362 msgid "Requires PHP Version" msgstr "" #: includes/fs-plugin-info-dialog.php:1375 msgid "Downloaded" msgstr "" #. translators: %s: 1 or One (Number of times downloaded) #: includes/fs-plugin-info-dialog.php:1379 msgid "%s time" msgstr "" #. translators: %s: Number of times downloaded #: includes/fs-plugin-info-dialog.php:1381 msgid "%s times" msgstr "" #: includes/fs-plugin-info-dialog.php:1392 msgid "WordPress.org Plugin Page" msgstr "" #: includes/fs-plugin-info-dialog.php:1401 msgid "Plugin Homepage" msgstr "" #: includes/fs-plugin-info-dialog.php:1410, includes/fs-plugin-info-dialog.php:1494 msgid "Donate to this plugin" msgstr "" #: includes/fs-plugin-info-dialog.php:1417 msgid "Average Rating" msgstr "" #: includes/fs-plugin-info-dialog.php:1424 msgid "based on %s" msgstr "" #. translators: %s: 1 or One #: includes/fs-plugin-info-dialog.php:1428 msgid "%s rating" msgstr "" #. translators: %s: Number larger than 1 #: includes/fs-plugin-info-dialog.php:1430 msgid "%s ratings" msgstr "" #. translators: %s: 1 or One #: includes/fs-plugin-info-dialog.php:1445 msgid "%s star" msgstr "" #. translators: %s: Number larger than 1 #: includes/fs-plugin-info-dialog.php:1447 msgid "%s stars" msgstr "" #. translators: %s: # of stars (e.g. 5 stars) #: includes/fs-plugin-info-dialog.php:1459 msgid "Click to see reviews that provided a rating of %s" msgstr "" #: includes/fs-plugin-info-dialog.php:1472 msgid "Contributors" msgstr "" #: includes/fs-plugin-info-dialog.php:1513 msgid "This plugin requires a newer version of PHP." msgstr "" #. translators: %s: URL to Update PHP page. #: includes/fs-plugin-info-dialog.php:1522 msgid "Click here to learn more about updating PHP." msgstr "" #: includes/fs-plugin-info-dialog.php:1538, includes/fs-plugin-info-dialog.php:1536 msgid "Warning" msgstr "" #: includes/fs-plugin-info-dialog.php:1538 msgid "This plugin has not been marked as compatible with your version of WordPress." msgstr "" #: includes/fs-plugin-info-dialog.php:1536 msgid "This plugin has not been tested with your current version of WordPress." msgstr "" #: includes/fs-plugin-info-dialog.php:1557 msgid "Paid add-on must be deployed to Freemius." msgstr "" #: includes/fs-plugin-info-dialog.php:1558 msgid "Add-on must be deployed to WordPress.org or Freemius." msgstr "" #: includes/fs-plugin-info-dialog.php:1587 msgid "Latest Version Installed" msgstr "" #: includes/fs-plugin-info-dialog.php:1588 msgid "Latest Free Version Installed" msgstr "" #: includes/fs-plugin-info-dialog.php:1579 msgid "Newer Version (%s) Installed" msgstr "" #: includes/fs-plugin-info-dialog.php:1580 msgid "Newer Free Version (%s) Installed" msgstr "" #: templates/account.php:111, templates/forms/subscription-cancellation.php:96, templates/account/partials/addon.php:31, templates/account/partials/site.php:313 msgid "Downgrading your plan" msgstr "" #: templates/account.php:112, templates/forms/subscription-cancellation.php:97, templates/account/partials/addon.php:32, templates/account/partials/site.php:314 msgid "Cancelling the subscription" msgstr "" #. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' #: templates/account.php:114, templates/forms/subscription-cancellation.php:99, templates/account/partials/addon.php:34, templates/account/partials/site.php:316 msgid "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." msgstr "" #: templates/account.php:115, templates/forms/subscription-cancellation.php:100, templates/account/partials/addon.php:35, templates/account/partials/site.php:317 msgid "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." msgstr "" #: templates/account.php:116, templates/forms/subscription-cancellation.php:106, templates/account/partials/addon.php:36 msgid "Cancelling the trial will immediately block access to all premium features. Are you sure?" msgstr "" #: templates/account.php:117, templates/forms/subscription-cancellation.php:101, templates/account/partials/addon.php:37, templates/account/partials/site.php:318 msgid "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." msgstr "" #: templates/account.php:118, templates/forms/subscription-cancellation.php:102, templates/account/partials/addon.php:38, templates/account/partials/site.php:319 msgid "Once your license expires you can still use the Free version but you will NOT have access to the %s features." msgstr "" #. translators: %s: Plan title (e.g. "Professional") #: templates/account.php:120, templates/account/partials/activate-license-button.php:31, templates/account/partials/addon.php:40 msgid "Activate %s Plan" msgstr "" #. translators: %s: Time period (e.g. Auto renews in "2 months") #: templates/account.php:123, templates/account/partials/addon.php:43, templates/account/partials/site.php:293 msgid "Auto renews in %s" msgstr "" #. translators: %s: Time period (e.g. Expires in "2 months") #: templates/account.php:125, templates/account/partials/addon.php:45, templates/account/partials/site.php:295 msgid "Expires in %s" msgstr "" #: templates/account.php:126 msgctxt "as synchronize license" msgid "Sync License" msgstr "" #: templates/account.php:127, templates/account/partials/addon.php:46 msgid "Cancel Trial" msgstr "" #: templates/account.php:128, templates/account/partials/addon.php:47 msgid "Change Plan" msgstr "" #: templates/account.php:129, templates/account/partials/addon.php:48 msgctxt "verb" msgid "Upgrade" msgstr "" #: templates/account.php:131, templates/account/partials/addon.php:50, templates/account/partials/site.php:320 msgctxt "verb" msgid "Downgrade" msgstr "" #: templates/account.php:133, templates/add-ons.php:246, templates/plugin-info/features.php:72, templates/account/partials/addon.php:52, templates/account/partials/site.php:33 msgid "Free" msgstr "" #: templates/account.php:135, templates/debug.php:471, includes/customizer/class-fs-customizer-upsell-control.php:110, templates/account/partials/addon.php:54 msgctxt "as product pricing plan" msgid "Plan" msgstr "" #: templates/account.php:136 msgid "Bundle Plan" msgstr "" #: templates/account.php:268 msgid "Free Trial" msgstr "" #: templates/account.php:279 msgid "Account Details" msgstr "" #: templates/account.php:288 msgid "Stop Debug" msgstr "" #: templates/account.php:286, templates/forms/data-debug-mode.php:33 msgid "Start Debug" msgstr "" #: templates/account.php:295 msgid "Billing & Invoices" msgstr "" #: templates/account.php:318, templates/account/partials/addon.php:236, templates/account/partials/deactivate-license-button.php:35 msgid "Deactivate License" msgstr "" #: templates/account.php:341, templates/forms/subscription-cancellation.php:125 msgid "Are you sure you want to proceed?" msgstr "" #: templates/account.php:341, templates/account/partials/addon.php:260 msgid "Cancel Subscription" msgstr "" #: templates/account.php:370, templates/account/partials/addon.php:345 msgctxt "as synchronize" msgid "Sync" msgstr "" #: templates/account.php:385, templates/debug.php:634 msgid "Name" msgstr "" #: templates/account.php:391, templates/debug.php:635 msgid "Email" msgstr "" #: templates/account.php:398, templates/debug.php:469, templates/debug.php:684 msgid "User ID" msgstr "" #: templates/account.php:416, templates/account.php:734, templates/account.php:785, templates/debug.php:326, templates/debug.php:463, templates/debug.php:574, templates/debug.php:633, templates/debug.php:682, templates/debug.php:761, templates/account/payments.php:35, templates/debug/logger.php:21 msgid "ID" msgstr "" #: templates/account.php:423 msgid "Site ID" msgstr "" #: templates/account.php:426 msgid "No ID" msgstr "" #: templates/account.php:431, templates/debug.php:333, templates/debug.php:472, templates/debug.php:578, templates/debug.php:637, templates/account/partials/site.php:228 msgid "Public Key" msgstr "" #: templates/account.php:437, templates/debug.php:473, templates/debug.php:579, templates/debug.php:638, templates/account/partials/site.php:241 msgid "Secret Key" msgstr "" #: templates/account.php:440 msgctxt "as secret encryption key missing" msgid "No Secret" msgstr "" #: templates/account.php:494, templates/debug.php:690, templates/account/partials/site.php:262 msgid "License Key" msgstr "" #: templates/account.php:467, templates/account/partials/site.php:122, templates/account/partials/site.php:120 msgid "Trial" msgstr "" #: templates/account.php:525 msgid "Join the Beta program" msgstr "" #: templates/account.php:531 msgid "not verified" msgstr "" #: templates/account.php:600 msgid "Free version" msgstr "" #: templates/account.php:598 msgid "Premium version" msgstr "" #: templates/account.php:540, templates/account/partials/addon.php:195 msgid "Expired" msgstr "" #: templates/account.php:612 msgid "Verify Email" msgstr "" #: templates/account.php:689, templates/forms/user-change.php:27 msgid "Change User" msgstr "" #: templates/account.php:676 msgid "What is your %s?" msgstr "" #: templates/account.php:684, templates/account/billing.php:21 msgctxt "verb" msgid "Edit" msgstr "" #: templates/account.php:660, templates/account.php:923, templates/account/partials/site.php:250, templates/account/partials/site.php:272 msgctxt "verb" msgid "Show" msgstr "" #: templates/account.php:626 msgid "Download %s Version" msgstr "" #: templates/account.php:642 msgid "Download Paid Version" msgstr "" #: templates/account.php:713 msgid "Sites" msgstr "" #: templates/account.php:726 msgid "Search by address" msgstr "" #: templates/account.php:735, templates/debug.php:466 msgid "Address" msgstr "" #: templates/account.php:736 msgid "License" msgstr "" #: templates/account.php:737 msgid "Plan" msgstr "" #: templates/account.php:788 msgctxt "as software license" msgid "License" msgstr "" #: templates/account.php:917 msgctxt "verb" msgid "Hide" msgstr "" #: templates/account.php:939, templates/forms/data-debug-mode.php:31, templates/forms/deactivation/form.php:358, templates/forms/deactivation/form.php:389 msgid "Processing" msgstr "" #: templates/account.php:942 msgid "Get updates for bleeding edge Beta versions of %s." msgstr "" #: templates/account.php:1000 msgid "Cancelling %s" msgstr "" #: templates/account.php:1000, templates/account.php:1017, templates/forms/subscription-cancellation.php:27, templates/forms/deactivation/form.php:178 msgid "trial" msgstr "" #: templates/account.php:1015, templates/forms/deactivation/form.php:195 msgid "Cancelling %s..." msgstr "" #: templates/account.php:1018, templates/forms/subscription-cancellation.php:28, templates/forms/deactivation/form.php:179 msgid "subscription" msgstr "" #: templates/account.php:1032 msgid "Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?" msgstr "" #: templates/account.php:1106 msgid "Disabling white-label mode" msgstr "" #: templates/account.php:1107 msgid "Enabling white-label mode" msgstr "" #: templates/add-ons.php:38 msgid "View details" msgstr "" #: templates/add-ons.php:48 msgid "Add Ons for %s" msgstr "" #: templates/add-ons.php:58 msgid "We couldn't load the add-ons list. It's probably an issue on our side, please try to come back in few minutes." msgstr "" #: templates/add-ons.php:229 msgctxt "active add-on" msgid "Active" msgstr "" #: templates/add-ons.php:230 msgctxt "installed add-on" msgid "Installed" msgstr "" #: templates/admin-notice.php:17, templates/forms/license-activation.php:245, templates/forms/resend-key.php:80 msgctxt "as close a window" msgid "Dismiss" msgstr "" #. translators: %s: Number of seconds #: templates/auto-installation.php:45 msgid "%s sec" msgstr "" #: templates/auto-installation.php:83 msgid "Automatic Installation" msgstr "" #: templates/auto-installation.php:93 msgid "An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now." msgstr "" #: templates/auto-installation.php:104 msgid "The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page." msgstr "" #: templates/auto-installation.php:109 msgid "Cancel Installation" msgstr "" #. translators: %s: name (e.g. Hey John,) #: templates/connect.php:118 msgctxt "greeting" msgid "Hey %s," msgstr "" #. translators: %1$s: plugin name (e.g., "Awesome Plugin"); %2$s: version (e.g., "1.2.3") #: templates/connect.php:185 msgid "Thank you for updating to %1$s v%2$s!" msgstr "" #: templates/connect.php:177 msgid "Never miss an important update" msgstr "" #: templates/connect.php:195 msgid "Allow & Continue" msgstr "" #. translators: %s: module type (plugin, theme, or add-on) #: templates/connect.php:235 msgid "We have introduced this opt-in so you never miss an important update and help us make the %s more compatible with your site and better at doing what you need it to." msgstr "" #: templates/connect.php:237 msgid "Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info." msgstr "" #: templates/connect.php:240 msgid "If you skip this, that's okay! %1$s will still work just fine." msgstr "" #: templates/connect.php:226 msgid "Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info. This will help us make the %s more compatible with your site and better at doing what you need it to." msgstr "" #: templates/connect.php:215 msgid "Welcome to %s! To get started, please enter your license key:" msgstr "" #: templates/connect.php:199 msgid "Re-send activation email" msgstr "" #: templates/connect.php:203 msgid "Thanks %s!" msgstr "" #: templates/connect.php:204 msgid "You should receive a confirmation email for %s to your mailbox at %s. Please make sure you click the button in that email to %s." msgstr "" #: templates/connect.php:270 msgid "We're excited to introduce the Freemius network-level integration." msgstr "" #: templates/connect.php:284 msgid "During the update process we detected %s site(s) in the network that are still pending your attention." msgstr "" #: templates/connect.php:273 msgid "During the update process we detected %d site(s) that are still pending license activation." msgstr "" #: templates/connect.php:275 msgid "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." msgstr "" #: templates/connect.php:277 msgid "%s's paid features" msgstr "" #: templates/connect.php:282 msgid "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." msgstr "" #: templates/connect.php:293, templates/forms/data-debug-mode.php:35, templates/forms/license-activation.php:42 msgid "License key" msgstr "" #: templates/connect.php:296, templates/forms/license-activation.php:22 msgid "Can't find your license key?" msgstr "" #: templates/connect.php:359, templates/connect.php:689, templates/forms/deactivation/retry-skip.php:20 msgctxt "verb" msgid "Skip" msgstr "" #: templates/connect.php:362 msgid "Delegate to Site Admins" msgstr "" #: templates/connect.php:362 msgid "If you click it, this decision will be delegated to the sites administrators." msgstr "" #: templates/connect.php:391 msgid "License issues?" msgstr "" #: templates/connect.php:420 msgid "This will allow %s to" msgstr "" #: templates/connect.php:415 msgid "For delivery of security & feature updates, and license management, %s needs to" msgstr "" #: templates/connect.php:438 msgid "Have a license key?" msgstr "" #: templates/connect.php:435 msgid "Don't have a license key?" msgstr "" #: templates/connect.php:446 msgid "Freemius is our licensing and software updates engine" msgstr "" #: templates/connect.php:449 msgid "Privacy Policy" msgstr "" #: templates/connect.php:454 msgid "Terms of Service" msgstr "" #: templates/connect.php:452 msgid "License Agreement" msgstr "" #: templates/connect.php:875 msgctxt "as in the process of sending an email" msgid "Sending email" msgstr "" #: templates/connect.php:876 msgctxt "as activating plugin" msgid "Activating" msgstr "" #: templates/contact.php:63 msgid "Contact" msgstr "" #: templates/debug.php:17 msgctxt "as turned off" msgid "Off" msgstr "" #: templates/debug.php:18 msgctxt "as turned on" msgid "On" msgstr "" #: templates/debug.php:29, includes/managers/class-fs-debug-manager.php:26 msgid "Freemius Debug" msgstr "" #: templates/debug.php:29 msgid "SDK" msgstr "" #: templates/debug.php:32 msgctxt "as code debugging" msgid "Debugging" msgstr "" #: templates/debug.php:38 msgctxt "timer for auto-disabling debug" msgid "Auto off in:" msgstr "" #: templates/debug.php:117, templates/debug.php:338, templates/debug.php:474, templates/debug.php:639 msgid "Actions" msgstr "" #: templates/debug.php:127 msgid "Are you sure you want to delete all Freemius data?" msgstr "" #: templates/debug.php:127 msgid "Delete All Accounts" msgstr "" #: templates/debug.php:134 msgid "Clear API Cache" msgstr "" #: templates/debug.php:142 msgid "Clear Updates Transients" msgstr "" #: templates/debug.php:151 msgid "Reset Deactivation Snoozing" msgstr "" #: templates/debug.php:159 msgid "Sync Data From Server" msgstr "" #: templates/debug.php:168 msgid "Migrate Options to Network" msgstr "" #: templates/debug.php:173 msgid "Load DB Option" msgstr "" #: templates/debug.php:176 msgid "Set DB Option" msgstr "" #: templates/debug.php:270 msgid "Key" msgstr "" #: templates/debug.php:271 msgid "Value" msgstr "" #: templates/debug.php:287 msgctxt "as software development kit versions" msgid "SDK Versions" msgstr "" #: templates/debug.php:292 msgid "SDK Path" msgstr "" #: templates/debug.php:293, templates/debug.php:332 msgid "Module Path" msgstr "" #: templates/debug.php:294 msgid "Is Active" msgstr "" #: templates/debug.php:322, templates/debug/plugins-themes-sync.php:35 msgid "Plugins" msgstr "" #: templates/debug.php:322, templates/debug/plugins-themes-sync.php:56 msgid "Themes" msgstr "" #: templates/debug.php:327, templates/debug.php:468, templates/debug.php:576, templates/debug/scheduled-crons.php:80 msgid "Slug" msgstr "" #: templates/debug.php:329, templates/debug.php:575 msgid "Title" msgstr "" #: templates/debug.php:330 msgctxt "as application program interface" msgid "API" msgstr "" #: templates/debug.php:331 msgid "Freemius State" msgstr "" #: templates/debug.php:335 msgid "Network Blog" msgstr "" #: templates/debug.php:336 msgid "Network User" msgstr "" #: templates/debug.php:382 msgctxt "as connection was successful" msgid "Connected" msgstr "" #: templates/debug.php:384 msgctxt "as connection blocked" msgid "Blocked" msgstr "" #: templates/debug.php:385 msgctxt "API connectivity state is unknown" msgid "Unknown" msgstr "" #: templates/debug.php:421 msgid "Simulate Trial Promotion" msgstr "" #: templates/debug.php:433 msgid "Simulate Network Upgrade" msgstr "" #. translators: %s: 'plugin' or 'theme' #: templates/debug.php:457 msgid "%s Installs" msgstr "" #: templates/debug.php:459 msgctxt "like websites" msgid "Sites" msgstr "" #: templates/debug.php:465, templates/account/partials/site.php:156 msgid "Blog ID" msgstr "" #: templates/debug.php:470 msgid "License ID" msgstr "" #: templates/debug.php:556, templates/debug.php:662, templates/account/partials/addon.php:440 msgctxt "verb" msgid "Delete" msgstr "" #: templates/debug.php:570 msgid "Add Ons of module %s" msgstr "" #: templates/debug.php:629 msgid "Users" msgstr "" #: templates/debug.php:636 msgid "Verified" msgstr "" #: templates/debug.php:678 msgid "%s Licenses" msgstr "" #: templates/debug.php:683 msgid "Plugin ID" msgstr "" #: templates/debug.php:685 msgid "Plan ID" msgstr "" #: templates/debug.php:686 msgid "Quota" msgstr "" #: templates/debug.php:687 msgid "Activated" msgstr "" #: templates/debug.php:688 msgid "Blocking" msgstr "" #: templates/debug.php:689, templates/debug.php:760, templates/debug/logger.php:22 msgid "Type" msgstr "" #: templates/debug.php:691 msgctxt "as expiration date" msgid "Expiration" msgstr "" #: templates/debug.php:719 msgid "Debug Log" msgstr "" #: templates/debug.php:723 msgid "All Types" msgstr "" #: templates/debug.php:730 msgid "All Requests" msgstr "" #: templates/debug.php:735, templates/debug.php:764, templates/debug/logger.php:25 msgid "File" msgstr "" #: templates/debug.php:736, templates/debug.php:762, templates/debug/logger.php:23 msgid "Function" msgstr "" #: templates/debug.php:737 msgid "Process ID" msgstr "" #: templates/debug.php:738 msgid "Logger" msgstr "" #: templates/debug.php:739, templates/debug.php:763, templates/debug/logger.php:24 msgid "Message" msgstr "" #: templates/debug.php:741 msgid "Filter" msgstr "" #: templates/debug.php:749 msgid "Download" msgstr "" #: templates/debug.php:765, templates/debug/logger.php:26 msgid "Timestamp" msgstr "" #. translators: %s: Page name #: templates/secure-https-header.php:28 msgid "Secure HTTPS %s page, running from an external domain" msgstr "" #: includes/customizer/class-fs-customizer-support-section.php:55, templates/plugin-info/features.php:43 msgid "Support" msgstr "" #: includes/debug/class-fs-debug-bar-panel.php:51, templates/debug/api-calls.php:54, templates/debug/logger.php:62 msgctxt "milliseconds" msgid "ms" msgstr "" #: includes/debug/debug-bar-start.php:41 msgid "Freemius API" msgstr "" #: includes/debug/debug-bar-start.php:42 msgid "Requests" msgstr "" #: includes/managers/class-fs-clone-manager.php:839 msgid "Invalid clone resolution action." msgstr "" #: includes/managers/class-fs-clone-manager.php:1024 msgid "products" msgstr "" #: includes/managers/class-fs-clone-manager.php:1211 msgid "The products below have been placed into safe mode because we noticed that %2$s is an exact copy of %3$s:%1$s" msgstr "" #: includes/managers/class-fs-clone-manager.php:1212 msgid "The products below have been placed into safe mode because we noticed that %2$s is an exact copy of these sites:%3$s%1$s" msgstr "" #: includes/managers/class-fs-clone-manager.php:1205 msgid "%1$s has been placed into safe mode because we noticed that %2$s is an exact copy of %3$s." msgstr "" #: includes/managers/class-fs-clone-manager.php:1238 msgid "the above-mentioned sites" msgstr "" #: includes/managers/class-fs-clone-manager.php:1251 msgid "Is %2$s a duplicate of %4$s?" msgstr "" #: includes/managers/class-fs-clone-manager.php:1252 msgid "Yes, %2$s is a duplicate of %4$s for the purpose of testing, staging, or development." msgstr "" #: includes/managers/class-fs-clone-manager.php:1257 msgid "Long-Term Duplicate" msgstr "" #: includes/managers/class-fs-clone-manager.php:1262 msgid "Duplicate Website" msgstr "" #: includes/managers/class-fs-clone-manager.php:1268 msgid "Is %2$s the new home of %4$s?" msgstr "" #: includes/managers/class-fs-clone-manager.php:1270 msgid "Yes, %%2$s is replacing %%4$s. I would like to migrate my %s from %%4$s to %%2$s." msgstr "" #: includes/managers/class-fs-clone-manager.php:1271, templates/forms/subscription-cancellation.php:52 msgid "license" msgstr "" #: includes/managers/class-fs-clone-manager.php:1271 msgid "data" msgstr "" #: includes/managers/class-fs-clone-manager.php:1277 msgid "Migrate License" msgstr "" #: includes/managers/class-fs-clone-manager.php:1278 msgid "Migrate" msgstr "" #: includes/managers/class-fs-clone-manager.php:1284 msgid "Is %2$s a new website?" msgstr "" #: includes/managers/class-fs-clone-manager.php:1285 msgid "Yes, %2$s is a new and different website that is separate from %4$s." msgstr "" #: includes/managers/class-fs-clone-manager.php:1287 msgid "It requires license activation." msgstr "" #: includes/managers/class-fs-clone-manager.php:1294 msgid "New Website" msgstr "" #: includes/managers/class-fs-clone-manager.php:1319 msgctxt "Clone resolution admin notice products list label" msgid "Products" msgstr "" #: includes/managers/class-fs-clone-manager.php:1408 msgid "You marked this website, %s, as a temporary duplicate of %s." msgstr "" #: includes/managers/class-fs-clone-manager.php:1409 msgid "You marked this website, %s, as a temporary duplicate of these sites" msgstr "" #: includes/managers/class-fs-clone-manager.php:1423 msgid "%s automatic security & feature updates and paid functionality will keep working without interruptions until %s (or when your license expires, whatever comes first)." msgstr "" #: includes/managers/class-fs-clone-manager.php:1426 msgctxt "\"The \", e.g.: \"The plugin\"" msgid "The %s's" msgstr "" #: includes/managers/class-fs-clone-manager.php:1429 msgid "The following products'" msgstr "" #: includes/managers/class-fs-clone-manager.php:1437 msgid "If this is a long term duplicate, to keep automatic updates and paid functionality after %s, please %s." msgstr "" #: includes/managers/class-fs-clone-manager.php:1439 msgid "activate a license here" msgstr "" #: includes/managers/class-fs-permission-manager.php:191 msgid "View Basic Website Info" msgstr "" #: includes/managers/class-fs-permission-manager.php:192 msgid "Homepage URL & title, WP & PHP versions, and site language" msgstr "" #. translators: %s: 'Plugin' or 'Theme' #: includes/managers/class-fs-permission-manager.php:195 msgid "To provide additional functionality that's relevant to your website, avoid WordPress or PHP version incompatibilities that can break your website, and recognize which languages & regions the %s should be translated and tailored to." msgstr "" #: includes/managers/class-fs-permission-manager.php:207 msgid "View Basic %s Info" msgstr "" #. translators: %s: 'Plugin' or 'Theme' #: includes/managers/class-fs-permission-manager.php:210 msgid "Current %s & SDK versions, and if active or uninstalled" msgstr "" #: includes/managers/class-fs-permission-manager.php:261 msgid "View License Essentials" msgstr "" #. translators: %s: 'Plugin' or 'Theme' #: includes/managers/class-fs-permission-manager.php:272 msgid "To let you manage & control where the license is activated and ensure %s security & feature updates are only delivered to websites you authorize." msgstr "" #: includes/managers/class-fs-permission-manager.php:284 msgid "View %s State" msgstr "" #. translators: %s: 'Plugin' or 'Theme' #: includes/managers/class-fs-permission-manager.php:287 msgid "Is active, deactivated, or uninstalled" msgstr "" #: includes/managers/class-fs-permission-manager.php:290 msgid "So you can reuse the license when the %s is no longer active." msgstr "" #: includes/managers/class-fs-permission-manager.php:326 msgid "View Diagnostic Info" msgstr "" #: includes/managers/class-fs-permission-manager.php:326, includes/managers/class-fs-permission-manager.php:363 msgid "optional" msgstr "" #: includes/managers/class-fs-permission-manager.php:327 msgid "WordPress & PHP versions, site language & title" msgstr "" #. translators: %s: 'Plugin' or 'Theme' #: includes/managers/class-fs-permission-manager.php:330 msgid "To avoid breaking your website due to WordPress or PHP version incompatibilities, and recognize which languages & regions the %s should be translated and tailored to." msgstr "" #: includes/managers/class-fs-permission-manager.php:363 msgid "View Plugins & Themes List" msgstr "" #: includes/managers/class-fs-permission-manager.php:364 msgid "Names, slugs, versions, and if active or not" msgstr "" #: includes/managers/class-fs-permission-manager.php:365 msgid "To ensure compatibility and avoid conflicts with your installed plugins and themes." msgstr "" #: includes/managers/class-fs-permission-manager.php:382 msgid "View Basic Profile Info" msgstr "" #: includes/managers/class-fs-permission-manager.php:383 msgid "Your WordPress user's: first & last name, and email address" msgstr "" #: includes/managers/class-fs-permission-manager.php:384 msgid "Never miss important updates, get security warnings before they become public knowledge, and receive notifications about special offers and awesome new features." msgstr "" #: includes/managers/class-fs-permission-manager.php:405 msgid "Newsletter" msgstr "" #: includes/managers/class-fs-permission-manager.php:406 msgid "Updates, announcements, marketing, no spam" msgstr "" #: templates/account/billing.php:22 msgctxt "verb" msgid "Update" msgstr "" #: templates/account/billing.php:33 msgid "Billing" msgstr "" #: templates/account/billing.php:38, templates/account/billing.php:38 msgid "Business name" msgstr "" #: templates/account/billing.php:39, templates/account/billing.php:39 msgid "Tax / VAT ID" msgstr "" #: templates/account/billing.php:42, templates/account/billing.php:42, templates/account/billing.php:43, templates/account/billing.php:43 msgid "Address Line %d" msgstr "" #: templates/account/billing.php:46, templates/account/billing.php:46 msgid "City" msgstr "" #: templates/account/billing.php:46, templates/account/billing.php:46 msgid "Town" msgstr "" #: templates/account/billing.php:47, templates/account/billing.php:47 msgid "ZIP / Postal Code" msgstr "" #: templates/account/billing.php:302 msgid "Country" msgstr "" #: templates/account/billing.php:304 msgid "Select Country" msgstr "" #: templates/account/billing.php:311, templates/account/billing.php:312 msgid "State" msgstr "" #: templates/account/billing.php:311, templates/account/billing.php:312 msgid "Province" msgstr "" #: templates/account/payments.php:29 msgid "Payments" msgstr "" #: templates/account/payments.php:36 msgid "Date" msgstr "" #: templates/account/payments.php:37 msgid "Amount" msgstr "" #: templates/account/payments.php:38, templates/account/payments.php:50 msgid "Invoice" msgstr "" #: templates/checkout/frame.php:77 msgid "Checkout" msgstr "" #: templates/checkout/frame.php:77 msgid "PCI compliant" msgstr "" #: templates/checkout/process-redirect.php:41 msgid "Processing, please wait and do not close or refresh this window..." msgstr "" #: templates/checkout/redirect.php:87 msgid "Redirecting, please click here if you're stuck..." msgstr "" #: templates/connect/permissions-group.php:31, templates/forms/optout.php:26, templates/js/permissions.php:78 msgctxt "verb" msgid "Opt Out" msgstr "" #: templates/connect/permissions-group.php:32, templates/js/permissions.php:77 msgctxt "verb" msgid "Opt In" msgstr "" #: templates/debug/api-calls.php:56 msgid "API" msgstr "" #: templates/debug/api-calls.php:68 msgid "Method" msgstr "" #: templates/debug/api-calls.php:69 msgid "Code" msgstr "" #: templates/debug/api-calls.php:70 msgid "Length" msgstr "" #: templates/debug/api-calls.php:71 msgctxt "as file/folder path" msgid "Path" msgstr "" #: templates/debug/api-calls.php:73 msgid "Body" msgstr "" #: templates/debug/api-calls.php:75 msgid "Result" msgstr "" #: templates/debug/api-calls.php:76 msgid "Start" msgstr "" #: templates/debug/api-calls.php:77 msgid "End" msgstr "" #: templates/debug/logger.php:15 msgid "Log" msgstr "" #. translators: %s: time period (e.g. In "2 hours") #: templates/debug/plugins-themes-sync.php:18, templates/debug/scheduled-crons.php:91 msgid "In %s" msgstr "" #. translators: %s: time period (e.g. "2 hours" ago) #: templates/debug/plugins-themes-sync.php:20, templates/debug/scheduled-crons.php:93 msgid "%s ago" msgstr "" #: templates/debug/plugins-themes-sync.php:21, templates/debug/scheduled-crons.php:74 msgctxt "seconds" msgid "sec" msgstr "" #: templates/debug/plugins-themes-sync.php:23 msgid "Plugins & Themes Sync" msgstr "" #: templates/debug/plugins-themes-sync.php:28 msgid "Total" msgstr "" #: templates/debug/plugins-themes-sync.php:29, templates/debug/scheduled-crons.php:84 msgid "Last" msgstr "" #: templates/debug/scheduled-crons.php:76 msgid "Scheduled Crons" msgstr "" #: templates/debug/scheduled-crons.php:81 msgid "Module" msgstr "" #: templates/debug/scheduled-crons.php:82 msgid "Module Type" msgstr "" #: templates/debug/scheduled-crons.php:83 msgid "Cron Type" msgstr "" #: templates/debug/scheduled-crons.php:85 msgid "Next" msgstr "" #: templates/forms/affiliation.php:86 msgid "Non-expiring" msgstr "" #: templates/forms/affiliation.php:89 msgid "Apply to become an affiliate" msgstr "" #: templates/forms/affiliation.php:137 msgid "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." msgstr "" #: templates/forms/affiliation.php:134 msgid "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." msgstr "" #: templates/forms/affiliation.php:131 msgid "Your affiliation account was temporarily suspended." msgstr "" #: templates/forms/affiliation.php:128 msgid "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." msgstr "" #: templates/forms/affiliation.php:113 msgid "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." msgstr "" #: templates/forms/affiliation.php:150 msgid "Like the %s? Become our ambassador and earn cash ;-)" msgstr "" #: templates/forms/affiliation.php:151 msgid "Refer new customers to our %s and earn %s commission on each successful sale you refer!" msgstr "" #: templates/forms/affiliation.php:154 msgid "Program Summary" msgstr "" #: templates/forms/affiliation.php:156 msgid "%s commission when a customer purchases a new license." msgstr "" #: templates/forms/affiliation.php:158 msgid "Get commission for automated subscription renewals." msgstr "" #: templates/forms/affiliation.php:161 msgid "%s tracking cookie after the first visit to maximize earnings potential." msgstr "" #: templates/forms/affiliation.php:164 msgid "Unlimited commissions." msgstr "" #: templates/forms/affiliation.php:166 msgid "%s minimum payout amount." msgstr "" #: templates/forms/affiliation.php:167 msgid "Payouts are in USD and processed monthly via PayPal." msgstr "" #: templates/forms/affiliation.php:168 msgid "As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days." msgstr "" #: templates/forms/affiliation.php:171 msgid "Affiliate" msgstr "" #: templates/forms/affiliation.php:174, templates/forms/resend-key.php:23 msgid "Email address" msgstr "" #: templates/forms/affiliation.php:178 msgid "Full name" msgstr "" #: templates/forms/affiliation.php:182 msgid "PayPal account email address" msgstr "" #: templates/forms/affiliation.php:186 msgid "Where are you going to promote the %s?" msgstr "" #: templates/forms/affiliation.php:188 msgid "Enter the domain of your website or other websites from where you plan to promote the %s." msgstr "" #: templates/forms/affiliation.php:190 msgid "Add another domain" msgstr "" #: templates/forms/affiliation.php:194 msgid "Extra Domains" msgstr "" #: templates/forms/affiliation.php:195 msgid "Extra domains where you will be marketing the product from." msgstr "" #: templates/forms/affiliation.php:205 msgid "Promotion methods" msgstr "" #: templates/forms/affiliation.php:208 msgid "Social media (Facebook, Twitter, etc.)" msgstr "" #: templates/forms/affiliation.php:212 msgid "Mobile apps" msgstr "" #: templates/forms/affiliation.php:216 msgid "Website, email, and social media statistics (optional)" msgstr "" #: templates/forms/affiliation.php:219 msgid "Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential)." msgstr "" #: templates/forms/affiliation.php:223 msgid "How will you promote us?" msgstr "" #: templates/forms/affiliation.php:226 msgid "Please provide details on how you intend to promote %s (please be as specific as possible)." msgstr "" #: templates/forms/affiliation.php:238, templates/forms/resend-key.php:22, templates/forms/subscription-cancellation.php:142, templates/account/partials/disconnect-button.php:92 msgid "Cancel" msgstr "" #: templates/forms/affiliation.php:240 msgid "Become an affiliate" msgstr "" #: templates/forms/data-debug-mode.php:25 msgid "Please enter the license key to enable the debug mode:" msgstr "" #: templates/forms/data-debug-mode.php:27 msgid "To enter the debug mode, please enter the secret key of the license owner (UserID = %d), which you can find in your \"My Profile\" section of your User Dashboard:" msgstr "" #: templates/forms/data-debug-mode.php:32 msgid "Submit" msgstr "" #: templates/forms/data-debug-mode.php:36 msgid "User key" msgstr "" #: templates/forms/email-address-update.php:32 msgid "Email address update" msgstr "" #: templates/forms/email-address-update.php:33, templates/forms/user-change.php:81 msgctxt "close window" msgid "Dismiss" msgstr "" #: templates/forms/email-address-update.php:38 msgid "Enter the new email address" msgstr "" #: templates/forms/email-address-update.php:42 msgid "Are both %s and %s your email addresses?" msgstr "" #: templates/forms/email-address-update.php:50 msgid "Yes - both addresses are mine" msgstr "" #: templates/forms/email-address-update.php:57 msgid "%s is my client's email address" msgstr "" #: templates/forms/email-address-update.php:66 msgid "%s is my email address" msgstr "" #: templates/forms/email-address-update.php:75 msgid "Would you like to merge %s into %s?" msgstr "" #: templates/forms/email-address-update.php:84 msgid "Yes - move all my data and assets from %s to %s" msgstr "" #: templates/forms/email-address-update.php:94 msgid "No - only move this site's data to %s" msgstr "" #: templates/forms/email-address-update.php:292, templates/forms/email-address-update.php:298 msgid "Update" msgstr "" #: templates/forms/license-activation.php:23 msgid "Please enter the license key that you received in the email right after the purchase:" msgstr "" #: templates/forms/license-activation.php:28 msgid "Update License" msgstr "" #: templates/forms/license-activation.php:34 msgid "The %1$s will be periodically sending essential license data to %2$s to check for security and feature updates, and verify the validity of your license." msgstr "" #: templates/forms/license-activation.php:39 msgid "Agree & Activate License" msgstr "" #: templates/forms/license-activation.php:206 msgid "Associate with the license owner's account." msgstr "" #: templates/forms/optout.php:104 msgid "Keep automatic updates" msgstr "" #: templates/forms/optout.php:44 msgid "Communication" msgstr "" #: templates/forms/optout.php:56 msgid "Stay Connected" msgstr "" #: templates/forms/optout.php:61 msgid "Diagnostic Info" msgstr "" #: templates/forms/optout.php:77 msgid "Keep Sharing" msgstr "" #: templates/forms/optout.php:82 msgid "Extensions" msgstr "" #: templates/forms/premium-versions-upgrade-handler.php:40 msgid "There is a new version of %s available." msgstr "" #: templates/forms/premium-versions-upgrade-handler.php:41 msgid " %s to access version %s security & feature updates, and support." msgstr "" #: templates/forms/premium-versions-upgrade-handler.php:54 msgid "New Version Available" msgstr "" #: templates/forms/premium-versions-upgrade-handler.php:75 msgctxt "close a window" msgid "Dismiss" msgstr "" #: templates/forms/resend-key.php:21 msgid "Send License Key" msgstr "" #: templates/forms/resend-key.php:58 msgid "Enter the email address you've used during the purchase and we will resend you the license key." msgstr "" #: templates/forms/resend-key.php:59 msgid "Enter the email address you've used for the upgrade below and we will resend you the license key." msgstr "" #: templates/forms/subscription-cancellation.php:37 msgid "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site." msgstr "" #: templates/forms/subscription-cancellation.php:47 msgid "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" msgstr "" #: templates/forms/subscription-cancellation.php:57 msgid "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." msgstr "" #: templates/forms/subscription-cancellation.php:68 msgid "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." msgstr "" #: templates/forms/subscription-cancellation.php:103 msgid "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." msgstr "" #: templates/forms/subscription-cancellation.php:136 msgid "Cancel %s?" msgstr "" #: templates/forms/subscription-cancellation.php:143 msgid "Proceed" msgstr "" #: templates/forms/subscription-cancellation.php:191, templates/forms/deactivation/form.php:216 msgid "Cancel %s & Proceed" msgstr "" #. translators: %1$s: Number of trial days; %2$s: Plan name; #: templates/forms/trial-start.php:22 msgid "You are 1-click away from starting your %1$s-day free trial of the %2$s plan." msgstr "" #. translators: %s: Link to freemius.com #: templates/forms/trial-start.php:28 msgid "For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial." msgstr "" #: templates/forms/user-change.php:26 msgid "By changing the user, you agree to transfer the account ownership to:" msgstr "" #: templates/forms/user-change.php:28 msgid "I Agree - Change User" msgstr "" #: templates/forms/user-change.php:30 msgid "Enter email address" msgstr "" #: templates/js/permissions.php:337, templates/js/permissions.php:485 msgid "Saved" msgstr "" #: templates/js/style-premium-theme.php:39 msgid "Premium" msgstr "" #: templates/js/style-premium-theme.php:42 msgid "Beta" msgstr "" #: templates/partials/network-activation.php:36 msgid "Activate license on all pending sites." msgstr "" #: templates/partials/network-activation.php:37 msgid "Apply on all pending sites." msgstr "" #: templates/partials/network-activation.php:32 msgid "Activate license on all sites in the network." msgstr "" #: templates/partials/network-activation.php:33 msgid "Apply on all sites in the network." msgstr "" #: templates/partials/network-activation.php:45, templates/partials/network-activation.php:79 msgid "allow" msgstr "" #: templates/partials/network-activation.php:48, templates/partials/network-activation.php:82 msgid "delegate" msgstr "" #: templates/partials/network-activation.php:52, templates/partials/network-activation.php:86 msgid "skip" msgstr "" #: templates/plugin-info/description.php:67, templates/plugin-info/screenshots.php:26 msgid "Click to view full-size screenshot %d" msgstr "" #: templates/plugin-info/features.php:56 msgid "Unlimited Updates" msgstr "" #: templates/account/partials/activate-license-button.php:46 msgid "Localhost" msgstr "" #: templates/account/partials/activate-license-button.php:50 msgctxt "as 5 licenses left" msgid "%s left" msgstr "" #: templates/account/partials/activate-license-button.php:51 msgid "Last license" msgstr "" #: templates/account/partials/addon.php:200 msgid "No expiration" msgstr "" #: templates/account/partials/addon.php:190 msgid "Cancelled" msgstr "" #. translators: %s is replaced with the website's homepage address. #: templates/account/partials/disconnect-button.php:78 msgid "Disconnecting the website will permanently remove %s from your User Dashboard's account." msgstr "" #: templates/account/partials/disconnect-button.php:74 msgid "By disconnecting the website, previously shared diagnostic data about %1$s will be deleted and no longer visible to %2$s." msgstr "" #. translators: %1$s is replaced by the paid plan name, %2$s is replaced with an anchor link with the text "User Dashboard". #: templates/account/partials/disconnect-button.php:84 msgid "If you wish to cancel your %1$s plan's subscription instead, please navigate to the %2$s and cancel it there." msgstr "" #: templates/account/partials/disconnect-button.php:88 msgid "Are you sure you would like to proceed with the disconnection?" msgstr "" #: templates/account/partials/site.php:190 msgid "Owner Name" msgstr "" #: templates/account/partials/site.php:202 msgid "Owner Email" msgstr "" #: templates/account/partials/site.php:214 msgid "Owner ID" msgstr "" #: templates/account/partials/site.php:288 msgid "Subscription" msgstr "" #: templates/forms/deactivation/contact.php:19 msgid "Sorry for the inconvenience and we are here to help if you give us a chance." msgstr "" #: templates/forms/deactivation/contact.php:22 msgid "Contact Support" msgstr "" #: templates/forms/deactivation/form.php:65 msgid "Anonymous feedback" msgstr "" #: templates/forms/deactivation/form.php:71 msgid "hour" msgstr "" #: templates/forms/deactivation/form.php:76 msgid "hours" msgstr "" #: templates/forms/deactivation/form.php:81, templates/forms/deactivation/form.php:86 msgid "days" msgstr "" #: templates/forms/deactivation/form.php:106 msgid "Deactivate" msgstr "" #: templates/forms/deactivation/form.php:108 msgid "Activate %s" msgstr "" #: templates/forms/deactivation/form.php:111 msgid "Submit & %s" msgstr "" #: templates/forms/deactivation/form.php:130 msgid "Quick Feedback" msgstr "" #: templates/forms/deactivation/form.php:134 msgid "If you have a moment, please let us know why you are %s" msgstr "" #: templates/forms/deactivation/form.php:134 msgid "deactivating" msgstr "" #: templates/forms/deactivation/form.php:134 msgid "switching" msgstr "" #: templates/forms/deactivation/form.php:448 msgid "Kindly tell us the reason so we can improve." msgstr "" #: templates/forms/deactivation/form.php:478 msgid "Snooze & %s" msgstr "" #: templates/forms/deactivation/form.php:638 msgid "Yes - %s" msgstr "" #: templates/forms/deactivation/form.php:645 msgid "Skip & %s" msgstr "" #: templates/forms/deactivation/retry-skip.php:21 msgid "Click here to use the plugin anonymously" msgstr "" #: templates/forms/deactivation/retry-skip.php:23 msgid "You might have missed it, but you don't have to share any data and can just %s the opt-in." msgstr "" PK!"Clibraries/evalmath.class.phpnu[ */ public array $variables = array(); /** * User-defined functions. * * @since 1.0.0 * @var array */ protected array $functions = array(); /** * Constants. * * @since 1.0.0 * @var array */ protected array $constants = array(); /** * Built-in functions. * * @since 1.0.0 * @var string[] */ protected array $builtin_functions = array( 'sin', 'sinh', 'arcsin', 'asin', 'arcsinh', 'asinh', 'cos', 'cosh', 'arccos', 'acos', 'arccosh', 'acosh', 'tan', 'tanh', 'arctan', 'atan', 'arctanh', 'atanh', 'sqrt', 'abs', 'ln', 'log10', 'exp', 'floor', 'ceil', ); /** * Emulated functions. * * @since 1.0.0 * @var array */ protected array $calc_functions = array( 'average' => array( -1 ), 'mean' => array( -1 ), 'median' => array( -1 ), 'mode' => array( -1 ), 'range' => array( -1 ), 'max' => array( -1 ), 'min' => array( -1 ), 'mod' => array( 2 ), 'pi' => array( 0 ), 'power' => array( 2 ), 'log' => array( 1, 2 ), 'round' => array( 1, 2 ), 'number_format' => array( 1, 2 ), 'number_format_eu' => array( 1, 2 ), 'sum' => array( -1 ), 'counta' => array( -1 ), 'product' => array( -1 ), 'rand_int' => array( 2 ), 'rand_float' => array( 0 ), 'arctan2' => array( 2 ), 'atan2' => array( 2 ), 'if' => array( 3 ), 'not' => array( 1 ), 'and' => array( -1 ), 'or' => array( -1 ), ); /** * Class constructor. * * @since 1.0.0 */ public function __construct() { // Set default constants. $this->variables['pi'] = pi(); $this->variables['e'] = exp( 1 ); } /** * Evaluate a math expression without checking it for variable or function assignments. * * @since 1.0.0 * * @param string $expression The expression that shall be evaluated. * @return string|false Evaluated expression or false on error. */ public function evaluate( $expression ) /* : string|false */ { return $this->pfx( $this->nfx( $expression ) ); } /** * Evaluate a math expression or formula, and check it for variable and function assignments. * * @since 1.0.0 * * @param string $expression The expression that shall be evaluated. * @return string|bool Evaluated expression, true on successful function assignment, or false on error. */ public function assign_and_evaluate( $expression ) /* : string|bool */ { $this->last_error = ''; $expression = trim( $expression ); $expression = rtrim( $expression, ';' ); // Is the expression a variable assignment? if ( 1 === preg_match( '/^\s*(' . self::$name_pattern . ')\s*=\s*(.+)$/', $expression, $matches ) ) { // Make sure we're not assigning to a constant. if ( in_array( $matches[1], $this->constants, true ) ) { return $this->raise_error( 'cannot_assign_to_constant', $matches[1] ); } // Evaluate the assignment. $tmp = $this->pfx( $this->nfx( $matches[2] ) ); if ( false === $tmp ) { return false; } // If it could be evaluated, add it to the variable array, ... $this->variables[ $matches[1] ] = $tmp; // ... and return the resulting value. return $tmp; // Is the expression a function assignment? } elseif ( 1 === preg_match( '/^\s*(' . self::$name_pattern . ')\s*\(\s*(' . self::$name_pattern . '(?:\s*,\s*' . self::$name_pattern . ')*)\s*\)\s*=\s*(.+)$/', $expression, $matches ) ) { // Get the function name. $function_name = $matches[1]; // Make sure it isn't a built-in function -- we can't redefine those. if ( in_array( $matches[1], $this->builtin_functions, true ) ) { return $this->raise_error( 'cannot_redefine_builtin_function', $matches[1] ); } // Get the function arguments after removing all whitespace. $matches[2] = str_replace( array( "\n", "\r", "\t", ' ' ), '', $matches[2] ); $args = explode( ',', $matches[2] ); // Convert the function definition to postfix notation. $stack = $this->nfx( $matches[3] ); if ( false === $stack ) { return false; } // Freeze the state of the non-argument variables. $stack_count = count( $stack ); for ( $i = 0; $i < $stack_count; $i++ ) { $token = $stack[ $i ]; if ( 1 === preg_match( '/^' . self::$name_pattern . '$/', $token ) && ! in_array( $token, $args, true ) ) { if ( array_key_exists( $token, $this->variables ) ) { $stack[ $i ] = $this->variables[ $token ]; } else { return $this->raise_error( 'undefined_variable_in_function_definition', $token ); } } } $this->functions[ $function_name ] = array( 'args' => $args, 'func' => $stack ); return true; // No variable or function assignment, so straight-up evaluation. } else { return $this->evaluate( $expression ); } } /** * Return all user-defined variables and values. * * @since 1.0.0 * * @return array User-defined variables and values. */ public function variables(): array { return $this->variables; } /** * Return all user-defined functions with their arguments. * * @since 1.0.0 * * @return array User-defined functions. */ public function functions(): array { $output = array(); foreach ( $this->functions as $name => $data ) { $output[] = $name . '( ' . implode( ', ', $data['args'] ) . ' )'; } return $output; } /* * Internal methods. */ /** * Convert infix to postfix notation. * * @since 1.0.0 * * @param string $expression Math expression that shall be converted. * @return mixed[]|false Converted expression or false on error. */ protected function nfx( $expression ) /* : mixed[]|false */ { $index = 0; $stack = new EvalMath_Stack(); $output = array(); // postfix form of expression, to be passed to pfx(). $expression = trim( strtolower( $expression ) ); $ops = array( '+', '-', '*', '/', '^', '_', '>', '<', '=', '%' ); $ops_r = array( '+' => 0, '-' => 0, '*' => 0, '/' => 0, '^' => 1, '>' => 0, '<' => 0, '=' => 0, '%' => 0 ); // Right-associative operator? $ops_p = array( '+' => 0, '-' => 0, '*' => 1, '/' => 1, '_' => 1, '^' => 2, '>' => 0, '<' => 0, '=' => 0, '%' => 1 ); // Operator precedence. // We use this in syntax-checking the expression and determining when a - (minus) is a negation. $expecting_operator = false; // Make sure the characters are all good. if ( 1 === preg_match( '/[^\%\w\s+*^\/()\.,-<>=]/', $expression, $matches ) ) { return $this->raise_error( 'illegal_character_general', $matches[0] ); } // Infinite Loop for the conversion. while ( true ) { // Get the first character at the current index. $op = substr( $expression, $index, 1 ); // Find out if we're currently at the beginning of a number/variable/function/parenthesis/operand. $ex = preg_match( '/^(' . self::$name_pattern . '\(?|\d+(?:\.\d*)?(?:(e[+-]?)\d*)?|\.\d+|\()/', substr( $expression, $index ), $match ); // Is it a negation instead of a minus (in a subtraction)? if ( '-' === $op && ! $expecting_operator ) { // Put a negation on the stack. $stack->push( '_' ); ++$index; } elseif ( '_' === $op ) { // We have to explicitly deny underscores (as they mean negation), because they are legal on the stack. return $this->raise_error( 'illegal_character_underscore' ); // Are we putting an operator on the stack? } elseif ( ( in_array( $op, $ops, true ) || $ex ) && $expecting_operator ) { // Are we expecting an operator but have a number/variable/function/opening parethesis? if ( $ex ) { // It's an implicit multiplication. $op = '*'; --$index; } // Heart of the algorithm: . // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition while ( $stack->count > 0 && ( $o2 = $stack->last() ) && in_array( $o2, $ops, true ) && ( $ops_r[ $op ] ? $ops_p[ $op ] < $ops_p[ $o2 ] : $ops_p[ $op ] <= $ops_p[ $o2 ] ) ) { // Pop stuff off the stack into the output. $output[] = $stack->pop(); } // Many thanks: https://en.wikipedia.org/wiki/Reverse_Polish_notation . $stack->push( $op ); // Finally put OUR operator onto the stack. ++$index; $expecting_operator = false; // Ready to close a parenthesis? } elseif ( ')' === $op && $expecting_operator ) { // Pop off the stack back to the last (. // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition while ( '(' !== ( $o2 = $stack->pop() ) ) { if ( is_null( $o2 ) ) { return $this->raise_error( 'unexpected_closing_bracket' ); } else { $output[] = $o2; } } // Did we just close a function? if ( 1 === preg_match( '/^(' . self::$name_pattern . ')\($/', (string) $stack->last( 2 ), $matches ) ) { // Get the function name. $function_name = $matches[1]; // See how many arguments there were (cleverly stored on the stack, thank you). $arg_count = $stack->pop(); $stack->pop(); // $fn // Send function to output. $output[] = array( 'function_name' => $function_name, 'arg_count' => $arg_count ); // Check the argument count, depending on what type of function we have. if ( in_array( $function_name, $this->builtin_functions, true ) ) { // Built-in functions. if ( $arg_count > 1 ) { // @phpstan-ignore greater.alwaysFalse $error_data = array( 'expected' => 1, 'given' => $arg_count ); return $this->raise_error( 'wrong_number_of_arguments', $error_data ); } } elseif ( array_key_exists( $function_name, $this->calc_functions ) ) { // Calc-emulation functions. $counts = $this->calc_functions[ $function_name ]; // @phpstan-ignore greater.alwaysFalse, booleanAnd.alwaysFalse if ( in_array( -1, $counts, true ) && $arg_count > 0 ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedIf // Everything is fine, we expected an indefinite number arguments and got some. } elseif ( ! in_array( $arg_count, $counts, true ) ) { // @phpstan-ignore function.impossibleType $error_data = array( 'expected' => implode( '/', $this->calc_functions[ $function_name ] ), 'given' => $arg_count ); return $this->raise_error( 'wrong_number_of_arguments', $error_data ); } } elseif ( array_key_exists( $function_name, $this->functions ) ) { // User-defined functions. if ( count( $this->functions[ $function_name ]['args'] ) !== $arg_count ) { // @phpstan-ignore notIdentical.alwaysTrue $error_data = array( 'expected' => count( $this->functions[ $function_name ]['args'] ), 'given' => $arg_count ); return $this->raise_error( 'wrong_number_of_arguments', $error_data ); } } else { // Did we somehow push a non-function on the stack? This should never happen. return $this->raise_error( 'internal_error' ); } } ++$index; // Did we just finish a function argument? } elseif ( ',' === $op && $expecting_operator ) { // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition while ( '(' !== ( $o2 = $stack->pop() ) ) { if ( is_null( $o2 ) ) { // Oops, never had a (. return $this->raise_error( 'unexpected_comma' ); } else { // Pop the argument expression stuff and push onto the output. $output[] = $o2; } } // Make sure there was a function. if ( 0 === preg_match( '/^(' . self::$name_pattern . ')\($/', (string) $stack->last( 2 ), $matches ) ) { return $this->raise_error( 'unexpected_comma' ); } // Increment the argument count. $stack->push( $stack->pop() + 1 ); // @phpstan-ignore binaryOp.invalid // Put the ( back on, we'll need to pop back to it again. $stack->push( '(' ); ++$index; $expecting_operator = false; } elseif ( '(' === $op && ! $expecting_operator ) { $stack->push( '(' ); // That was easy. ++$index; // Do we now have a function/variable/number? } elseif ( $ex && ! $expecting_operator ) { $expecting_operator = true; $value = $match[1]; // May be a function, or variable with implicit multiplication against parentheses... if ( 1 === preg_match( '/^(' . self::$name_pattern . ')\($/', $value, $matches ) ) { // Is it a function? if ( in_array( $matches[1], $this->builtin_functions, true ) || array_key_exists( $matches[1], $this->functions ) || array_key_exists( $matches[1], $this->calc_functions ) ) { $stack->push( $value ); $stack->push( 1 ); $stack->push( '(' ); $expecting_operator = false; // It's a variable with implicit multiplication. } else { $value = $matches[1]; $output[] = $value; } } else { // It's a plain old variable or number. $output[] = $value; } $index += strlen( $value ); } elseif ( ')' === $op ) { // It could be only custom function with no arguments or a general error. if ( '(' !== $stack->last() || 1 !== $stack->last( 2 ) ) { return $this->raise_error( 'unexpected_closing_bracket' ); } // Did we just close a function? if ( 1 === preg_match( '/^(' . self::$name_pattern . ')\($/', (string) $stack->last( 3 ), $matches ) ) { $stack->pop(); // ( $stack->pop(); // 1 $stack->pop(); // $fn // Get the function name. $function_name = $matches[1]; if ( isset( $this->calc_functions[ $function_name ] ) ) { // Custom calc-emulation function. $counts = $this->calc_functions[ $function_name ]; } else { // Default count for built-in functions. $counts = array( 1 ); } if ( ! in_array( 0, $counts, true ) ) { $error_data = array( 'expected' => $counts, 'given' => 0 ); return $this->raise_error( 'wrong_number_of_arguments', $error_data ); } // Send function to output. $output[] = array( 'function_name' => $function_name, 'arg_count' => 0 ); ++$index; $expecting_operator = true; } else { return $this->raise_error( 'unexpected_closing_bracket' ); } // Miscellaneous error checking. } elseif ( in_array( $op, $ops, true ) && ! $expecting_operator ) { return $this->raise_error( 'unexpected_operator', $op ); // I don't even want to know what you did to get here. } else { return $this->raise_error( 'an_unexpected_error_occurred' ); } if ( strlen( $expression ) === $index ) { // Did we end with an operator? Bad. if ( in_array( $op, $ops, true ) ) { return $this->raise_error( 'operator_lacks_operand', $op ); } else { break; } } // Step the index past whitespace (pretty much turns whitespace into implicit multiplication if no operator is there). while ( ' ' === substr( $expression, $index, 1 ) ) { ++$index; } } // while ( true ) // Pop everything off the stack and push onto output. // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition while ( ! is_null( $op = $stack->pop() ) ) { if ( '(' === $op ) { // If there are (s on the stack, ()s were unbalanced. return $this->raise_error( 'expecting_a_closing_bracket' ); } $output[] = $op; } return $output; } /** * Evaluate postfix notation. * * @since 1.0.0 * * @param array|false $tokens [description]. * @param array $variables Optional. [description]. * @return mixed [description]. */ protected function pfx( $tokens, array $variables = array() ) /* : mixed */ { if ( false === $tokens ) { return false; } $stack = new EvalMath_Stack(); foreach ( $tokens as $token ) { // If the token is a function, pop arguments off the stack, hand them to the function, and push the result back on. if ( is_array( $token ) ) { // it's a function! $function_name = $token['function_name']; $count = $token['arg_count']; if ( in_array( $function_name, $this->builtin_functions, true ) ) { // Built-in function. $op1 = $stack->pop(); if ( is_null( $op1 ) ) { return $this->raise_error( 'internal_error' ); } // For the "arc" trigonometric synonyms. $function_name = preg_replace( '/^arc/', 'a', $function_name ); // Rewrite "ln" (only allows one argument) to "log" (natural logarithm). if ( 'ln' === $function_name ) { $function_name = 'log'; } // Perfectly safe eval(). // phpcs:ignore Squiz.PHP.Eval.Discouraged eval( '$stack->push( ' . $function_name . '( $op1 ) );' ); } elseif ( array_key_exists( $function_name, $this->calc_functions ) ) { // Calc-emulation function. // Get function arguments. $args = array(); for ( $i = $count - 1; $i >= 0; $i-- ) { $arg = $stack->pop(); if ( is_null( $arg ) ) { return $this->raise_error( 'internal_error' ); } else { $args[] = $arg; } } // Rewrite some functions to their synonyms. if ( 'if' === $function_name ) { $function_name = 'func_if'; } elseif ( 'not' === $function_name ) { $function_name = 'func_not'; } elseif ( 'and' === $function_name ) { $function_name = 'func_and'; } elseif ( 'or' === $function_name ) { $function_name = 'func_or'; } elseif ( 'mean' === $function_name ) { $function_name = 'average'; } elseif ( 'arctan2' === $function_name ) { $function_name = 'atan2'; } $result = EvalMath_Functions::$function_name( ...array_reverse( $args ) ); if ( false === $result ) { return $this->raise_error( 'internal_error' ); } $stack->push( $result ); } elseif ( array_key_exists( $function_name, $this->functions ) ) { // User-defined function. // Get function arguments. $args = array(); for ( $i = count( $this->functions[ $function_name ]['args'] ) - 1; $i >= 0; $i-- ) { $arg = $stack->pop(); if ( is_null( $arg ) ) { return $this->raise_error( 'internal_error' ); } else { $args[ $this->functions[ $function_name ]['args'][ $i ] ] = $arg; } } // Recursion. $stack->push( $this->pfx( $this->functions[ $function_name ]['func'], $args ) ); } } elseif ( in_array( $token, array( '+', '-', '*', '/', '^', '>', '<', '=', '%' ), true ) ) { // If the token is a binary operator, pop two values off the stack, do the operation, and push the result back on. $op2 = $stack->pop(); if ( is_null( $op2 ) ) { return $this->raise_error( 'internal_error' ); } $op1 = $stack->pop(); if ( is_null( $op1 ) ) { return $this->raise_error( 'internal_error' ); } switch ( $token ) { case '+': $stack->push( $op1 + $op2 ); break; case '-': $stack->push( $op1 - $op2 ); break; case '*': $stack->push( $op1 * $op2 ); break; case '/': if ( 0 === $op2 || '0' === $op2 ) { return $this->raise_error( 'division_by_zero' ); } $stack->push( $op1 / $op2 ); break; case '^': $stack->push( pow( $op1, $op2 ) ); break; case '>': $stack->push( (int) ( $op1 > $op2 ) ); break; case '<': $stack->push( (int) ( $op1 < $op2 ) ); break; case '=': // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison,Universal.Operators.StrictComparisons.LooseEqual $stack->push( (int) ( $op1 == $op2 ) ); // Don't use === as the variable type can differ (int/double/bool). break; case '%': $stack->push( $op1 % $op2 ); break; } } elseif ( '_' === $token ) { // If the token is a unary operator, pop one value off the stack, do the operation, and push it back on. $stack->push( -1 * $stack->pop() ); } elseif ( is_numeric( $token ) ) { // If the token is a number, push it on the stack. $stack->push( $token ); } elseif ( array_key_exists( $token, $this->variables ) ) { // If the token is a variable, push it on the stack. $stack->push( $this->variables[ $token ] ); } elseif ( array_key_exists( $token, $variables ) ) { // If the token is a variable, push it on the stack. $stack->push( $variables[ $token ] ); } else { return $this->raise_error( 'undefined_variable', $token ); } } // When we're out of tokens, the stack should have a single element, the final result. if ( 1 !== $stack->count ) { return $this->raise_error( 'internal_error' ); } return $stack->pop(); } /** * Raise an error. * * @since 1.0.0 * * @param string $message Error message. * @param mixed[]|string $error_data Optional. Additional error data. * @return false False, to stop evaluation. */ protected function raise_error( $message, $error_data = null ): bool { $this->last_error = $this->get_error_string( $message, $error_data ); return false; } /** * Get a translated string for an error message. * * @since 1.0.0 * * @link https://github.com/moodle/moodle/blob/13264f35057d2f37374ec3e0e8ad4070f4676bd7/lang/en/mathslib.php * @link https://github.com/moodle/moodle/blob/8e54ce9717c19f768b95f4332f70e3180ffafc46/lib/moodlelib.php#L6323 * * @param string $identifier Identifier of the string. * @param mixed[]|string $error_data Optional. Additional error data. * @return string Translated string. */ protected function get_error_string( $identifier, $error_data = null ): string { $strings = array(); $strings['an_unexpected_error_occurred'] = 'an unexpected error occurred'; $strings['cannot_assign_to_constant'] = 'cannot assign to constant \'{$error_data}\''; $strings['cannot_redefine_builtin_function'] = 'cannot redefine built-in function \'{$error_data}()\''; $strings['division_by_zero'] = 'division by zero'; $strings['expecting_a_closing_bracket'] = 'expecting a closing bracket'; $strings['illegal_character_general'] = 'illegal character \'{$error_data}\''; $strings['illegal_character_underscore'] = 'illegal character \'_\''; $strings['internal_error'] = 'internal error'; $strings['operator_lacks_operand'] = 'operator \'{$error_data}\' lacks operand'; $strings['undefined_variable'] = 'undefined variable \'{$error_data}\''; $strings['undefined_variable_in_function_definition'] = 'undefined variable \'{$error_data}\' in function definition'; $strings['unexpected_closing_bracket'] = 'unexpected closing bracket'; $strings['unexpected_comma'] = 'unexpected comma'; $strings['unexpected_operator'] = 'unexpected operator \'{$error_data}\''; $strings['wrong_number_of_arguments'] = 'wrong number of arguments ({$error_data->given} given, {$error_data->expected} expected)'; $a_string = $strings[ $identifier ]; if ( null !== $error_data ) { if ( is_array( $error_data ) ) { $search = array(); $replace = array(); foreach ( $error_data as $key => $value ) { if ( is_int( $key ) ) { // We do not support numeric keys! continue; } if ( is_object( $value ) || is_array( $value ) ) { $value = (array) $value; if ( count( $value ) > 1 ) { $value = implode( ' or ', $value ); } else { $value = (string) $value[0]; if ( '-1' === $value ) { $value = 'at least 1'; } } } $search[] = '{$error_data->' . $key . '}'; $replace[] = (string) $value; } if ( $search ) { $a_string = str_replace( $search, $replace, $a_string ); } } else { $a_string = str_replace( '{$error_data}', (string) $error_data, $a_string ); } } return $a_string; } } // class EvalMath /** * Stack for the postfix/infix conversion of math expressions. * * @package TablePress * @subpackage Formulas * @since 1.0.0 */ class EvalMath_Stack { // phpcs:ignore Generic.Files.OneObjectStructurePerFile.MultipleFound,Generic.Classes.OpeningBraceSameLine.ContentAfterBrace /** * The stack. * * @since 1.0.0 * @var mixed[] */ protected array $stack = array(); /** * Number of items on the stack. * * @since 1.0.0 */ public int $count = 0; /** * Push an item onto the stack. * * @since 1.0.0 * * @param mixed $value The item that is pushed onto the stack. */ public function push( $value ): void { $this->stack[ $this->count ] = $value; ++$this->count; } /** * Pop an item from the top of the stack. * * @since 1.0.0 * * @return mixed|null The item that is popped from the stack. */ public function pop() /* : mixed|null */ { if ( $this->count > 0 ) { --$this->count; return $this->stack[ $this->count ]; } return null; } /** * Pop an item from the end of the stack. * * @since 1.0.0 * * @param int $n Count from the end of the stack. * @return mixed|null The item that is popped from the stack. */ public function last( $n = 1 ) /* : mixed|null */ { if ( ( $this->count - $n ) >= 0 ) { return $this->stack[ $this->count - $n ]; } return null; } } // class EvalMath_Stack /** * Common math functions, prepared for usage in EvalMath. * * @package TablePress * @subpackage EvalMath * @since 1.0.0 */ class EvalMath_Functions { // phpcs:ignore Generic.Files.OneObjectStructurePerFile.MultipleFound,Generic.Classes.OpeningBraceSameLine.ContentAfterBrace /** * Seed for the generation of random numbers. * * @since 1.0.0 */ protected static ?string $random_seed = null; /** * Choose from two values based on an if-condition. * * "if" is not a valid function name, which is why it's prefixed with "func_". * * @since 1.0.0 * * @param double|int $condition Condition. * @param double|int $statement Return value if the condition is true. * @param double|int $alternative Return value if the condition is false. * @return double|int Result of the if check. */ public static function func_if( $condition, $statement, $alternative ) /* : float|int */ { return ( (bool) $condition ? $statement : $alternative ); } /** * Return the negation (boolean "not") of a value. * * Similar to "func_if", the function name is prefixed with "func_", although it wouldn't be necessary. * * @since 1.0.0 * * @param double|int $value Value to be negated. * @return int Negated value (0 for false, 1 for true). */ public static function func_not( $value ): int { return (int) ! (bool) $value; } /** * Calculate the conjunction (boolean "and") of some values. * * "and" is not a valid function name, which is why it's prefixed with "func_". * * @since 1.0.0 * * @param double|int ...$args Values for which the conjunction shall be calculated. * @return int Conjunction of the passed arguments. */ public static function func_and( ...$args ): int { foreach ( $args as $value ) { if ( ! $value ) { return 0; } } return 1; } /** * Calculate the disjunction (boolean "or") of some values. * * "or" is not a valid function name, which is why it's prefixed with "func_". * * @since 1.0.0 * * @param double|int ...$args Values for which the disjunction shall be calculated. * @return int Disjunction of the passed arguments. */ public static function func_or( ...$args ): int { foreach ( $args as $value ) { if ( $value ) { return 1; } } return 0; } /** * Return the (rounded) value of Pi. * * @since 1.0.0 * * @return double Rounded value of Pi. */ public static function pi(): float { return pi(); } /** * Calculate the sum of the arguments. * * @since 1.0.0 * * @param double|int ...$args Values for which the sum shall be calculated. * @return double|int Sum of the passed arguments. */ public static function sum( ...$args ) /* : float|int */ { return array_sum( $args ); } /** * Count the number of non-empty arguments. * * @since 1.10.0 * * @param double|int ...$args Values for which the number of non-empty elements shall be counted. * @return int Counted number of non-empty elements in the passed values. */ public static function counta( ...$args ): int { return count( array_filter( $args ) ); } /** * Calculate the product of the arguments. * * @since 1.0.0 * * @param double|int ...$args Values for which the product shall be calculated. * @return double|int Product of the passed arguments. */ public static function product( ...$args ) /* : float|int */ { return array_product( $args ); } /** * Calculate the average/mean value of the arguments. * * @since 1.0.0 * * @param double|int ...$args Values for which the average shall be calculated. * @return double|int Average value of the passed arguments. */ public static function average( ...$args ) /* : float|int */ { // Catch division by zero. if ( 0 === count( $args ) ) { return 0; } return array_sum( $args ) / count( $args ); } /** * Calculate the median of the arguments. * * For even counts of arguments, the upper median is returned. * * @since 1.0.0 * * @param array ...$args Values for which the median shall be calculated. * @return double|int Median of the passed arguments. */ public static function median( array ...$args ) /* : float|int */ { sort( $args ); $middle = intdiv( count( $args ), 2 ); // Upper median for even counts. return $args[ $middle ]; // @phpstan-ignore return.type } /** * Calculate the mode of the arguments. * * @since 1.0.0 * * @param array ...$args Values for which the mode shall be calculated. * @return double|int Mode of the passed arguments. */ public static function mode( ...$args ) /* : float|int */ { $values = array_count_values( $args ); // @phpstan-ignore argument.type asort( $values ); return array_key_last( $values ); // @phpstan-ignore return.type } /** * Calculate the range of the arguments. * * @since 1.0.0 * * @param double|int ...$args Values for which the range shall be calculated. * @return double|int Range of the passed arguments. */ public static function range( ...$args ) /* : float|int */ { sort( $args ); return end( $args ) - reset( $args ); } /** * Find the maximum value of the arguments. * * @since 1.0.0 * * @param double|int ...$args Values for which the maximum value shall be found. * @return double|int Maximum value of the passed arguments. */ public static function max( ...$args ) /* : float|int */ { return max( $args ); // @phpstan-ignore argument.type } /** * Find the minimum value of the arguments. * * @since 1.0.0 * * @param double|int ...$args Values for which the minimum value shall be found. * @return double|int Minimum value of the passed arguments. */ public static function min( ...$args ) /* : float|int */ { return min( $args ); // @phpstan-ignore argument.type } /** * Calculate the remainder of a division of two numbers. * * @since 1.0.0 * * @param double|int $op1 First number (dividend). * @param double|int $op2 Second number (divisor). * @return int Remainder of the division (dividend / divisor). */ public static function mod( $op1, $op2 ): int { return $op1 % $op2; } /** * Calculate the power of a base and an exponent. * * @since 1.0.0 * * @param double|int $base Base. * @param double|int $exponent Exponent. * @return double|int Power base^exponent. */ public static function power( $base, $exponent ) /* : float|int */ { return pow( $base, $exponent ); } /** * Calculate the logarithm of a number to a base. * * @since 1.0.0 * * @param double|int $number Number. * @param double|int $base Optional. Base for the logarithm. Default e (for the natural logarithm). * @return double Logarithm of the number to the base. */ public static function log( $number, $base = M_E ): float { return log( $number, $base ); } /** * Calculate the arc tangent of two variables. * * The signs of the numbers determine the quadrant of the result. * * @since 1.0.0 * * @param double|int $op1 First number. * @param double|int $op2 Second number. * @return double Arc tangent of two numbers, similar to arc tangent of $op1/op$ except for the sign. */ public static function atan2( $op1, $op2 ): float { return atan2( $op1, $op2 ); } /** * Round a number to a given precision. * * @since 1.0.0 * * @param double|int $value Number to be rounded. * @param int $decimals Optional. Number of decimals after the comma after the rounding. * @return double Rounded number. */ public static function round( $value, $decimals = 0 ): float { return round( $value, $decimals ); } /** * Format a number with the . as the decimal separator and the , as the thousand separator, rounded to a precision. * * The is the common number format in English-language regions. * * @since 1.0.0 * * @param double|int $value Number to be rounded and formatted. * @param int $decimals Optional. Number of decimals after the decimal separator after the rounding. * @return string Formatted number. */ public static function number_format( $value, $decimals = 0 ): string { return number_format( $value, $decimals, '.', ',' ); } /** * Format a number with the , as the decimal separator and the space as the thousand separator, rounded to a precision. * * The is the common number format in non-English-language regions, mainly in Europe. * * @since 1.0.0 * * @param double|int $value Number to be rounded and formatted. * @param int $decimals Optional. Number of decimals after the decimal separator after the rounding. * @return string Formatted number. */ public static function number_format_eu( $value, $decimals = 0 ): string { return number_format( $value, $decimals, ',', ' ' ); } /** * Set the seed for the generation of random numbers. * * @since 1.0.0 * * @param string $random_seed The seed. */ protected static function _set_random_seed( $random_seed ): void { self::$random_seed = $random_seed; } /** * Get the seed for the generation of random numbers. * * @since 1.0.0 * * @return string The seed. */ protected static function _get_random_seed(): string { if ( is_null( self::$random_seed ) ) { return microtime(); } return self::$random_seed; } /** * Get a random integer from a range. * * @since 1.0.0 * * @param int $min Minimum value for the range. * @param int $max Maximum value for the range. * @return int Random integer from the range [$min, $max]. */ public static function rand_int( $min, $max ): int { // Swap min and max value if min is bigger than max. if ( $min > $max ) { $tmp = $max; $max = $min; $min = $tmp; unset( $tmp ); } $number_characters = (int) ceil( log( $max + 1 - $min, 16 ) ); $md5string = md5( self::_get_random_seed() ); $offset = 0; do { while ( ( $offset + $number_characters ) > strlen( $md5string ) ) { // phpcs:ignore Squiz.PHP.DisallowSizeFunctionsInLoops.Found $md5string .= md5( $md5string ); } $random_number = (int) hexdec( substr( $md5string, $offset, $number_characters ) ); $offset += $number_characters; } while ( ( $min + $random_number ) > $max ); return $min + $random_number; } /** * Get a random double value from a range [0, 1]. * * @since 1.0.0 * * @return double Random number from the range [0, 1]. */ public static function rand_float(): float { $random_values = unpack( 'v', md5( self::_get_random_seed(), true ) ); return array_shift( $random_values ) / 65536; // @phpstan-ignore argument.type } } // class EvalMath_Functions PK!xSrSrlibraries/simplexlsx.class.phpnu[ 'General', 1 => '0', 2 => '0.00', 3 => '#,##0', 4 => '#,##0.00', 9 => '0%', 10 => '0.00%', 11 => '0.00E+00', 12 => '# ?/?', 13 => '# ??/??', 14 => 'mm-dd-yy', 15 => 'd-mmm-yy', 16 => 'd-mmm', 17 => 'mmm-yy', 18 => 'h:mm AM/PM', 19 => 'h:mm:ss AM/PM', 20 => 'h:mm', 21 => 'h:mm:ss', 22 => 'm/d/yy h:mm', 37 => '#,##0 ;(#,##0)', 38 => '#,##0 ;[Red](#,##0)', 39 => '#,##0.00;(#,##0.00)', 40 => '#,##0.00;[Red](#,##0.00)', 44 => '_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)', 45 => 'mm:ss', 46 => '[h]:mm:ss', 47 => 'mmss.0', 48 => '##0.0E+0', 49 => '@', 27 => '[$-404]e/m/d', 30 => 'm/d/yy', 36 => '[$-404]e/m/d', 50 => '[$-404]e/m/d', 57 => '[$-404]e/m/d', 59 => 't0', 60 => 't0.00', 61 => 't#,##0', 62 => 't#,##0.00', 67 => 't0%', 68 => 't0.00%', 69 => 't# ?/?', 70 => 't# ??/??', ]; public $nf = []; // number formats public $cellFormats = []; // cellXfs public $datetimeFormat = 'Y-m-d H:i:s'; public $debug; public $activeSheet = 0; public $rowsExReader; /* @var SimpleXMLElement[] $sheets */ public $sheets; public $sheetFiles = []; public $sheetMetaData = []; public $sheetRels = []; // scheme public $styles; /* @var array[] $package */ public $package; public $sharedstrings; public $date1904 = 0; /* private $date_formats = array( 0xe => "d/m/Y", 0xf => "d-M-Y", 0x10 => "d-M", 0x11 => "M-Y", 0x12 => "h:i a", 0x13 => "h:i:s a", 0x14 => "H:i", 0x15 => "H:i:s", 0x16 => "d/m/Y H:i", 0x2d => "i:s", 0x2e => "H:i:s", 0x2f => "i:s.S" ); private $number_formats = array( 0x1 => "%1.0f", // "0" 0x2 => "%1.2f", // "0.00", 0x3 => "%1.0f", //"#,##0", 0x4 => "%1.2f", //"#,##0.00", 0x5 => "%1.0f", //"$#,##0;($#,##0)", 0x6 => '$%1.0f', //"$#,##0;($#,##0)", 0x7 => '$%1.2f', //"$#,##0.00;($#,##0.00)", 0x8 => '$%1.2f', //"$#,##0.00;($#,##0.00)", 0x9 => '%1.0f%%', //"0%" 0xa => '%1.2f%%', //"0.00%" 0xb => '%1.2f', //"0.00E00", 0x25 => '%1.0f', //"#,##0;(#,##0)", 0x26 => '%1.0f', //"#,##0;(#,##0)", 0x27 => '%1.2f', //"#,##0.00;(#,##0.00)", 0x28 => '%1.2f', //"#,##0.00;(#,##0.00)", 0x29 => '%1.0f', //"#,##0;(#,##0)", 0x2a => '$%1.0f', //"$#,##0;($#,##0)", 0x2b => '%1.2f', //"#,##0.00;(#,##0.00)", 0x2c => '$%1.2f', //"$#,##0.00;($#,##0.00)", 0x30 => '%1.0f'); //"##0.0E0"; // }}} */ public $errno = 0; public $error = false; /** * @var false|SimpleXMLElement */ public $theme; public function __construct($filename = null, $is_data = null, $debug = null) { if ($debug !== null) { $this->debug = $debug; } $this->package = [ 'filename' => '', 'mtime' => 0, 'size' => 0, 'comment' => '', 'entries' => [] ]; if ($filename && $this->unzip($filename, $is_data)) { $this->parseEntries(); } } public function unzip($filename, $is_data = false) { if ($is_data) { $this->package['filename'] = 'default.xlsx'; $this->package['mtime'] = time(); $this->package['size'] = SimpleXLSX::strlen($filename); $vZ = $filename; } else { if (!is_readable($filename)) { $this->error(1, 'File not found ' . $filename); return false; } // Package information $this->package['filename'] = $filename; $this->package['mtime'] = filemtime($filename); $this->package['size'] = filesize($filename); // Read file $vZ = file_get_contents($filename); } // Cut end of central directory /* $aE = explode("\x50\x4b\x05\x06", $vZ); if (count($aE) == 1) { $this->error('Unknown format'); return false; } */ // Explode to each part $aE = explode("\x50\x4b\x03\x04", $vZ); array_shift($aE); $aEL = count($aE); if ($aEL === 0) { $this->error(2, 'Unknown archive format'); return false; } // Search central directory end record $last = $aE[$aEL - 1]; $last = explode("\x50\x4b\x05\x06", $last); if (count($last) !== 2) { $this->error(2, 'Unknown archive format'); return false; } // Search central directory $last = explode("\x50\x4b\x01\x02", $last[0]); if (count($last) < 2) { $this->error(2, 'Unknown archive format'); return false; } $aE[$aEL - 1] = $last[0]; // Loop through the entries foreach ($aE as $vZ) { $aI = []; $aI['E'] = 0; $aI['EM'] = ''; // Retrieving local file header information // $aP = unpack('v1VN/v1GPF/v1CM/v1FT/v1FD/V1CRC/V1CS/V1UCS/v1FNL', $vZ); $aP = unpack('v1VN/v1GPF/v1CM/v1FT/v1FD/V1CRC/V1CS/V1UCS/v1FNL/v1EFL', $vZ); // Check if data is encrypted // $bE = ($aP['GPF'] && 0x0001) ? TRUE : FALSE; // $bE = false; $nF = $aP['FNL']; $mF = $aP['EFL']; // Special case : value block after the compressed data if ($aP['GPF'] & 0x0008) { $aP1 = unpack('V1CRC/V1CS/V1UCS', SimpleXLSX::substr($vZ, -12)); $aP['CRC'] = $aP1['CRC']; $aP['CS'] = $aP1['CS']; $aP['UCS'] = $aP1['UCS']; // 2013-08-10 $vZ = SimpleXLSX::substr($vZ, 0, -12); if (SimpleXLSX::substr($vZ, -4) === "\x50\x4b\x07\x08") { $vZ = SimpleXLSX::substr($vZ, 0, -4); } } // Getting stored filename $aI['N'] = SimpleXLSX::substr($vZ, 26, $nF); $aI['N'] = str_replace('\\', '/', $aI['N']); if (SimpleXLSX::substr($aI['N'], -1) === '/') { // is a directory entry - will be skipped continue; } // Truncate full filename in path and filename $aI['P'] = dirname($aI['N']); $aI['P'] = ($aI['P'] === '.') ? '' : $aI['P']; $aI['N'] = basename($aI['N']); $vZ = SimpleXLSX::substr($vZ, 26 + $nF + $mF); if ($aP['CS'] > 0 && (SimpleXLSX::strlen($vZ) !== (int)$aP['CS'])) { // check only if availabled $aI['E'] = 1; $aI['EM'] = 'Compressed size is not equal with the value in header information.'; } // } elseif ( $bE ) { // $aI['E'] = 5; // $aI['EM'] = 'File is encrypted, which is not supported from this class.'; /* } else { switch ($aP['CM']) { case 0: // Stored // Here is nothing to do, the file ist flat. break; case 8: // Deflated $vZ = gzinflate($vZ); break; case 12: // BZIP2 if (extension_loaded('bz2')) { $vZ = bzdecompress($vZ); } else { $aI['E'] = 7; $aI['EM'] = 'PHP BZIP2 extension not available.'; } break; default: $aI['E'] = 6; $aI['EM'] = "De-/Compression method {$aP['CM']} is not supported."; } if (!$aI['E']) { if ($vZ === false) { $aI['E'] = 2; $aI['EM'] = 'Decompression of data failed.'; } elseif ($this->_strlen($vZ) !== (int)$aP['UCS']) { $aI['E'] = 3; $aI['EM'] = 'Uncompressed size is not equal with the value in header information.'; } elseif (crc32($vZ) !== $aP['CRC']) { $aI['E'] = 4; $aI['EM'] = 'CRC32 checksum is not equal with the value in header information.'; } } } */ // DOS to UNIX timestamp $aI['T'] = mktime( ($aP['FT'] & 0xf800) >> 11, ($aP['FT'] & 0x07e0) >> 5, ($aP['FT'] & 0x001f) << 1, ($aP['FD'] & 0x01e0) >> 5, $aP['FD'] & 0x001f, (($aP['FD'] & 0xfe00) >> 9) + 1980 ); $this->package['entries'][] = [ 'data' => $vZ, 'ucs' => (int)$aP['UCS'], // ucompresses size 'cm' => $aP['CM'], // compressed method 'cs' => isset($aP['CS']) ? (int) $aP['CS'] : 0, // compresses size 'crc' => $aP['CRC'], 'error' => $aI['E'], 'error_msg' => $aI['EM'], 'name' => $aI['N'], 'path' => $aI['P'], 'time' => $aI['T'] ]; } // end for each entries return true; } public function error($num = null, $str = null) { if ($num) { $this->errno = $num; $this->error = $str; if ($this->debug) { trigger_error(__CLASS__ . ': ' . $this->error, E_USER_WARNING); } } return $this->error; } public function parseEntries() { // Document data holders $this->sharedstrings = []; $this->sheets = []; // $this->styles = array(); // $m1 = 0; // memory_get_peak_usage( true ); // Read relations and search for officeDocument if ($relations = $this->getEntryXML('_rels/.rels')) { foreach ($relations->Relationship as $rel) { $rel_type = basename(trim((string)$rel['Type'])); // officeDocument $rel_target = SimpleXLSX::getTarget('', (string)$rel['Target']); // /xl/workbook.xml or xl/workbook.xml if ($rel_type === 'officeDocument' && $workbook = $this->getEntryXML($rel_target) ) { $index_rId = []; // [0 => rId1] $index = 0; foreach ($workbook->sheets->sheet as $s) { $a = []; foreach ($s->attributes() as $k => $v) { $a[(string)$k] = (string)$v; } $this->sheetMetaData[$index] = $a; $index_rId[$index] = (string)$s['id']; $index++; } if ((int)$workbook->workbookPr['date1904'] === 1) { $this->date1904 = 1; } if ($workbookRelations = $this->getEntryXML(dirname($rel_target) . '/_rels/workbook.xml.rels')) { // Loop relations for workbook and extract sheets... foreach ($workbookRelations->Relationship as $workbookRelation) { $wrel_type = basename(trim((string)$workbookRelation['Type'])); // worksheet $wrel_target = SimpleXLSX::getTarget(dirname($rel_target), (string)$workbookRelation['Target']); if (!$this->entryExists($wrel_target)) { continue; } if ($wrel_type === 'worksheet') { // Sheets if ($sheet = $this->getEntryXML($wrel_target)) { $index = array_search((string)$workbookRelation['Id'], $index_rId, true); $this->sheets[$index] = $sheet; $this->sheetFiles[$index] = $wrel_target; $srel_d = dirname($wrel_target); $srel_f = basename($wrel_target); $srel_file = $srel_d . '/_rels/' . $srel_f . '.rels'; if ($this->entryExists($srel_file)) { $this->sheetRels[$index] = $this->getEntryXML($srel_file); } } } elseif ($wrel_type === 'sharedStrings') { if ($sharedStrings = $this->getEntryXML($wrel_target)) { foreach ($sharedStrings->si as $val) { if (isset($val->t)) { $this->sharedstrings[] = (string)$val->t; } elseif (isset($val->r)) { $this->sharedstrings[] = SimpleXLSX::parseRichText($val); } } } } elseif ($wrel_type === 'styles') { $this->styles = $this->getEntryXML($wrel_target); // number formats $this->nf = []; if (isset($this->styles->numFmts->numFmt)) { foreach ($this->styles->numFmts->numFmt as $v) { $this->nf[(int)$v['numFmtId']] = (string)$v['formatCode']; } } $this->cellFormats = []; if (isset($this->styles->cellXfs->xf)) { foreach ($this->styles->cellXfs->xf as $v) { $x = [ 'format' => null ]; foreach ($v->attributes() as $k1 => $v1) { $x[ $k1 ] = (int) $v1; } if (isset($x['numFmtId'])) { if (isset($this->nf[$x['numFmtId']])) { $x['format'] = $this->nf[$x['numFmtId']]; } elseif (isset(self::$CF[$x['numFmtId']])) { $x['format'] = self::$CF[$x['numFmtId']]; } } $this->cellFormats[] = $x; } } } elseif ($wrel_type === 'theme') { $this->theme = $this->getEntryXML($wrel_target); } } // break; } // reptile hack :: find active sheet from workbook.xml if ($workbook->bookViews->workbookView) { foreach ($workbook->bookViews->workbookView as $v) { if (!empty($v['activeTab'])) { $this->activeSheet = (int)$v['activeTab']; } } } break; } } } // $m2 = memory_get_peak_usage(true); // echo __FUNCTION__.' M='.round( ($m2-$m1) / 1048576, 2).'MB'.PHP_EOL; if (count($this->sheets)) { // Sort sheets ksort($this->sheets); return true; } return false; } public function getEntryXML($name) { if ($entry_xml = $this->getEntryData($name)) { $this->deleteEntry($name); // economy memory // dirty remove namespace prefixes and empty rows $entry_xml = preg_replace('/xmlns[^=]*="[^"]*"/i', '', $entry_xml); // remove namespaces $entry_xml .= ' '; // force run garbage collector $entry_xml = preg_replace('/[a-zA-Z0-9]+:([a-zA-Z0-9]+="[^"]+")/', '$1', $entry_xml); // remove namespaced attrs $entry_xml .= ' '; $entry_xml = preg_replace('/<[a-zA-Z0-9]+:([^>]+)>/', '<$1>', $entry_xml); // fix namespaced openned tags $entry_xml .= ' '; $entry_xml = preg_replace('/<\/[a-zA-Z0-9]+:([^>]+)>/', '', $entry_xml); // fix namespaced closed tags $entry_xml .= ' '; if (strpos($name, '/sheet')) { // dirty skip empty rows // remove $entry_xml = preg_replace('/]+>\s*(\s*)+<\/row>/', '', $entry_xml, -1, $cnt); $entry_xml .= ' '; // remove $entry_xml = preg_replace('/]*\/>/', '', $entry_xml, -1, $cnt2); $entry_xml .= ' '; // remove $entry_xml = preg_replace('/]*><\/row>/', '', $entry_xml, -1, $cnt3); $entry_xml .= ' '; if ($cnt || $cnt2 || $cnt3) { $entry_xml = preg_replace('//', '', $entry_xml); $entry_xml .= ' '; } // file_put_contents( basename( $name ), $entry_xml ); // @to do comment!!! } $entry_xml = trim($entry_xml); // $m1 = memory_get_usage(); // XML External Entity (XXE) Prevention, libxml_disable_entity_loader deprecated in PHP 8 if (LIBXML_VERSION < 20900 && function_exists('libxml_disable_entity_loader')) { $_old = libxml_disable_entity_loader(); } $_old_uie = libxml_use_internal_errors(true); $entry_xmlobj = simplexml_load_string($entry_xml, 'SimpleXMLElement', LIBXML_COMPACT | LIBXML_PARSEHUGE); libxml_use_internal_errors($_old_uie); if (LIBXML_VERSION < 20900 && function_exists('libxml_disable_entity_loader')) { /** @noinspection PhpUndefinedVariableInspection */ libxml_disable_entity_loader($_old); } // $m2 = memory_get_usage(); // echo round( ($m2-$m1) / (1024 * 1024), 2).' MB'.PHP_EOL; if ($entry_xmlobj) { return $entry_xmlobj; } $e = libxml_get_last_error(); if ($e) { $this->error(3, 'XML-entry ' . $name . ' parser error ' . $e->message . ' line ' . $e->line); } } else { $this->error(4, 'XML-entry not found ' . $name); } return false; } // sheets numeration: 1,2,3.... public function getEntryData($name) { $name = ltrim(str_replace('\\', '/', $name), '/'); $dir = SimpleXLSX::strtoupper(dirname($name)); $name = SimpleXLSX::strtoupper(basename($name)); foreach ($this->package['entries'] as &$entry) { if (SimpleXLSX::strtoupper($entry['path']) === $dir && SimpleXLSX::strtoupper($entry['name']) === $name) { if ($entry['error']) { return false; } switch ($entry['cm']) { case -1: case 0: // Stored // Here is nothing to do, the file ist flat. break; case 8: // Deflated $entry['data'] = gzinflate($entry['data']); break; case 12: // BZIP2 if (extension_loaded('bz2')) { $entry['data'] = bzdecompress($entry['data']); } else { $entry['error'] = 7; $entry['error_message'] = 'PHP BZIP2 extension not available.'; } break; default: $entry['error'] = 6; $entry['error_msg'] = 'De-/Compression method '.$entry['cm'].' is not supported.'; } if (!$entry['error'] && $entry['cm'] > -1) { $entry['cm'] = -1; if ($entry['data'] === false) { $entry['error'] = 2; $entry['error_msg'] = 'Decompression of data failed.'; } elseif ($entry['ucs'] > 0 && (SimpleXLSX::strlen($entry['data']) !== (int)$entry['ucs'])) { $entry['error'] = 3; $entry['error_msg'] = 'Uncompressed size is not equal with the value in header information.'; } elseif (crc32($entry['data']) !== $entry['crc']) { $entry['error'] = 4; $entry['error_msg'] = 'CRC32 checksum is not equal with the value in header information.'; } } return $entry['data']; } } unset($entry); $this->error(5, 'Entry not found ' . ($dir ? $dir . '/' : '') . $name); return false; } public function deleteEntry($name) { $name = ltrim(str_replace('\\', '/', $name), '/'); $dir = SimpleXLSX::strtoupper(dirname($name)); $name = SimpleXLSX::strtoupper(basename($name)); foreach ($this->package['entries'] as $k => $entry) { if (SimpleXLSX::strtoupper($entry['path']) === $dir && SimpleXLSX::strtoupper($entry['name']) === $name) { unset($this->package['entries'][$k]); return true; } } return false; } public static function strtoupper($str) { return (ini_get('mbstring.func_overload') & 2) ? mb_strtoupper($str, '8bit') : strtoupper($str); } /* * @param string $name Filename in archive * @return SimpleXMLElement|bool */ public function entryExists($name) { // 0.6.6 $dir = SimpleXLSX::strtoupper(dirname($name)); $name = SimpleXLSX::strtoupper(basename($name)); foreach ($this->package['entries'] as $entry) { if (SimpleXLSX::strtoupper($entry['path']) === $dir && SimpleXLSX::strtoupper($entry['name']) === $name) { return true; } } return false; } public static function parseFile($filename, $debug = false) { return self::parse($filename, false, $debug); } public static function parse($filename, $is_data = false, $debug = false) { $xlsx = new self(); $xlsx->debug = $debug; if ($xlsx->unzip($filename, $is_data)) { $xlsx->parseEntries(); } if ($xlsx->success()) { return $xlsx; } self::parseError($xlsx->error()); self::parseErrno($xlsx->errno()); return false; } public function success() { return !$this->error; } // https://github.com/shuchkin/simplexlsx#gets-extend-cell-info-by--rowsex public static function parseError($set = false) { static $error = false; return $set ? $error = $set : $error; } public static function parseErrno($set = false) { static $errno = false; return $set ? $errno = $set : $errno; } public function errno() { return $this->errno; } public static function parseData($data, $debug = false) { return self::parse($data, true, $debug); } public function worksheet($worksheetIndex = 0) { if (isset($this->sheets[$worksheetIndex])) { return $this->sheets[$worksheetIndex]; } $this->error(6, 'Worksheet not found ' . $worksheetIndex); return false; } /** * returns [numCols,numRows] of worksheet * * @param int $worksheetIndex * * @return array */ public function dimension($worksheetIndex = 0) { if (($ws = $this->worksheet($worksheetIndex)) === false) { return [0, 0]; } /* @var SimpleXMLElement $ws */ $ref = (string)$ws->dimension['ref']; if (SimpleXLSX::strpos($ref, ':') !== false) { $d = explode(':', $ref); $idx = $this->getIndex($d[1]); return [$idx[0] + 1, $idx[1] + 1]; } /* if ( $ref !== '' ) { // 0.6.8 $index = $this->getIndex( $ref ); return [ $index[0] + 1, $index[1] + 1 ]; } */ // slow method $maxC = $maxR = 0; $iR = -1; foreach ($ws->sheetData->row as $row) { $iR++; $iC = -1; foreach ($row->c as $c) { $iC++; $idx = $this->getIndex((string)$c['r']); $x = $idx[0]; $y = $idx[1]; if ($x > -1) { if ($x > $maxC) { $maxC = $x; } if ($y > $maxR) { $maxR = $y; } } else { if ($iC > $maxC) { $maxC = $iC; } if ($iR > $maxR) { $maxR = $iR; } } } } return [$maxC + 1, $maxR + 1]; } public function getIndex($cell = 'A1') { if (preg_match('/([A-Z]+)(\d+)/', $cell, $m)) { $col = $m[1]; $row = $m[2]; $colLen = SimpleXLSX::strlen($col); $index = 0; for ($i = $colLen - 1; $i >= 0; $i--) { $index += (ord($col[$i]) - 64) * pow(26, $colLen - $i - 1); } return [$index - 1, $row - 1]; } // $this->error( 'Invalid cell index ' . $cell ); return [-1, -1]; } public function value($cell) { // Determine data type $dataType = (string)$cell['t']; if ($dataType === '' || $dataType === 'n') { // number $s = (int)$cell['s']; if ($s > 0 && isset($this->cellFormats[$s])) { if (array_key_exists('format', $this->cellFormats[$s])) { $format = $this->cellFormats[$s]['format']; if ($format && preg_match('/[mM]/', preg_replace('/\"[^"]+\"/', '', $format))) { // [mm]onth,AM|PM $dataType = 'D'; } } else { $dataType = 'n'; } } } $value = ''; switch ($dataType) { case 's': // Value is a shared string if ((string)$cell->v !== '') { $value = $this->sharedstrings[(int)$cell->v]; } break; case 'str': // formula? if ((string)$cell->v !== '') { $value = (string)$cell->v; } break; case 'b': // Value is boolean $value = (string)$cell->v; if ($value === '0') { $value = false; } elseif ($value === '1') { $value = true; } else { $value = (bool)$cell->v; } break; case 'inlineStr': // Value is rich text inline $value = SimpleXLSX::parseRichText($cell->is); break; case 'e': // Value is an error message if ((string)$cell->v !== '') { $value = (string)$cell->v; } break; case 'D': // Date as float if (!empty($cell->v)) { $value = $this->datetimeFormat ? gmdate($this->datetimeFormat, $this->unixstamp((float)$cell->v)) : (float)$cell->v; } break; case 'd': // Date as ISO YYYY-MM-DD if ((string)$cell->v !== '') { $value = (string)$cell->v; } break; default: // Value is a string $value = (string)$cell->v; // Check for numeric values if (is_numeric($value)) { /** @noinspection TypeUnsafeComparisonInspection */ if ($value == (int)$value) { $value = (int)$value; } /** @noinspection TypeUnsafeComparisonInspection */ elseif ($value == (float)$value) { $value = (float)$value; } } } return $value; } public function unixstamp($excelDateTime) { $d = floor($excelDateTime); // days since 1900 or 1904 $t = $excelDateTime - $d; if ($this->date1904) { $d += 1462; } $t = (abs($d) > 0) ? ($d - 25569) * 86400 + round($t * 86400) : round($t * 86400); return (int)$t; } public function toHTML($worksheetIndex = 0) { $s = ''; foreach ($this->readRows($worksheetIndex) as $r) { $s .= ''; foreach ($r as $c) { $s .= ''; } $s .= "\r\n"; } $s .= '
    ' . ($c === '' ? ' ' : htmlspecialchars($c, ENT_QUOTES)) . '
    '; return $s; } public function toHTMLEx($worksheetIndex = 0) { $s = ''; $y = 0; foreach ($this->readRowsEx($worksheetIndex) as $r) { $s .= ''; $x = 0; foreach ($r as $c) { $tag = 'td'; $css = $c['css']; if ($y === 0) { $tag = 'th'; $css .= $c['width'] ? 'width: '.round($c['width'] * 0.47, 2).'em;' : ''; } if ($x === 0 && $c['height']) { $css .= 'height: '.round($c['height'] * 1.3333).'px;'; } $s .= '<'.$tag.' style="'.$css.'" nowrap>' . ($c['value'] === '' ? ' ' : htmlspecialchars($c['value'], ENT_QUOTES)) . ''; $x++; } $s .= "\r\n"; $y++; } $s .= '
    '; return $s; } public function rows($worksheetIndex = 0, $limit = 0) { return iterator_to_array($this->readRows($worksheetIndex, $limit), false); } // thx Gonzo /** * @param $worksheetIndex * @param $limit * @return \Generator */ public function readRows($worksheetIndex = 0, $limit = 0) { if (($ws = $this->worksheet($worksheetIndex)) === false) { return; } $dim = $this->dimension($worksheetIndex); $numCols = $dim[0]; $numRows = $dim[1]; $emptyRow = []; for ($i = 0; $i < $numCols; $i++) { $emptyRow[] = ''; } $curR = 0; $_limit = $limit; /* @var SimpleXMLElement $ws */ foreach ($ws->sheetData->row as $row) { $r = $emptyRow; $curC = 0; foreach ($row->c as $c) { // detect skipped cols $idx = $this->getIndex((string)$c['r']); $x = $idx[0]; $y = $idx[1]; if ($x > -1) { $curC = $x; while ($curR < $y) { yield $emptyRow; $curR++; $_limit--; if ($_limit === 0) { return; } } } $r[$curC] = $this->value($c); $curC++; } yield $r; $curR++; $_limit--; if ($_limit === 0) { return; } } while ($curR < $numRows) { yield $emptyRow; $curR++; $_limit--; if ($_limit === 0) { return; } } } public function rowsEx($worksheetIndex = 0, $limit = 0) { return iterator_to_array($this->readRowsEx($worksheetIndex, $limit), false); } // https://github.com/shuchkin/simplexlsx#gets-extend-cell-info-by--rowsex /** * @param $worksheetIndex * @param $limit * @return \Generator|null */ public function readRowsEx($worksheetIndex = 0, $limit = 0) { if (!$this->rowsExReader) { require_once __DIR__ . '/SimpleXLSXEx.php'; $this->rowsExReader = new SimpleXLSXEx($this); } return $this->rowsExReader->readRowsEx($worksheetIndex, $limit); } /** * Returns cell value * VERY SLOW! Use ->rows() or ->rowsEx() * * @param int $worksheetIndex * @param string|array $cell ref or coords, D12 or [3,12] * * @return mixed Returns NULL if not found */ public function getCell($worksheetIndex = 0, $cell = 'A1') { if (($ws = $this->worksheet($worksheetIndex)) === false) { return false; } if (is_array($cell)) { $cell = SimpleXLSX::num2name($cell[0]) . $cell[1];// [3,21] -> D21 } if (is_string($cell)) { $result = $ws->sheetData->xpath("row/c[@r='" . $cell . "']"); if (count($result)) { return $this->value($result[0]); } } return null; } public function getSheets() { return $this->sheets; } public function sheetsCount() { return count($this->sheets); } public function sheetName($worksheetIndex) { $sn = $this->sheetNames(); if (isset($sn[$worksheetIndex])) { return $sn[$worksheetIndex]; } return false; } public function sheetNames() { $a = []; foreach ($this->sheetMetaData as $k => $v) { $a[$k] = $v['name']; } return $a; } public function sheetMeta($worksheetIndex = null) { if ($worksheetIndex === null) { return $this->sheetMetaData; } return isset($this->sheetMetaData[$worksheetIndex]) ? $this->sheetMetaData[$worksheetIndex] : false; } public function isHiddenSheet($worksheetIndex) { return isset($this->sheetMetaData[$worksheetIndex]['state']) && $this->sheetMetaData[$worksheetIndex]['state'] === 'hidden'; } public function getStyles() { return $this->styles; } public function getPackage() { return $this->package; } public function setDateTimeFormat($value) { $this->datetimeFormat = is_string($value) ? $value : false; } public static function getTarget($base, $target) { $target = trim($target); if (strpos($target, '/') === 0) { return SimpleXLSX::substr($target, 1); } $target = ($base ? $base . '/' : '') . $target; // a/b/../c -> a/c $parts = explode('/', $target); $abs = []; foreach ($parts as $p) { if ('.' === $p) { continue; } if ('..' === $p) { array_pop($abs); } else { $abs[] = $p; } } return implode('/', $abs); } public static function parseRichText($is = null) { $value = []; if (isset($is->t)) { $value[] = (string)$is->t; } elseif (isset($is->r)) { foreach ($is->r as $run) { $value[] = (string)$run->t; } } return implode('', $value); } public static function num2name($num) { $numeric = ($num - 1) % 26; $letter = chr(65 + $numeric); $num2 = (int)(($num - 1) / 26); if ($num2 > 0) { return SimpleXLSX::num2name($num2) . $letter; } return $letter; } public static function strlen($str) { return (ini_get('mbstring.func_overload') & 2) ? mb_strlen($str, '8bit') : strlen($str); } public static function substr($str, $start, $length = null) { return (ini_get('mbstring.func_overload') & 2) ? mb_substr($str, $start, ($length === null) ? mb_strlen($str, '8bit') : $length, '8bit') : substr($str, $start, ($length === null) ? strlen($str) : $length); } public static function strpos($haystack, $needle, $offset = 0) { return (ini_get('mbstring.func_overload') & 2) ? mb_strpos($haystack, $needle, $offset, '8bit') : strpos($haystack, $needle, $offset); } } PK!j6s))libraries/csv-parser.class.phpnu[> */ public array $error_info = array(); /** * Class Constructor. * * @since 1.0.0 */ public function __construct() { // Unused. } /** * Load CSV data that shall be parsed. * * @since 1.0.0 * * @param string $data Data to be parsed. */ public function load_data( string $data ): void { // Check for mandatory trailing line break. if ( ! str_ends_with( $data, "\n" ) ) { $data .= "\n"; } $this->import_data = $data; } /** * Detect the CSV delimiter, by analyzing some rows to determine the most probable delimiter character. * * @since 1.0.0 * * @return string Most probable delimiter character. */ public function find_delimiter(): string { $data = &$this->import_data; $delimiter_count = array(); $enclosed = false; $current_line = 0; // Walk through each character in the CSV string (up to $this->delimiter_search_max_lines) and search potential delimiter characters. $data_length = strlen( $data ); for ( $i = 0; $i < $data_length; $i++ ) { $prev_char = ( $i - 1 >= 0 ) ? $data[ $i - 1 ] : ''; $curr_char = $data[ $i ]; $next_char = ( $i + 1 < $data_length ) ? $data[ $i + 1 ] : ''; if ( $curr_char === $this->enclosure ) { // Open and closing quotes. if ( ! $enclosed || $next_char !== $this->enclosure ) { $enclosed = ! $enclosed; // Flip bool. } elseif ( $enclosed ) { ++$i; // Skip next character. } } elseif ( ( ( "\n" === $curr_char && "\r" !== $prev_char ) || "\r" === $curr_char ) && ! $enclosed ) { // Reached end of a line. ++$current_line; if ( $current_line >= $this->delimiter_search_max_lines ) { break; } } elseif ( ! $enclosed ) { // At this point, $curr_char seems to be used as a delimiter, as it is not enclosed. // Count $curr_char if it is not in the $this->non_delimiter_chars list. if ( 0 === preg_match( '#[' . $this->non_delimiter_chars . ']#i', $curr_char ) ) { if ( ! isset( $delimiter_count[ $curr_char ][ $current_line ] ) ) { $delimiter_count[ $curr_char ][ $current_line ] = 0; // Initialize empty. } ++$delimiter_count[ $curr_char ][ $current_line ]; } } } // Find most probable delimiter, by sorting their counts. $potential_delimiters = array(); foreach ( $delimiter_count as $char => $line_counts ) { $is_possible_delimiter = $this->_check_delimiter_count( $char, $line_counts, $current_line ); if ( false !== $is_possible_delimiter ) { $potential_delimiters[ $is_possible_delimiter ] = $char; } } ksort( $potential_delimiters ); // If no valid delimiter was found, use the character that was found in most rows. if ( empty( $potential_delimiters ) ) { $delimiter_counts = array_map( 'count', $delimiter_count ); arsort( $delimiter_counts, SORT_NUMERIC ); $potential_delimiters = array_keys( $delimiter_counts ); } // If still no delimiter was found, fall back to a comma. if ( empty( $potential_delimiters ) ) { $potential_delimiters = array( ',' ); } // Return first array element, as that has the highest count. return array_shift( $potential_delimiters ); } /** * Check if passed character can be a delimiter, by checking counts in each line. * * @since 1.0.0 * * @param string $char Character to check. * @param int[] $line_counts Counts for the characters in the lines. * @param int $number_lines Number of lines. * @return bool|string False if delimiter is not possible, string to be used as a sort key if character could be a delimiter. */ protected function _check_delimiter_count( string $char, array $line_counts, int $number_lines ) /* : bool|string */ { // Was the potential delimiter found in every line? if ( count( $line_counts ) !== $number_lines ) { return false; } // Check if the count in every line is the same (or one higher for an "almost"). $first = null; $equal = null; $almost = false; foreach ( $line_counts as $count ) { if ( is_null( $first ) ) { $first = $count; } elseif ( $count === $first && false !== $equal ) { $equal = true; } elseif ( $count === $first + 1 && false !== $equal ) { $equal = true; $almost = true; } else { $equal = false; } } // Check equality only if there's more than one line. if ( $number_lines > 1 && ! $equal ) { return false; } // At this point, count is equal in all lines, so determine a string to sort priority. $match = ( $almost ) ? 2 : 1; // There must not be more than 9 characters in the preferred delimiter character list. $pref = strpos( $this->preferred_delimiter_chars, $char ); if ( false === $pref ) { $pref = 9; } return $pref . $match . '.' . ( 99999 - $first ); } /** * Parse CSV string into a two-dimensional array. * * @since 1.0.0 * * @param string $delimiter Delimiter character for the CSV parsing. * @return array> Two-dimensional array with the data from the CSV string. */ public function parse( string $delimiter ): array { $data = &$this->import_data; // Filter delimiter from the list, if it is a whitespace character. $white_spaces = str_replace( $delimiter, '', " \t\x0B\0" ); $rows = array(); // Complete rows. $row = array(); // Row that is currently built. $column = 0; // Current column index. $cell_content = ''; // Content of the currently processed cell. $enclosed = false; $was_enclosed = false; // To determine if the cell content will be trimmed of whitespace (only for enclosed cells). // Walk through each character in the CSV string. $data_length = strlen( $data ); for ( $i = 0; $i < $data_length; $i++ ) { $curr_char = $data[ $i ]; $next_char = ( $i + 1 < $data_length ) ? $data[ $i + 1 ] : ''; if ( $curr_char === $this->enclosure ) { // Open/close quotes, and inline quotes. if ( ! $enclosed ) { if ( '' === ltrim( $cell_content, $white_spaces ) ) { $enclosed = true; $was_enclosed = true; } else { $this->error = 2; $error_line = count( $rows ) + 1; $error_column = $column + 1; if ( ! isset( $this->error_info[ "{$error_line}-{$error_column}" ] ) ) { $this->error_info[ "{$error_line}-{$error_column}" ] = array( 'type' => 2, 'info' => "Syntax error found in line {$error_line}. Non-enclosed fields can not contain double-quotes.", 'line' => $error_line, 'column' => $error_column, ); } $cell_content .= $curr_char; } } elseif ( $next_char === $this->enclosure ) { // Enclosure character within enclosed cell (" encoded as ""). $cell_content .= $curr_char; ++$i; // Skip next character. } elseif ( $next_char !== $delimiter && "\r" !== $next_char && "\n" !== $next_char ) { // for-loop (instead of while-loop) that skips whitespace. for ( $x = ( $i + 1 ); isset( $data[ $x ] ) && '' === ltrim( $data[ $x ], $white_spaces ); $x++ ) { // phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed,Generic.CodeAnalysis.EmptyStatement.DetectedFor // Action is in iterator check. } if ( $data[ $x ] === $delimiter ) { $enclosed = false; $i = $x; } else { if ( $this->error < 1 ) { $this->error = 1; } $error_line = count( $rows ) + 1; $error_column = $column + 1; if ( ! isset( $this->error_info[ "{$error_line}-{$error_column}" ] ) ) { $this->error_info[ "{$error_line}-{$error_column}" ] = array( 'type' => 1, 'info' => "Syntax error found in line {$error_line}. A single double-quote was found within an enclosed string. Enclosed double-quotes must be escaped with a second double-quote.", 'line' => $error_line, 'column' => $error_column, ); } $cell_content .= $curr_char; $enclosed = false; } } else { // The " was the closing one for the cell. $enclosed = false; } } elseif ( ( $curr_char === $delimiter || "\n" === $curr_char || "\r" === $curr_char ) && ! $enclosed ) { // End of cell (by $delimiter), or end of line (by line break, and not enclosed!). $row[ $column ] = ( $was_enclosed ) ? $cell_content : trim( $cell_content ); $cell_content = ''; $was_enclosed = false; ++$column; // End of line. if ( "\n" === $curr_char || "\r" === $curr_char ) { // Append completed row. $rows[] = $row; $row = array(); $column = 0; if ( "\r" === $curr_char && "\n" === $next_char ) { // Skip next character in \r\n line breaks. ++$i; } } } else { // Append character to current cell. $cell_content .= $curr_char; } } return $rows; } } // class CSV_Parser PK!Jlibraries/csstidy/data.inc.phpnu[?[]^`|~'; /** * All CSS units (CSS3 units included). * * @see compress_numbers() */ $data['csstidy']['units'] = array( 'in', 'cm', 'mm', 'pt', 'pc', 'px', 'rem', 'em', '%', 'ex', 'gd', 'vw', 'vh', 'vm', 'deg', 'grad', 'rad', 'turn', 'ms', 's', 'khz', 'hz', 'ch', 'vmin', 'vmax', 'dpi', 'dpcm', 'dppx' ); /** * Available at-rules. */ $data['csstidy']['at_rules'] = array( 'page' => 'is', 'font-face' => 'atis', 'charset' => 'iv', 'import' => 'iv', 'namespace' => 'iv', 'media' => 'at', 'keyframes' => 'at', '-moz-keyframes' => 'at', '-o-keyframes' => 'at', '-webkit-keyframes' => 'at', '-ms-keyframes' => 'at', 'viewport' => 'at', '-webkit-viewport' => 'at', '-moz-viewport' => 'at', '-ms-viewport' => 'at', 'supports' => 'at', 'container' => 'at', ); /** * Properties that need a value with unit. * * @todo CSS3 properties. * @see compress_numbers(); */ $data['csstidy']['unit_values'] = array( 'background', 'background-position', 'background-size', 'border', 'border-top', 'border-right', 'border-bottom', 'border-left', 'border-width', 'border-top-width', 'border-right-width', 'border-left-width', 'border-bottom-width', 'bottom', 'border-spacing', 'column-gap', 'column-width', 'font-size', 'height', 'left', 'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left', 'margin-block', 'margin-block-start', 'margin-block-end', 'margin-inline', 'margin-inline-start', 'margin-inline-end', 'max-height', 'max-width', 'min-height', 'min-width', 'outline', 'outline-width', 'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left', 'perspective', 'right', 'top', 'text-indent', 'letter-spacing', 'word-spacing', 'width', ); /** * Properties that allow as value. * * @todo CSS3 properties * @see compress_numbers(); */ $data['csstidy']['color_values'] = array( 'background-color', 'border-color', 'border-top-color', 'border-right-color', 'border-bottom-color', 'border-left-color', 'color', 'outline-color', 'column-rule-color' ); /** * Default values for the background properties. * * @todo Possibly property names will change during CSS3 development. * @see dissolve_short_bg() * @see merge_bg() */ $data['csstidy']['background_prop_default'] = array(); $data['csstidy']['background_prop_default']['background-image'] = 'none'; $data['csstidy']['background_prop_default']['background-size'] = 'auto'; $data['csstidy']['background_prop_default']['background-repeat'] = 'repeat'; $data['csstidy']['background_prop_default']['background-position'] = '0 0'; $data['csstidy']['background_prop_default']['background-attachment'] = 'scroll'; $data['csstidy']['background_prop_default']['background-clip'] = 'border'; $data['csstidy']['background_prop_default']['background-origin'] = 'padding'; $data['csstidy']['background_prop_default']['background-color'] = 'transparent'; /** * Default values for the font properties. * * @see merge_fonts() */ $data['csstidy']['font_prop_default'] = array(); $data['csstidy']['font_prop_default']['font-style'] = 'normal'; $data['csstidy']['font_prop_default']['font-variant'] = 'normal'; $data['csstidy']['font_prop_default']['font-weight'] = 'normal'; $data['csstidy']['font_prop_default']['font-size'] = ''; $data['csstidy']['font_prop_default']['line-height'] = ''; $data['csstidy']['font_prop_default']['font-family'] = ''; /** * A list of non-W3C color names which get replaced by their hex-codes. * * @see cut_color() */ $data['csstidy']['replace_colors'] = array(); $data['csstidy']['replace_colors']['aliceblue'] = '#f0f8ff'; $data['csstidy']['replace_colors']['antiquewhite'] = '#faebd7'; $data['csstidy']['replace_colors']['aquamarine'] = '#7fffd4'; $data['csstidy']['replace_colors']['azure'] = '#f0ffff'; $data['csstidy']['replace_colors']['beige'] = '#f5f5dc'; $data['csstidy']['replace_colors']['bisque'] = '#ffe4c4'; $data['csstidy']['replace_colors']['blanchedalmond'] = '#ffebcd'; $data['csstidy']['replace_colors']['blueviolet'] = '#8a2be2'; $data['csstidy']['replace_colors']['brown'] = '#a52a2a'; $data['csstidy']['replace_colors']['burlywood'] = '#deb887'; $data['csstidy']['replace_colors']['cadetblue'] = '#5f9ea0'; $data['csstidy']['replace_colors']['chartreuse'] = '#7fff00'; $data['csstidy']['replace_colors']['chocolate'] = '#d2691e'; $data['csstidy']['replace_colors']['coral'] = '#ff7f50'; $data['csstidy']['replace_colors']['cornflowerblue'] = '#6495ed'; $data['csstidy']['replace_colors']['cornsilk'] = '#fff8dc'; $data['csstidy']['replace_colors']['crimson'] = '#dc143c'; $data['csstidy']['replace_colors']['cyan'] = '#00ffff'; $data['csstidy']['replace_colors']['darkblue'] = '#00008b'; $data['csstidy']['replace_colors']['darkcyan'] = '#008b8b'; $data['csstidy']['replace_colors']['darkgoldenrod'] = '#b8860b'; $data['csstidy']['replace_colors']['darkgray'] = '#a9a9a9'; $data['csstidy']['replace_colors']['darkgreen'] = '#006400'; $data['csstidy']['replace_colors']['darkkhaki'] = '#bdb76b'; $data['csstidy']['replace_colors']['darkmagenta'] = '#8b008b'; $data['csstidy']['replace_colors']['darkolivegreen'] = '#556b2f'; $data['csstidy']['replace_colors']['darkorange'] = '#ff8c00'; $data['csstidy']['replace_colors']['darkorchid'] = '#9932cc'; $data['csstidy']['replace_colors']['darkred'] = '#8b0000'; $data['csstidy']['replace_colors']['darksalmon'] = '#e9967a'; $data['csstidy']['replace_colors']['darkseagreen'] = '#8fbc8f'; $data['csstidy']['replace_colors']['darkslateblue'] = '#483d8b'; $data['csstidy']['replace_colors']['darkslategray'] = '#2f4f4f'; $data['csstidy']['replace_colors']['darkturquoise'] = '#00ced1'; $data['csstidy']['replace_colors']['darkviolet'] = '#9400d3'; $data['csstidy']['replace_colors']['deeppink'] = '#ff1493'; $data['csstidy']['replace_colors']['deepskyblue'] = '#00bfff'; $data['csstidy']['replace_colors']['dimgray'] = '#696969'; $data['csstidy']['replace_colors']['dodgerblue'] = '#1e90ff'; $data['csstidy']['replace_colors']['feldspar'] = '#d19275'; $data['csstidy']['replace_colors']['firebrick'] = '#b22222'; $data['csstidy']['replace_colors']['floralwhite'] = '#fffaf0'; $data['csstidy']['replace_colors']['forestgreen'] = '#228b22'; $data['csstidy']['replace_colors']['gainsboro'] = '#dcdcdc'; $data['csstidy']['replace_colors']['ghostwhite'] = '#f8f8ff'; $data['csstidy']['replace_colors']['gold'] = '#ffd700'; $data['csstidy']['replace_colors']['goldenrod'] = '#daa520'; $data['csstidy']['replace_colors']['greenyellow'] = '#adff2f'; $data['csstidy']['replace_colors']['honeydew'] = '#f0fff0'; $data['csstidy']['replace_colors']['hotpink'] = '#ff69b4'; $data['csstidy']['replace_colors']['indianred'] = '#cd5c5c'; $data['csstidy']['replace_colors']['indigo'] = '#4b0082'; $data['csstidy']['replace_colors']['ivory'] = '#fffff0'; $data['csstidy']['replace_colors']['khaki'] = '#f0e68c'; $data['csstidy']['replace_colors']['lavender'] = '#e6e6fa'; $data['csstidy']['replace_colors']['lavenderblush'] = '#fff0f5'; $data['csstidy']['replace_colors']['lawngreen'] = '#7cfc00'; $data['csstidy']['replace_colors']['lemonchiffon'] = '#fffacd'; $data['csstidy']['replace_colors']['lightblue'] = '#add8e6'; $data['csstidy']['replace_colors']['lightcoral'] = '#f08080'; $data['csstidy']['replace_colors']['lightcyan'] = '#e0ffff'; $data['csstidy']['replace_colors']['lightgoldenrodyellow'] = '#fafad2'; $data['csstidy']['replace_colors']['lightgrey'] = '#d3d3d3'; $data['csstidy']['replace_colors']['lightgreen'] = '#90ee90'; $data['csstidy']['replace_colors']['lightpink'] = '#ffb6c1'; $data['csstidy']['replace_colors']['lightsalmon'] = '#ffa07a'; $data['csstidy']['replace_colors']['lightseagreen'] = '#20b2aa'; $data['csstidy']['replace_colors']['lightskyblue'] = '#87cefa'; $data['csstidy']['replace_colors']['lightslateblue'] = '#8470ff'; $data['csstidy']['replace_colors']['lightslategray'] = '#778899'; $data['csstidy']['replace_colors']['lightsteelblue'] = '#b0c4de'; $data['csstidy']['replace_colors']['lightyellow'] = '#ffffe0'; $data['csstidy']['replace_colors']['limegreen'] = '#32cd32'; $data['csstidy']['replace_colors']['linen'] = '#faf0e6'; $data['csstidy']['replace_colors']['magenta'] = '#ff00ff'; $data['csstidy']['replace_colors']['mediumaquamarine'] = '#66cdaa'; $data['csstidy']['replace_colors']['mediumblue'] = '#0000cd'; $data['csstidy']['replace_colors']['mediumorchid'] = '#ba55d3'; $data['csstidy']['replace_colors']['mediumpurple'] = '#9370d8'; $data['csstidy']['replace_colors']['mediumseagreen'] = '#3cb371'; $data['csstidy']['replace_colors']['mediumslateblue'] = '#7b68ee'; $data['csstidy']['replace_colors']['mediumspringgreen'] = '#00fa9a'; $data['csstidy']['replace_colors']['mediumturquoise'] = '#48d1cc'; $data['csstidy']['replace_colors']['mediumvioletred'] = '#c71585'; $data['csstidy']['replace_colors']['midnightblue'] = '#191970'; $data['csstidy']['replace_colors']['mintcream'] = '#f5fffa'; $data['csstidy']['replace_colors']['mistyrose'] = '#ffe4e1'; $data['csstidy']['replace_colors']['moccasin'] = '#ffe4b5'; $data['csstidy']['replace_colors']['navajowhite'] = '#ffdead'; $data['csstidy']['replace_colors']['oldlace'] = '#fdf5e6'; $data['csstidy']['replace_colors']['olivedrab'] = '#6b8e23'; $data['csstidy']['replace_colors']['orangered'] = '#ff4500'; $data['csstidy']['replace_colors']['orchid'] = '#da70d6'; $data['csstidy']['replace_colors']['palegoldenrod'] = '#eee8aa'; $data['csstidy']['replace_colors']['palegreen'] = '#98fb98'; $data['csstidy']['replace_colors']['paleturquoise'] = '#afeeee'; $data['csstidy']['replace_colors']['palevioletred'] = '#d87093'; $data['csstidy']['replace_colors']['papayawhip'] = '#ffefd5'; $data['csstidy']['replace_colors']['peachpuff'] = '#ffdab9'; $data['csstidy']['replace_colors']['peru'] = '#cd853f'; $data['csstidy']['replace_colors']['pink'] = '#ffc0cb'; $data['csstidy']['replace_colors']['plum'] = '#dda0dd'; $data['csstidy']['replace_colors']['powderblue'] = '#b0e0e6'; $data['csstidy']['replace_colors']['rosybrown'] = '#bc8f8f'; $data['csstidy']['replace_colors']['royalblue'] = '#4169e1'; $data['csstidy']['replace_colors']['saddlebrown'] = '#8b4513'; $data['csstidy']['replace_colors']['salmon'] = '#fa8072'; $data['csstidy']['replace_colors']['sandybrown'] = '#f4a460'; $data['csstidy']['replace_colors']['seagreen'] = '#2e8b57'; $data['csstidy']['replace_colors']['seashell'] = '#fff5ee'; $data['csstidy']['replace_colors']['sienna'] = '#a0522d'; $data['csstidy']['replace_colors']['skyblue'] = '#87ceeb'; $data['csstidy']['replace_colors']['slateblue'] = '#6a5acd'; $data['csstidy']['replace_colors']['slategray'] = '#708090'; $data['csstidy']['replace_colors']['snow'] = '#fffafa'; $data['csstidy']['replace_colors']['springgreen'] = '#00ff7f'; $data['csstidy']['replace_colors']['steelblue'] = '#4682b4'; $data['csstidy']['replace_colors']['tan'] = '#d2b48c'; $data['csstidy']['replace_colors']['thistle'] = '#d8bfd8'; $data['csstidy']['replace_colors']['tomato'] = '#ff6347'; $data['csstidy']['replace_colors']['turquoise'] = '#40e0d0'; $data['csstidy']['replace_colors']['violet'] = '#ee82ee'; $data['csstidy']['replace_colors']['violetred'] = '#d02090'; $data['csstidy']['replace_colors']['wheat'] = '#f5deb3'; $data['csstidy']['replace_colors']['whitesmoke'] = '#f5f5f5'; $data['csstidy']['replace_colors']['yellowgreen'] = '#9acd32'; /** * A list of all shorthand properties that are divided into four properties and/or have four subvalues. * * @see dissolve_4value_shorthands() * @see merge_4value_shorthands() */ $data['csstidy']['shorthands'] = array(); $data['csstidy']['shorthands']['border-color'] = array( 'border-top-color', 'border-right-color', 'border-bottom-color', 'border-left-color' ); $data['csstidy']['shorthands']['border-style'] = array( 'border-top-style', 'border-right-style', 'border-bottom-style', 'border-left-style' ); $data['csstidy']['shorthands']['border-width'] = array( 'border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width' ); $data['csstidy']['shorthands']['margin'] = array( 'margin-top', 'margin-right', 'margin-bottom', 'margin-left' ); $data['csstidy']['shorthands']['margin-block'] = array( 'margin-block-start', 'margin-block-end' ); $data['csstidy']['shorthands']['margin-inline'] = array( 'margin-inline-start', 'margin-inline-end' ); $data['csstidy']['shorthands']['padding'] = array( 'padding-top', 'padding-right', 'padding-bottom', 'padding-left' ); /** * All CSS Properties. * * @see CSSTidy::property_is_next() */ $data['csstidy']['all_properties']['accent-color'] = 'CSS3.0'; $data['csstidy']['all_properties']['align-content'] = 'CSS3.0'; $data['csstidy']['all_properties']['align-items'] = 'CSS3.0'; $data['csstidy']['all_properties']['align-self'] = 'CSS3.0'; $data['csstidy']['all_properties']['alignment-adjust'] = 'CSS3.0'; $data['csstidy']['all_properties']['alignment-baseline'] = 'CSS3.0'; $data['csstidy']['all_properties']['animation'] = 'CSS3.0'; $data['csstidy']['all_properties']['animation-delay'] = 'CSS3.0'; $data['csstidy']['all_properties']['animation-direction'] = 'CSS3.0'; $data['csstidy']['all_properties']['animation-duration'] = 'CSS3.0'; $data['csstidy']['all_properties']['animation-fill-mode'] = 'CSS3.0'; $data['csstidy']['all_properties']['animation-iteration-count'] = 'CSS3.0'; $data['csstidy']['all_properties']['animation-name'] = 'CSS3.0'; $data['csstidy']['all_properties']['animation-play-state'] = 'CSS3.0'; $data['csstidy']['all_properties']['animation-timing-function'] = 'CSS3.0'; $data['csstidy']['all_properties']['appearance'] = 'CSS3.0'; $data['csstidy']['all_properties']['aspect-ratio'] = 'CSS3.0'; $data['csstidy']['all_properties']['azimuth'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['backface-visibility'] = 'CSS3.0'; $data['csstidy']['all_properties']['background'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['background-attachment'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['background-clip'] = 'CSS3.0'; $data['csstidy']['all_properties']['background-color'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['background-image'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['background-origin'] = 'CSS3.0'; $data['csstidy']['all_properties']['background-position'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['background-repeat'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['background-size'] = 'CSS3.0'; $data['csstidy']['all_properties']['baseline-shift'] = 'CSS3.0'; $data['csstidy']['all_properties']['bleed'] = 'CSS3.0'; $data['csstidy']['all_properties']['bookmark-label'] = 'CSS3.0'; $data['csstidy']['all_properties']['bookmark-level'] = 'CSS3.0'; $data['csstidy']['all_properties']['bookmark-state'] = 'CSS3.0'; $data['csstidy']['all_properties']['bookmark-target'] = 'CSS3.0'; $data['csstidy']['all_properties']['border'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['border-bottom'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['border-bottom-color'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['border-bottom-left-radius'] = 'CSS3.0'; $data['csstidy']['all_properties']['border-bottom-right-radius'] = 'CSS3.0'; $data['csstidy']['all_properties']['border-bottom-style'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['border-bottom-width'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['border-collapse'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['border-color'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['border-image'] = 'CSS3.0'; $data['csstidy']['all_properties']['border-image-outset'] = 'CSS3.0'; $data['csstidy']['all_properties']['border-image-repeat'] = 'CSS3.0'; $data['csstidy']['all_properties']['border-image-slice'] = 'CSS3.0'; $data['csstidy']['all_properties']['border-image-source'] = 'CSS3.0'; $data['csstidy']['all_properties']['border-image-width'] = 'CSS3.0'; $data['csstidy']['all_properties']['border-left'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['border-left-color'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['border-left-style'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['border-left-width'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['border-radius'] = 'CSS3.0'; $data['csstidy']['all_properties']['border-right'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['border-right-color'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['border-right-style'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['border-right-width'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['border-spacing'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['border-style'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['border-top'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['border-top-color'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['border-top-left-radius'] = 'CSS3.0'; $data['csstidy']['all_properties']['border-top-right-radius'] = 'CSS3.0'; $data['csstidy']['all_properties']['border-top-style'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['border-top-width'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['border-width'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['bottom'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['box-decoration-break'] = 'CSS3.0'; $data['csstidy']['all_properties']['box-shadow'] = 'CSS3.0'; $data['csstidy']['all_properties']['box-sizing'] = 'CSS3.0'; $data['csstidy']['all_properties']['break-after'] = 'CSS3.0'; $data['csstidy']['all_properties']['break-before'] = 'CSS3.0'; $data['csstidy']['all_properties']['break-inside'] = 'CSS3.0'; $data['csstidy']['all_properties']['caption-side'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['clear'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['clip'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['clip-path'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['color'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['color-profile'] = 'CSS3.0'; $data['csstidy']['all_properties']['column-count'] = 'CSS3.0'; $data['csstidy']['all_properties']['column-fill'] = 'CSS3.0'; $data['csstidy']['all_properties']['column-gap'] = 'CSS3.0'; $data['csstidy']['all_properties']['column-rule'] = 'CSS3.0'; $data['csstidy']['all_properties']['column-rule-color'] = 'CSS3.0'; $data['csstidy']['all_properties']['column-rule-style'] = 'CSS3.0'; $data['csstidy']['all_properties']['column-rule-width'] = 'CSS3.0'; $data['csstidy']['all_properties']['column-span'] = 'CSS3.0'; $data['csstidy']['all_properties']['column-width'] = 'CSS3.0'; $data['csstidy']['all_properties']['columns'] = 'CSS3.0'; $data['csstidy']['all_properties']['content'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['counter-increment'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['counter-reset'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['crop'] = 'CSS3.0'; $data['csstidy']['all_properties']['cue'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['cue-after'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['cue-before'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['cursor'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['direction'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['display'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['dominant-baseline'] = 'CSS3.0'; $data['csstidy']['all_properties']['drop-initial-after-adjust'] = 'CSS3.0'; $data['csstidy']['all_properties']['drop-initial-after-align'] = 'CSS3.0'; $data['csstidy']['all_properties']['drop-initial-before-adjust'] = 'CSS3.0'; $data['csstidy']['all_properties']['drop-initial-before-align'] = 'CSS3.0'; $data['csstidy']['all_properties']['drop-initial-size'] = 'CSS3.0'; $data['csstidy']['all_properties']['drop-initial-value'] = 'CSS3.0'; $data['csstidy']['all_properties']['elevation'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['empty-cells'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['fill'] = 'CSS3.0'; $data['csstidy']['all_properties']['fit'] = 'CSS3.0'; $data['csstidy']['all_properties']['fit-position'] = 'CSS3.0'; $data['csstidy']['all_properties']['flex'] = 'CSS3.0'; $data['csstidy']['all_properties']['flex-align'] = 'CSS3.0'; $data['csstidy']['all_properties']['flex-basis'] = 'CSS3.0'; $data['csstidy']['all_properties']['flex-direction'] = 'CSS3.0'; $data['csstidy']['all_properties']['flex-flow'] = 'CSS3.0'; $data['csstidy']['all_properties']['flex-grow'] = 'CSS3.0'; $data['csstidy']['all_properties']['flex-line-pack'] = 'CSS3.0'; $data['csstidy']['all_properties']['flex-order'] = 'CSS3.0'; $data['csstidy']['all_properties']['flex-pack'] = 'CSS3.0'; $data['csstidy']['all_properties']['flex-shrink'] = 'CSS3.0'; $data['csstidy']['all_properties']['flex-wrap'] = 'CSS3.0'; $data['csstidy']['all_properties']['float'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['float-offset'] = 'CSS3.0'; $data['csstidy']['all_properties']['font'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['font-family'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['font-size'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['font-size-adjust'] = 'CSS2.0,CSS3.0'; $data['csstidy']['all_properties']['font-stretch'] = 'CSS2.0,CSS3.0'; $data['csstidy']['all_properties']['font-style'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['font-variant'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['font-weight'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['gap'] = 'CSS3.0'; $data['csstidy']['all_properties']['grid'] = 'CSS3.0'; $data['csstidy']['all_properties']['grid-area'] = 'CSS3.0'; $data['csstidy']['all_properties']['grid-auto-columns'] = 'CSS3.0'; $data['csstidy']['all_properties']['grid-auto-flow'] = 'CSS3.0'; $data['csstidy']['all_properties']['grid-auto-rows'] = 'CSS3.0'; $data['csstidy']['all_properties']['grid-column'] = 'CSS3.0'; $data['csstidy']['all_properties']['grid-columns'] = 'CSS3.0'; $data['csstidy']['all_properties']['grid-column-end'] = 'CSS3.0'; $data['csstidy']['all_properties']['grid-column-gap'] = 'CSS3.0'; $data['csstidy']['all_properties']['grid-column-start'] = 'CSS3.0'; $data['csstidy']['all_properties']['grid-gap'] = 'CSS3.0'; $data['csstidy']['all_properties']['grid-row'] = 'CSS3.0'; $data['csstidy']['all_properties']['grid-rows'] = 'CSS3.0'; $data['csstidy']['all_properties']['grid-row-end'] = 'CSS3.0'; $data['csstidy']['all_properties']['grid-row-gap'] = 'CSS3.0'; $data['csstidy']['all_properties']['grid-row-start'] = 'CSS3.0'; $data['csstidy']['all_properties']['grid-template'] = 'CSS3.0'; $data['csstidy']['all_properties']['grid-template-areas'] = 'CSS3.0'; $data['csstidy']['all_properties']['grid-template-columns'] = 'CSS3.0'; $data['csstidy']['all_properties']['grid-template-rows'] = 'CSS3.0'; $data['csstidy']['all_properties']['hanging-punctuation'] = 'CSS3.0'; $data['csstidy']['all_properties']['height'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['hyphenate-after'] = 'CSS3.0'; $data['csstidy']['all_properties']['hyphenate-before'] = 'CSS3.0'; $data['csstidy']['all_properties']['hyphenate-character'] = 'CSS3.0'; $data['csstidy']['all_properties']['hyphenate-lines'] = 'CSS3.0'; $data['csstidy']['all_properties']['hyphenate-resource'] = 'CSS3.0'; $data['csstidy']['all_properties']['hyphens'] = 'CSS3.0'; $data['csstidy']['all_properties']['icon'] = 'CSS3.0'; $data['csstidy']['all_properties']['image-orientation'] = 'CSS3.0'; $data['csstidy']['all_properties']['image-rendering'] = 'CSS3.0'; $data['csstidy']['all_properties']['image-resolution'] = 'CSS3.0'; $data['csstidy']['all_properties']['inline-box-align'] = 'CSS3.0'; $data['csstidy']['all_properties']['justify-content'] = 'CSS3.0'; $data['csstidy']['all_properties']['justify-items'] = 'CSS3.0'; $data['csstidy']['all_properties']['justify-self'] = 'CSS3.0'; $data['csstidy']['all_properties']['left'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['letter-spacing'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['line-break'] = 'CSS3.0'; $data['csstidy']['all_properties']['line-height'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['line-stacking'] = 'CSS3.0'; $data['csstidy']['all_properties']['line-stacking-ruby'] = 'CSS3.0'; $data['csstidy']['all_properties']['line-stacking-shift'] = 'CSS3.0'; $data['csstidy']['all_properties']['line-stacking-strategy'] = 'CSS3.0'; $data['csstidy']['all_properties']['list-style'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['list-style-image'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['list-style-position'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['list-style-type'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['margin'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['margin-bottom'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['margin-left'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['margin-right'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['margin-top'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['margin-block'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['margin-block-start'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['margin-block-end'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['margin-inline'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['margin-inline-start'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['margin-inline-end'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['marker-offset'] = 'CSS2.0,CSS3.0'; $data['csstidy']['all_properties']['marks'] = 'CSS2.0,CSS3.0'; $data['csstidy']['all_properties']['marquee-direction'] = 'CSS3.0'; $data['csstidy']['all_properties']['marquee-loop'] = 'CSS3.0'; $data['csstidy']['all_properties']['marquee-play-count'] = 'CSS3.0'; $data['csstidy']['all_properties']['marquee-speed'] = 'CSS3.0'; $data['csstidy']['all_properties']['marquee-style'] = 'CSS3.0'; $data['csstidy']['all_properties']['mask-clip'] = 'CSS3.0'; $data['csstidy']['all_properties']['mask-composite'] = 'CSS3.0'; $data['csstidy']['all_properties']['mask-image'] = 'CSS3.0'; $data['csstidy']['all_properties']['mask-mode'] = 'CSS3.0'; $data['csstidy']['all_properties']['mask-origin'] = 'CSS3.0'; $data['csstidy']['all_properties']['mask-position'] = 'CSS3.0'; $data['csstidy']['all_properties']['mask-repeat'] = 'CSS3.0'; $data['csstidy']['all_properties']['mask-size'] = 'CSS3.0'; $data['csstidy']['all_properties']['max-height'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['max-width'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['min-height'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['min-width'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['move-to'] = 'CSS3.0'; $data['csstidy']['all_properties']['nav-down'] = 'CSS3.0'; $data['csstidy']['all_properties']['nav-index'] = 'CSS3.0'; $data['csstidy']['all_properties']['nav-left'] = 'CSS3.0'; $data['csstidy']['all_properties']['nav-right'] = 'CSS3.0'; $data['csstidy']['all_properties']['nav-up'] = 'CSS3.0'; $data['csstidy']['all_properties']['object-fit'] = 'CSS3.0'; $data['csstidy']['all_properties']['object-position'] = 'CSS3.0'; $data['csstidy']['all_properties']['opacity'] = 'CSS3.0'; $data['csstidy']['all_properties']['order'] = 'CSS3.0'; $data['csstidy']['all_properties']['orphans'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['outline'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['outline-color'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['outline-offset'] = 'CSS3.0'; $data['csstidy']['all_properties']['outline-style'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['outline-width'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['overflow'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['overflow-style'] = 'CSS3.0'; $data['csstidy']['all_properties']['overflow-wrap'] = 'CSS3.0'; $data['csstidy']['all_properties']['overflow-x'] = 'CSS3.0'; $data['csstidy']['all_properties']['overflow-y'] = 'CSS3.0'; $data['csstidy']['all_properties']['padding'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['padding-bottom'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['padding-left'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['padding-right'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['padding-top'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['page'] = 'CSS2.0,CSS3.0'; $data['csstidy']['all_properties']['page-break-after'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['page-break-before'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['page-break-inside'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['page-policy'] = 'CSS3.0'; $data['csstidy']['all_properties']['pause'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['pause-after'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['pause-before'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['perspective'] = 'CSS3.0'; $data['csstidy']['all_properties']['perspective-origin'] = 'CSS3.0'; $data['csstidy']['all_properties']['phonemes'] = 'CSS3.0'; $data['csstidy']['all_properties']['pitch'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['pitch-range'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['play-during'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['position'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['presentation-level'] = 'CSS3.0'; $data['csstidy']['all_properties']['punctuation-trim'] = 'CSS3.0'; $data['csstidy']['all_properties']['quotes'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['rendering-intent'] = 'CSS3.0'; $data['csstidy']['all_properties']['resize'] = 'CSS3.0'; $data['csstidy']['all_properties']['rest'] = 'CSS3.0'; $data['csstidy']['all_properties']['rest-after'] = 'CSS3.0'; $data['csstidy']['all_properties']['rest-before'] = 'CSS3.0'; $data['csstidy']['all_properties']['richness'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['right'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['rotation'] = 'CSS3.0'; $data['csstidy']['all_properties']['rotation-point'] = 'CSS3.0'; $data['csstidy']['all_properties']['ruby-align'] = 'CSS3.0'; $data['csstidy']['all_properties']['ruby-overhang'] = 'CSS3.0'; $data['csstidy']['all_properties']['ruby-position'] = 'CSS3.0'; $data['csstidy']['all_properties']['ruby-span'] = 'CSS3.0'; $data['csstidy']['all_properties']['scroll-behavior'] = 'CSS3.0'; $data['csstidy']['all_properties']['scroll-margin'] = 'CSS3.0'; $data['csstidy']['all_properties']['scroll-margin-block'] = 'CSS3.0'; $data['csstidy']['all_properties']['scroll-margin-block-end'] = 'CSS3.0'; $data['csstidy']['all_properties']['scroll-margin-block-start'] = 'CSS3.0'; $data['csstidy']['all_properties']['scroll-margin-bottom'] = 'CSS3.0'; $data['csstidy']['all_properties']['scroll-margin-inline'] = 'CSS3.0'; $data['csstidy']['all_properties']['scroll-margin-inline-end'] = 'CSS3.0'; $data['csstidy']['all_properties']['scroll-margin-inline-start'] = 'CSS3.0'; $data['csstidy']['all_properties']['scroll-margin-left'] = 'CSS3.0'; $data['csstidy']['all_properties']['scroll-margin-right'] = 'CSS3.0'; $data['csstidy']['all_properties']['scroll-margin-top'] = 'CSS3.0'; $data['csstidy']['all_properties']['scroll-padding'] = 'CSS3.0'; $data['csstidy']['all_properties']['scroll-padding-block'] = 'CSS3.0'; $data['csstidy']['all_properties']['scroll-padding-block-end'] = 'CSS3.0'; $data['csstidy']['all_properties']['scroll-padding-block-start'] = 'CSS3.0'; $data['csstidy']['all_properties']['scroll-padding-bottom'] = 'CSS3.0'; $data['csstidy']['all_properties']['scroll-padding-inline'] = 'CSS3.0'; $data['csstidy']['all_properties']['scroll-padding-inline-end'] = 'CSS3.0'; $data['csstidy']['all_properties']['scroll-padding-inline-start'] = 'CSS3.0'; $data['csstidy']['all_properties']['scroll-padding-left'] = 'CSS3.0'; $data['csstidy']['all_properties']['scroll-padding-right'] = 'CSS3.0'; $data['csstidy']['all_properties']['scroll-padding-top'] = 'CSS3.0'; $data['csstidy']['all_properties']['scroll-snap-align'] = 'CSS3.0'; $data['csstidy']['all_properties']['scroll-snap-stop'] = 'CSS3.0'; $data['csstidy']['all_properties']['size'] = 'CSS2.0,CSS3.0'; $data['csstidy']['all_properties']['speak'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['speak-header'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['speak-numeral'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['speak-punctuation'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['speech-rate'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['src'] = 'CSS3.0'; $data['csstidy']['all_properties']['stress'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['string-set'] = 'CSS3.0'; $data['csstidy']['all_properties']['stroke'] = 'CSS3.0'; $data['csstidy']['all_properties']['tab-size'] = 'CSS3.0'; $data['csstidy']['all_properties']['table-layout'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['target'] = 'CSS3.0'; $data['csstidy']['all_properties']['target-name'] = 'CSS3.0'; $data['csstidy']['all_properties']['target-new'] = 'CSS3.0'; $data['csstidy']['all_properties']['target-position'] = 'CSS3.0'; $data['csstidy']['all_properties']['text-align'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['text-align-last'] = 'CSS3.0'; $data['csstidy']['all_properties']['text-decoration'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['text-decoration-color'] = 'CSS3.0'; $data['csstidy']['all_properties']['text-decoration-line'] = 'CSS3.0'; $data['csstidy']['all_properties']['text-decoration-skip'] = 'CSS3.0'; $data['csstidy']['all_properties']['text-decoration-style'] = 'CSS3.0'; $data['csstidy']['all_properties']['text-emphasis'] = 'CSS3.0'; $data['csstidy']['all_properties']['text-emphasis-color'] = 'CSS3.0'; $data['csstidy']['all_properties']['text-emphasis-position'] = 'CSS3.0'; $data['csstidy']['all_properties']['text-emphasis-style'] = 'CSS3.0'; $data['csstidy']['all_properties']['text-height'] = 'CSS3.0'; $data['csstidy']['all_properties']['text-indent'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['text-justify'] = 'CSS3.0'; $data['csstidy']['all_properties']['text-outline'] = 'CSS3.0'; $data['csstidy']['all_properties']['text-shadow'] = 'CSS2.0,CSS3.0'; $data['csstidy']['all_properties']['text-size-adjust'] = 'CSS3.0'; $data['csstidy']['all_properties']['text-space-collapse'] = 'CSS3.0'; $data['csstidy']['all_properties']['text-transform'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['text-underline-offset'] = 'CSS3.0'; $data['csstidy']['all_properties']['text-underline-position'] = 'CSS3.0'; $data['csstidy']['all_properties']['text-wrap'] = 'CSS3.0'; $data['csstidy']['all_properties']['top'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['transform'] = 'CSS3.0'; $data['csstidy']['all_properties']['transform-origin'] = 'CSS3.0'; $data['csstidy']['all_properties']['transform-style'] = 'CSS3.0'; $data['csstidy']['all_properties']['transition'] = 'CSS3.0'; $data['csstidy']['all_properties']['transition-delay'] = 'CSS3.0'; $data['csstidy']['all_properties']['transition-duration'] = 'CSS3.0'; $data['csstidy']['all_properties']['transition-property'] = 'CSS3.0'; $data['csstidy']['all_properties']['transition-timing-function'] = 'CSS3.0'; $data['csstidy']['all_properties']['unicode-bidi'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['vertical-align'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['visibility'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['voice-balance'] = 'CSS3.0'; $data['csstidy']['all_properties']['voice-duration'] = 'CSS3.0'; $data['csstidy']['all_properties']['voice-family'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['voice-pitch'] = 'CSS3.0'; $data['csstidy']['all_properties']['voice-pitch-range'] = 'CSS3.0'; $data['csstidy']['all_properties']['voice-rate'] = 'CSS3.0'; $data['csstidy']['all_properties']['voice-stress'] = 'CSS3.0'; $data['csstidy']['all_properties']['voice-volume'] = 'CSS3.0'; $data['csstidy']['all_properties']['volume'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['white-space'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['widows'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['width'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['word-break'] = 'CSS3.0'; $data['csstidy']['all_properties']['word-spacing'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['word-wrap'] = 'CSS3.0'; $data['csstidy']['all_properties']['z-index'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['zoom'] = 'CSS3.0'; $data['csstidy']['all_properties']['--custom'] = 'CSS3.0'; // Placeholder for custom properties, used in TablePress_CSSTidy::property_is_valid(). /** * An array containing all properties that can accept a quoted string as a value. */ $data['csstidy']['quoted_string_properties'] = array( 'content', 'font', 'font-family', 'quotes' ); /** * An array containing all properties that can be defined multiple times without being overwritten. * All unit values are included so that units like rem can be supported with fallbacks to px or em. */ $data['csstidy']['multiple_properties'] = array_merge( $data['csstidy']['color_values'], $data['csstidy']['unit_values'], array( 'transition', 'background-image', 'border-image', 'list-style-image' ) ); /** * An array containing all predefined templates. * * @see CSSTidy::load_template() */ $data['csstidy']['predefined_templates']['default'][] = ''; // string before @rule. $data['csstidy']['predefined_templates']['default'][] = " {\n"; // bracket after @-rule. $data['csstidy']['predefined_templates']['default'][] = ''; // string before selector. $data['csstidy']['predefined_templates']['default'][] = " {\n"; // bracket after selector. $data['csstidy']['predefined_templates']['default'][] = "\t"; // string before property. $data['csstidy']['predefined_templates']['default'][] = ' '; // string after property+before value. $data['csstidy']['predefined_templates']['default'][] = ';'; // string after value. $data['csstidy']['predefined_templates']['default'][] = '}'; // closing bracket - selector. $data['csstidy']['predefined_templates']['default'][] = "\n\n"; // space between blocks {...}. $data['csstidy']['predefined_templates']['default'][] = "\n}\n\n"; // closing bracket @-rule. $data['csstidy']['predefined_templates']['default'][] = "\t"; // indent in @-rule. $data['csstidy']['predefined_templates']['default'][] = ''; // before comment. $data['csstidy']['predefined_templates']['default'][] = "\n"; // after comment. $data['csstidy']['predefined_templates']['default'][] = "\n"; // after each line @-rule. $data['csstidy']['predefined_templates']['low'][] = ''; $data['csstidy']['predefined_templates']['low'][] = ' {' . "\n"; $data['csstidy']['predefined_templates']['low'][] = ''; $data['csstidy']['predefined_templates']['low'][] = '' . "\n" . '{' . "\n"; $data['csstidy']['predefined_templates']['low'][] = ' '; $data['csstidy']['predefined_templates']['low'][] = ''; $data['csstidy']['predefined_templates']['low'][] = ';' . "\n"; $data['csstidy']['predefined_templates']['low'][] = '}'; $data['csstidy']['predefined_templates']['low'][] = "\n\n"; $data['csstidy']['predefined_templates']['low'][] = "\n" . '}' . "\n\n"; $data['csstidy']['predefined_templates']['low'][] = ' '; $data['csstidy']['predefined_templates']['low'][] = ''; $data['csstidy']['predefined_templates']['low'][] = "\n"; $data['csstidy']['predefined_templates']['low'][] = "\n"; $data['csstidy']['predefined_templates']['high'][] = ''; $data['csstidy']['predefined_templates']['high'][] = ' {' . "\n"; $data['csstidy']['predefined_templates']['high'][] = ''; $data['csstidy']['predefined_templates']['high'][] = '{'; $data['csstidy']['predefined_templates']['high'][] = ''; $data['csstidy']['predefined_templates']['high'][] = ''; $data['csstidy']['predefined_templates']['high'][] = ';'; $data['csstidy']['predefined_templates']['high'][] = '}'; $data['csstidy']['predefined_templates']['high'][] = "\n"; $data['csstidy']['predefined_templates']['high'][] = "\n" . '}' . "\n" . ''; $data['csstidy']['predefined_templates']['high'][] = ''; $data['csstidy']['predefined_templates']['high'][] = ''; $data['csstidy']['predefined_templates']['high'][] = "\n"; $data['csstidy']['predefined_templates']['high'][] = "\n"; $data['csstidy']['predefined_templates']['highest'][] = ''; $data['csstidy']['predefined_templates']['highest'][] = '{'; $data['csstidy']['predefined_templates']['highest'][] = ''; $data['csstidy']['predefined_templates']['highest'][] = '{'; $data['csstidy']['predefined_templates']['highest'][] = ''; $data['csstidy']['predefined_templates']['highest'][] = ''; $data['csstidy']['predefined_templates']['highest'][] = ';'; $data['csstidy']['predefined_templates']['highest'][] = '}'; $data['csstidy']['predefined_templates']['highest'][] = ''; $data['csstidy']['predefined_templates']['highest'][] = '}'; $data['csstidy']['predefined_templates']['highest'][] = ''; $data['csstidy']['predefined_templates']['highest'][] = ''; $data['csstidy']['predefined_templates']['highest'][] = "\n"; $data['csstidy']['predefined_templates']['highest'][] = ''; // Support browser prefixes for properties only in the latest CSS draft. foreach ( $data['csstidy']['all_properties'] as $property => $levels ) { if ( ! str_contains( $levels, ',' ) ) { $data['csstidy']['all_properties'][ '-moz-' . $property ] = $levels; $data['csstidy']['all_properties'][ '-webkit-' . $property ] = $levels; $data['csstidy']['all_properties'][ '-ms-' . $property ] = $levels; $data['csstidy']['all_properties'][ '-o-' . $property ] = $levels; if ( in_array( $property, $data['csstidy']['unit_values'], true ) ) { $data['csstidy']['unit_values'][] = '-moz-' . $property; $data['csstidy']['unit_values'][] = '-webkit-' . $property; $data['csstidy']['unit_values'][] = '-ms-' . $property; $data['csstidy']['unit_values'][] = '-o-' . $property; } if ( in_array( $property, $data['csstidy']['color_values'], true ) ) { $data['csstidy']['color_values'][] = '-moz-' . $property; $data['csstidy']['color_values'][] = '-webkit-' . $property; $data['csstidy']['color_values'][] = '-ms-' . $property; $data['csstidy']['color_values'][] = '-o-' . $property; } } } // Allow vendor prefixes for any property that is allowed to be used multiple times inside a single selector. foreach ( $data['csstidy']['multiple_properties'] as $property ) { if ( '-' !== $property[0] ) { $data['csstidy']['multiple_properties'][] = '-o-' . $property; $data['csstidy']['multiple_properties'][] = '-ms-' . $property; $data['csstidy']['multiple_properties'][] = '-webkit-' . $property; $data['csstidy']['multiple_properties'][] = '-moz-' . $property; } } /** * Non-standard CSS properties. They're not part of any spec, but we say * they're in all of them so that we can support them. */ $data['csstidy']['all_properties']['-webkit-user-select'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['-ms-user-select'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['user-select'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['-webkit-filter'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['-moz-filter'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['-ms-filter'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['filter'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['scrollbar-face-color'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['-ms-interpolation-mode'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['text-rendering'] = 'CSS2.0,CSS2.1,CSS3.0'; $data['csstidy']['all_properties']['-webkit-transform-origin-x'] = 'CSS3.0'; $data['csstidy']['all_properties']['-webkit-transform-origin-y'] = 'CSS3.0'; $data['csstidy']['all_properties']['-webkit-transform-origin-z'] = 'CSS3.0'; $data['csstidy']['all_properties']['-webkit-font-smoothing'] = 'CSS3.0'; $data['csstidy']['all_properties']['-moz-osx-font-smoothing'] = 'CSS3.0'; $data['csstidy']['all_properties']['-font-smooth'] = 'CSS3.0'; $data['csstidy']['all_properties']['text-overflow'] = 'CSS3.0'; $data['csstidy']['all_properties']['-o-text-overflow'] = 'CSS3.0'; $data['csstidy']['all_properties']['-ms-touch-action'] = 'CSS3.0'; $data['csstidy']['all_properties']['-webkit-overflow-scrolling'] = 'CSS3.0'; $data['csstidy']['all_properties']['pointer-events'] = 'CSS3.0'; $data['csstidy']['all_properties']['font-feature-settings'] = 'CSS3.0'; $data['csstidy']['all_properties']['font-kerning'] = 'CSS3.0'; $data['csstidy']['all_properties']['font-language-override'] = 'CSS3.0'; $data['csstidy']['all_properties']['font-synthesis'] = 'CSS3.0'; $data['csstidy']['all_properties']['font-variant-alternates'] = 'CSS3.0'; $data['csstidy']['all_properties']['font-variant-caps'] = 'CSS3.0'; $data['csstidy']['all_properties']['font-variant-east-asian'] = 'CSS3.0'; $data['csstidy']['all_properties']['font-variant-ligatures'] = 'CSS3.0'; $data['csstidy']['all_properties']['font-variant-numeric'] = 'CSS3.0'; $data['csstidy']['all_properties']['font-variant-position'] = 'CSS3.0'; $data['csstidy']['all_properties']['font-variation-settings'] = 'CSS3.0'; $data['csstidy']['all_properties']['line-height-step'] = 'CSS3.0'; $data['csstidy']['all_properties']['-webkit-text-fill-color'] = 'CSS3.0'; $data['csstidy']['all_properties']['-webkit-backdrop-filter'] = 'CSS3.0'; $data['csstidy']['all_properties']['backdrop-filter'] = 'CSS3.0'; $data['csstidy']['all_properties']['-webkit-box-orient'] = 'CSS3.0'; $data['csstidy']['all_properties']['-webkit-line-clamp'] = 'CSS3.0'; $data['csstidy']['all_properties']['-webkit-box-orient'] = 'CSS3.0'; $data['csstidy']['all_properties']['contain'] = 'CSS3.0'; $data['csstidy']['all_properties']['contain-intrinsic-size'] = 'CSS3.0'; $data['csstidy']['all_properties']['container'] = 'CSS3.0'; $data['csstidy']['all_properties']['container-name'] = 'CSS3.0'; $data['csstidy']['all_properties']['container-type'] = 'CSS3.0'; $data['csstidy']['all_properties']['content-visibility'] = 'CSS3.0'; return $data; PK!R)00)libraries/csstidy/class.csstidy_print.phpnu[parser = $csstidy; $this->css = &$csstidy->css; $this->template = &$csstidy->template; $this->tokens = &$csstidy->tokens; $this->charset = &$csstidy->charset; $this->import = &$csstidy->import; $this->namespace = &$csstidy->namespace; } /** * Resets output_css and output_css_plain (new CSS code). * * @since 1.0 */ public function _reset(): void { $this->output_css = ''; $this->output_css_plain = ''; } /** * Returns the CSS code as plain text. * * @since 1.0 * * @param string $default_media Optional. Default @media to add to selectors without any @media. * @return string Plain CSS. */ public function plain( string $default_media = '' ): string { $this->_print( true, $default_media ); return $this->output_css_plain; } /** * Returns the formatted CSS Code and saves it into $this->output_css and $this->output_css_plain. * * @since 1.0 * * @param bool $plain Optional. Plain text or not. * @param string $default_media Optional. Default @media to add to selectors without any @media. */ protected function _print( bool $plain = false, string $default_media = '' ): void { if ( $this->output_css && $this->output_css_plain ) { return; } $output = ''; if ( ! $this->parser->get_cfg( 'preserve_css' ) ) { $this->_convert_raw_css( $default_media ); } $template = &$this->template; if ( $plain ) { $template = array_map( 'strip_tags', $template ); } if ( $this->parser->get_cfg( 'timestamp' ) ) { array_unshift( $this->tokens, array( TablePress_CSSTidy::COMMENT, ' CSSTidy: ' . gmdate( 'r' ) . ' ' ) ); } if ( ! empty( $this->charset ) ) { $output .= $template[0] . '@charset ' . $template[5] . $this->charset . $template[6] . $template[13]; } if ( ! empty( $this->import ) ) { for ( $i = 0, $size = count( $this->import ); $i < $size; $i++ ) { $import_components = explode( ' ', $this->import[ $i ] ); if ( str_starts_with( $import_components[0], 'url(' ) && str_ends_with( $import_components[0], ')' ) ) { $import_components[0] = '\'' . trim( substr( $import_components[0], 4, -1 ), "'\"" ) . '\''; $this->import[ $i ] = implode( ' ', $import_components ); $this->parser->log( 'Optimised @import : Removed "url("', 'Information' ); } elseif ( ! preg_match( '/^".+"$/', $this->import[ $i ] ) ) { // Fixes a bug for @import ".." instead of the expected @import url(".."). // If it comes in due to @import ".." the "" will be missing and the output will become @import .. (which is an error). $this->import[ $i ] = '"' . $this->import[ $i ] . '"'; } $output .= $template[0] . '@import ' . $template[5] . $this->import[ $i ] . $template[6] . $template[13]; } } if ( ! empty( $this->namespace ) ) { if ( false !== ( $p = strpos( $this->namespace, 'url(' ) ) && str_ends_with( $this->namespace, ')' ) ) { // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.Found,Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure $this->namespace = substr_replace( $this->namespace, '"', $p, 4 ); $this->namespace = substr( $this->namespace, 0, -1 ) . '"'; $this->parser->log( 'Optimised @namespace : Removed "url("', 'Information' ); } $output .= $template[0] . '@namespace ' . $template[5] . $this->namespace . $template[6] . $template[13]; } $in_at_out = array(); $out = &$output; $indent_level = 0; foreach ( $this->tokens as $key => $token ) { switch ( $token[0] ) { case TablePress_CSSTidy::AT_START: if ( $this->parser->get_cfg( 'preserve_css' ) ) { $token[1] = str_replace( ',', ",\n", $token[1] ); } $out .= $template[0] . $this->_htmlsp( $token[1], $plain ) . $template[1]; ++$indent_level; if ( ! isset( $in_at_out[ $indent_level ] ) ) { $in_at_out[ $indent_level ] = ''; } $out = &$in_at_out[ $indent_level ]; break; case TablePress_CSSTidy::SEL_START: if ( $this->parser->get_cfg( 'lowercase_s' ) ) { $token[1] = strtolower( $token[1] ); } if ( $this->parser->get_cfg( 'preserve_css' ) ) { $token[1] = str_replace( ',', ",\n", $token[1] ); } $out .= ( '@' !== $token[1][0] ) ? $template[2] . $this->_htmlsp( $token[1], $plain ) : $template[0] . $this->_htmlsp( $token[1], $plain ); $out .= $template[3]; break; case TablePress_CSSTidy::PROPERTY: if ( 2 === $this->parser->get_cfg( 'case_properties' ) ) { $token[1] = strtoupper( $token[1] ); } elseif ( 1 === $this->parser->get_cfg( 'case_properties' ) ) { $token[1] = strtolower( $token[1] ); } $out .= $template[4] . $this->_htmlsp( $token[1], $plain ) . ':' . $template[5]; break; case TablePress_CSSTidy::VALUE: $out .= $this->_htmlsp( $token[1], $plain ); if ( TablePress_CSSTidy::SEL_END === $this->_seeknocomment( $key, 1 ) && $this->parser->get_cfg( 'remove_last_;' ) ) { $out .= str_replace( ';', '', $template[6] ); } else { $out .= $template[6]; } if ( $this->parser->get_cfg( 'preserve_css' ) ) { $out .= ( TablePress_CSSTidy::COMMENT === $this->tokens[ $key + 1 ][0] ) ? ' ' : "\n"; } break; case TablePress_CSSTidy::SEL_END: $out .= $template[7]; if ( TablePress_CSSTidy::AT_END !== $this->_seeknocomment( $key, 1 ) ) { $out .= $template[8]; } break; case TablePress_CSSTidy::AT_END: if ( strlen( $template[10] ) ) { // Indent the block we are closing. $out = str_replace( "\n\n", "\r\n", $out ); // Don't fill empty lines. $out = str_replace( "\n", "\n" . $template[10], $out ); $out = str_replace( "\r\n", "\n\n", $out ); } if ( $indent_level > 1 ) { $out = &$in_at_out[ $indent_level - 1 ]; } else { $out = &$output; } $out .= $template[10] . $in_at_out[ $indent_level ]; if ( TablePress_CSSTidy::AT_END !== $this->_seeknocomment( $key, 1 ) ) { $out .= $template[9]; } else { $out .= rtrim( $template[9] ); } unset( $in_at_out[ $indent_level ] ); --$indent_level; break; case TablePress_CSSTidy::IMPORTANT_COMMENT: case TablePress_CSSTidy::COMMENT: $out .= $template[11] . '/*' . $this->_htmlsp( $token[1], $plain ) . '*/' . $template[12]; break; } } if ( ! $this->parser->get_cfg( 'preserve_css' ) ) { $output = str_replace( ' !important', '!important', $output ); } $output = trim( $output ); if ( ! $plain ) { $this->output_css = $output; $this->_print( true ); } else { // If using spaces in the template, don't want these to appear in the plain output. $this->output_css_plain = str_replace( ' ', '', $output ); } } /** * Gets the next token type which is $move away from $key, excluding comments. * * @since 1.0 * * @param int $key Current position. * @param int $move Move this far. * @return mixed A token type. */ protected function _seeknocomment( int $key, int $move ) /* : mixed */ { $go = ( $move > 0 ) ? 1 : -1; for ( $i = $key + 1; abs( $key - $i ) - 1 < abs( $move ); $i += $go ) { // phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed if ( ! isset( $this->tokens[ $i ] ) ) { return; } if ( TablePress_CSSTidy::COMMENT === $this->tokens[ $i ][0] ) { ++$move; continue; } return $this->tokens[ $i ][0]; } } /** * Converts $this->css array to a raw array ($this->tokens). * * @since 1.0.0 * * @param string $default_media Optional. Default @media to add to selectors without any @media. */ protected function _convert_raw_css( string $default_media = '' ): void { $this->tokens = array(); $sort_selectors = $this->parser->get_cfg( 'sort_selectors' ); $sort_properties = $this->parser->get_cfg( 'sort_properties' ); // Important comment section? if ( isset( $this->css['!'] ) ) { $this->parser->_add_token( TablePress_CSSTidy::IMPORTANT_COMMENT, rtrim( $this->css['!'] ), true ); unset( $this->css['!'] ); } foreach ( $this->css as $medium => $val ) { if ( $sort_selectors ) { ksort( $val ); } if ( (int) $medium < TablePress_CSSTidy::DEFAULT_AT ) { // An empty medium (containing @font-face or other @) produces no container. if ( strlen( trim( $medium ) ) ) { $parts_to_open = explode( '{', $medium ); foreach ( $parts_to_open as $part ) { $this->parser->_add_token( TablePress_CSSTidy::AT_START, $part, true ); } } } elseif ( $default_media ) { $this->parser->_add_token( TablePress_CSSTidy::AT_START, $default_media, true ); } foreach ( $val as $selector => $vali ) { if ( $sort_properties ) { ksort( $vali ); } $this->parser->_add_token( TablePress_CSSTidy::SEL_START, $selector, true ); $invalid = array( '*' => array(), // IE7 hacks first. '_' => array(), // IE6 hacks. '/' => array(), // IE6 hacks. '-' => array(), // IE6 hacks. ); foreach ( $vali as $property => $valj ) { if ( ! str_starts_with( $property, '//' ) ) { $matches = array(); if ( $sort_properties && preg_match( '/^(\*|_|\/|-)(?!(ms|moz|o\b|xv|atsc|wap|khtml|webkit|ah|hp|ro|rim|tc)-)/', $property, $matches ) ) { $invalid[ $matches[1] ][ $property ] = $valj; } else { $this->parser->_add_token( TablePress_CSSTidy::PROPERTY, $property, true ); $this->parser->_add_token( TablePress_CSSTidy::VALUE, $valj, true ); } } } foreach ( $invalid as $prefix => $props ) { foreach ( $props as $property => $valj ) { $this->parser->_add_token( TablePress_CSSTidy::PROPERTY, $property, true ); $this->parser->_add_token( TablePress_CSSTidy::VALUE, $valj, true ); } } $this->parser->_add_token( TablePress_CSSTidy::SEL_END, $selector, true ); } if ( (int) $medium < TablePress_CSSTidy::DEFAULT_AT ) { // An empty medium (containing @font-face or other @) produces no container. if ( strlen( trim( $medium ) ) ) { $parts_to_close = explode( '{', $medium ); foreach ( array_reverse( $parts_to_close ) as $part ) { $this->parser->_add_token( TablePress_CSSTidy::AT_END, $part, true ); } } } elseif ( $default_media ) { $this->parser->_add_token( TablePress_CSSTidy::AT_END, $default_media, true ); } } } /** * Same as htmlspecialchars, only that chars are not replaced if $plain is true. This makes print_code() cleaner. * * @since 1.0 * * @see CSSTidy_print::_print() * * @param string $a_string String. * @param bool $plain Whether to print plain. * @return string Original string or htmlspecialchars() version. */ protected function _htmlsp( string $a_string, bool $plain ): string { if ( ! $plain ) { return htmlspecialchars( $a_string, ENT_QUOTES, 'utf-8' ); } return $a_string; } } // class TablePress_CSSTidy_Print PK!libraries/csstidy/index.phpnu[parser = $csstidy; $this->css = &$csstidy->css; $this->sub_value = &$csstidy->sub_value; $this->at = &$csstidy->at; $this->selector = &$csstidy->selector; $this->property = &$csstidy->property; $this->value = &$csstidy->value; } /** * Optimises $css after parsing. * * @since 1.0.0 */ public function postparse(): void { if ( $this->parser->get_cfg( 'preserve_css' ) ) { return; } if ( 2 === (int) $this->parser->get_cfg( 'merge_selectors' ) ) { foreach ( $this->css as $medium => $value ) { if ( is_array( $value ) ) { $this->merge_selectors( $this->css[ $medium ] ); } } } if ( $this->parser->get_cfg( 'discard_invalid_selectors' ) ) { foreach ( $this->css as $medium => $value ) { if ( is_array( $value ) ) { $this->discard_invalid_selectors( $this->css[ $medium ] ); } } } if ( $this->parser->get_cfg( 'optimise_shorthands' ) > 0 ) { foreach ( $this->css as $medium => $value ) { if ( is_array( $value ) ) { foreach ( $value as $selector => $value1 ) { $this->css[ $medium ][ $selector ] = $this->merge_4value_shorthands( $this->css[ $medium ][ $selector ] ); if ( $this->parser->get_cfg( 'optimise_shorthands' ) < 2 ) { continue; } $this->css[ $medium ][ $selector ] = $this->merge_font( $this->css[ $medium ][ $selector ] ); if ( $this->parser->get_cfg( 'optimise_shorthands' ) < 3 ) { continue; } $this->css[ $medium ][ $selector ] = $this->merge_bg( $this->css[ $medium ][ $selector ] ); if ( empty( $this->css[ $medium ][ $selector ] ) ) { unset( $this->css[ $medium ][ $selector ] ); } } } } } } /** * Optimises values * * @since 1.0.0 */ public function value(): void { $shorthands = &$this->parser->data['csstidy']['shorthands']; // Optimise shorthand properties. if ( isset( $shorthands[ $this->property ] ) && $this->parser->get_cfg( 'optimise_shorthands' ) > 0 ) { $temp = $this->shorthand( $this->value ); // FIXME - move. if ( $temp !== $this->value ) { $this->parser->log( 'Optimised shorthand notation (' . $this->property . '): Changed "' . $this->value . '" to "' . $temp . '"', 'Information' ); } $this->value = $temp; } // Remove whitespace at !important. if ( $this->value !== $this->compress_important( $this->value ) ) { $this->parser->log( 'Optimised !important', 'Information' ); } } /** * Optimises shorthands. * * @since 1.0.0 */ public function shorthands(): void { $shorthands = &$this->parser->data['csstidy']['shorthands']; if ( ! $this->parser->get_cfg( 'optimise_shorthands' ) || $this->parser->get_cfg( 'preserve_css' ) ) { return; } if ( 'font' === $this->property && $this->parser->get_cfg( 'optimise_shorthands' ) > 1 ) { $this->css[ $this->at ][ $this->selector ]['font'] = ''; $this->parser->merge_css_blocks( $this->at, $this->selector, $this->dissolve_short_font( $this->value ) ); } if ( 'background' === $this->property && $this->parser->get_cfg( 'optimise_shorthands' ) > 2 ) { $this->css[ $this->at ][ $this->selector ]['background'] = ''; $this->parser->merge_css_blocks( $this->at, $this->selector, $this->dissolve_short_bg( $this->value ) ); } if ( isset( $shorthands[ $this->property ] ) ) { $this->parser->merge_css_blocks( $this->at, $this->selector, $this->dissolve_4value_shorthands( $this->property, $this->value ) ); if ( is_array( $shorthands[ $this->property ] ) ) { $this->css[ $this->at ][ $this->selector ][ $this->property ] = ''; } } } /** * Optimises a sub-value. * * @since 1.0.0 */ public function subvalue(): void { $replace_colors = &$this->parser->data['csstidy']['replace_colors']; $this->sub_value = trim( $this->sub_value ); if ( '' === $this->sub_value ) { // caution : '0'. return; } $important = ''; if ( $this->parser->is_important( $this->sub_value ) ) { $important = ' !important'; } $this->sub_value = $this->parser->gvw_important( $this->sub_value ); // Compress font-weight. if ( 'font-weight' === $this->property && $this->parser->get_cfg( 'compress_font-weight' ) ) { if ( 'bold' === $this->sub_value ) { $this->sub_value = '700'; $this->parser->log( 'Optimised font-weight: Changed "bold" to "700"', 'Information' ); } elseif ( 'normal' === $this->sub_value ) { $this->sub_value = '400'; $this->parser->log( 'Optimised font-weight: Changed "normal" to "400"', 'Information' ); } } $temp = $this->compress_numbers( $this->sub_value ); if ( 0 !== strcasecmp( $temp, $this->sub_value ) ) { if ( strlen( $temp ) > strlen( $this->sub_value ) ) { $this->parser->log( 'Fixed invalid number: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Warning' ); } else { $this->parser->log( 'Optimised number: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Information' ); } $this->sub_value = $temp; } if ( $this->parser->get_cfg( 'compress_colors' ) ) { $temp = $this->cut_color( $this->sub_value ); if ( $temp !== $this->sub_value ) { if ( isset( $replace_colors[ $this->sub_value ] ) ) { $this->parser->log( 'Fixed invalid color name: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Warning' ); } else { $this->parser->log( 'Optimised color: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Information' ); } $this->sub_value = $temp; } } $this->sub_value .= $important; } /** * Compresses shorthand values. * * Example: `margin: 1px 1px 1px 1px` will become `margin: 1px`. * * @since 1.0.0 * * @param string $value Shorthand value. * @return string Compressed value. */ public function shorthand( string $value ): string { $important = ''; if ( $this->parser->is_important( $value ) ) { $values = $this->parser->gvw_important( $value ); $important = ' !important'; } else { $values = $value; } $values = explode( ' ', $values ); switch ( count( $values ) ) { case 4: if ( $values[0] === $values[1] && $values[0] === $values[2] && $values[0] === $values[3] ) { return $values[0] . $important; } elseif ( $values[1] === $values[3] && $values[0] === $values[2] ) { return $values[0] . ' ' . $values[1] . $important; } elseif ( $values[1] === $values[3] ) { return $values[0] . ' ' . $values[1] . ' ' . $values[2] . $important; } break; case 3: if ( $values[0] === $values[1] && $values[0] === $values[2] ) { return $values[0] . $important; } elseif ( $values[0] === $values[2] ) { return $values[0] . ' ' . $values[1] . $important; } break; case 2: if ( $values[0] === $values[1] ) { return $values[0] . $important; } break; } return $value; } /** * Removes unnecessary whitespace in ! important. * * @since 1.0.0 * * @param string $a_string String. * @return string Cleaned string. */ public function compress_important( string &$a_string ): string { if ( $this->parser->is_important( $a_string ) ) { $a_string = $this->parser->gvw_important( $a_string ) . ' !important'; } return $a_string; } /** * Color compression function. Converts all rgb() values to #-values and uses the short-form if possible. Also replaces 4 color names by #-values. * * @since 1.0.0 * * @param string $color Color value. * @return string Compressed color. */ public function cut_color( string $color ): string { $replace_colors = &$this->parser->data['csstidy']['replace_colors']; // If it's a string, don't touch it! if ( str_starts_with( $color, "'" ) || str_starts_with( $color, '"' ) ) { return $color; } // Complex gradient expressions. if ( str_contains( $color, '(' ) && 0 !== strncasecmp( $color, 'rgb(', 4 ) && 0 !== strncasecmp( $color, 'rgba(', 5 ) ) { // Don't touch properties within MSIE filters, those are too sensitive. if ( false !== stripos( $color, 'progid:' ) ) { return $color; } preg_match_all( ',rgba?\([^)]+\),i', $color, $matches, PREG_SET_ORDER ); if ( count( $matches ) ) { foreach ( $matches as $m ) { $color = str_replace( $m[0], $this->cut_color( $m[0] ), $color ); } } preg_match_all( ',#[0-9a-f]{6}(?=[^0-9a-f]),i', $color, $matches, PREG_SET_ORDER ); if ( count( $matches ) ) { foreach ( $matches as $m ) { $color = str_replace( $m[0], $this->cut_color( $m[0] ), $color ); } } return $color; } // rgb(0,0,0) -> #000000 (or #000 in this case later). if ( // Be sure to not corrupt a rgb with calc() value. ( 0 === strncasecmp( $color, 'rgb(', 4 ) && false === strpos( $color, '(', 4 ) ) || ( 0 === strncasecmp( $color, 'rgba(', 5 ) && false === strpos( $color, '(', 5 ) ) ) { $color_tmp = explode( '(', $color, 2 ); $color_tmp = rtrim( end( $color_tmp ), ')' ); if ( str_contains( $color_tmp, '/' ) ) { $color_tmp = explode( '/', $color_tmp, 2 ); $color_parts = explode( ' ', trim( reset( $color_tmp ) ), 3 ); while ( count( $color_parts ) < 3 ) { // phpcs:ignore Squiz.PHP.DisallowSizeFunctionsInLoops.Found $color_parts[] = 0; } $color_parts[] = end( $color_tmp ); } else { $color_parts = explode( ',', $color_tmp, 4 ); } $color_parts_count = count( $color_parts ); for ( $i = 0; $i < $color_parts_count; $i++ ) { $color_parts[ $i ] = trim( $color_parts[ $i ] ); if ( str_ends_with( $color_parts[ $i ], '%' ) ) { $color_parts[ $i ] = round( ( 255 * intval( $color_parts[ $i ] ) ) / 100 ); } elseif ( $i > 2 ) { // 4th argument is alpha layer between 0 and 1 (if not %). $color_parts[ $i ] = round( 255 * floatval( $color_parts[ $i ] ) ); } $color_parts[ $i ] = intval( $color_parts[ $i ] ); if ( $color_parts[ $i ] > 255 ) { $color_parts[ $i ] = 255; } } $color = '#'; // 3 or 4 parts depending on alpha layer. $nb = min( max( count( $color_parts ), 3 ), 4 ); for ( $i = 0; $i < $nb; $i++ ) { if ( ! isset( $color_parts[ $i ] ) ) { $color_parts[ $i ] = 0; } if ( $color_parts[ $i ] < 16 ) { $color .= '0' . dechex( $color_parts[ $i ] ); } else { $color .= dechex( $color_parts[ $i ] ); } } } // Fix bad color names. if ( isset( $replace_colors[ strtolower( $color ) ] ) ) { $color = $replace_colors[ strtolower( $color ) ]; } if ( 7 === strlen( $color ) ) { // #aabbcc -> #abc $color_temp = strtolower( $color ); if ( '#' === $color_temp[0] && $color_temp[1] === $color_temp[2] && $color_temp[3] === $color_temp[4] && $color_temp[5] === $color_temp[6] ) { $color = '#' . $color[1] . $color[3] . $color[5]; } } elseif ( 9 === strlen( $color ) ) { // #aabbccdd -> #abcd $color_temp = strtolower( $color ); if ( '#' === $color_temp[0] && $color_temp[1] === $color_temp[2] && $color_temp[3] === $color_temp[4] && $color_temp[5] === $color_temp[6] && $color_temp[7] === $color_temp[8] ) { $color = '#' . $color[1] . $color[3] . $color[5] . $color[7]; } } switch ( strtolower( $color ) ) { /* color name -> hex code */ case 'black': return '#000'; case 'fuchsia': return '#f0f'; case 'white': return '#fff'; case 'yellow': return '#ff0'; /* hex code -> color name */ case '#800000': return 'maroon'; case '#ffa500': return 'orange'; case '#808000': return 'olive'; case '#800080': return 'purple'; case '#008000': return 'green'; case '#000080': return 'navy'; case '#008080': return 'teal'; case '#c0c0c0': return 'silver'; case '#808080': return 'gray'; case '#f00': return 'red'; } return $color; } /** * Compresses numbers (ie. 1.0 becomes 1 or 1.100 becomes 1.1). * * @since 1.0.0 * * @param string $subvalue Value. * @return string Compressed value. */ public function compress_numbers( string $subvalue ): string { $unit_values = &$this->parser->data['csstidy']['unit_values']; $color_values = &$this->parser->data['csstidy']['color_values']; // for font:1em/1em sans-serif...;. if ( 'font' === $this->property ) { $temp = explode( '/', $subvalue ); } else { $temp = array( $subvalue ); } $temp_count = count( $temp ); for ( $l = 0; $l < $temp_count; $l++ ) { // If we are not dealing with a number at this point, do not optimize anything. $number = $this->analyse_css_number( $temp[ $l ] ); if ( false === $number ) { return $subvalue; } // Fix bad colors. if ( in_array( $this->property, $color_values, true ) ) { if ( 3 === strlen( $temp[ $l ] ) || 6 === strlen( $temp[ $l ] ) ) { $temp[ $l ] = '#' . $temp[ $l ]; } else { $temp[ $l ] = '0'; } continue; } if ( abs( $number[0] ) > 0 ) { if ( '' === $number[1] && in_array( $this->property, $unit_values, true ) ) { $number[1] = 'px'; } } elseif ( 's' !== $number[1] && 'ms' !== $number[1] ) { $number[1] = ''; } $temp[ $l ] = $number[0] . $number[1]; } return ( count( $temp ) > 1 ) ? $temp[0] . '/' . $temp[1] : $temp[0]; } /** * Checks if a given string is a CSS valid number. If it is, an array containing the value and unit is returned. * * @since 1.0.0 * * @param string $a_string String. * @return array{int|string, string}|false ('unit' if unit is found or '' if no unit exists, number value) or false if no number. */ public function analyse_css_number( string $a_string ) /* : array|false */ { // Most simple checks first. if ( 0 === strlen( $a_string ) || ctype_alpha( $a_string[0] ) ) { return false; } $units = &$this->parser->data['csstidy']['units']; $return = array( 0, '' ); $return[0] = (float) $a_string; if ( abs( $return[0] ) > 0 && abs( $return[0] ) < 1 ) { if ( $return[0] < 0 ) { $return[0] = '-' . ltrim( substr( $return[0], 1 ), '0' ); } else { $return[0] = ltrim( $return[0], '0' ); } } // Look for unit and split from value if exists. foreach ( $units as $unit ) { $expect_unit_at = strlen( $a_string ) - strlen( $unit ); if ( ! ( $unit_in_string = stristr( $a_string, $unit ) ) ) { // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.Found,Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure continue; } $actual_position = strpos( $a_string, $unit_in_string ); if ( $expect_unit_at === $actual_position ) { $return[1] = $unit; $a_string = substr( $a_string, 0, - strlen( $unit ) ); break; } } if ( ! is_numeric( $a_string ) ) { return false; } return $return; } /** * Merges selectors with same properties. Example: a{color:red} b{color:red} -> a,b{color:red} * Very basic and has at least one bug. Hopefully there is a replacement soon. * * @since 1.0.0 * * @param array $an_array List of selectors. This parameter is modified by reference. */ public function merge_selectors( array &$an_array ): void { $css = $an_array; foreach ( $css as $key => $value ) { if ( ! isset( $css[ $key ] ) ) { continue; } // Check if properties also exist in another selector. $keys = array(); // PHP bug (?) without $css = $an_array; here. foreach ( $css as $selector => $vali ) { if ( $selector === $key ) { continue; } if ( $css[ $key ] === $vali ) { $keys[] = $selector; } } if ( ! empty( $keys ) ) { $newsel = $key; unset( $css[ $key ] ); foreach ( $keys as $selector ) { unset( $css[ $selector ] ); $newsel .= ',' . $selector; } $css[ $newsel ] = $value; } } $an_array = $css; } /** * Removes invalid selectors and their corresponding rule-sets as * defined by 4.1.7 in REC-CSS2. This is a very rudimentary check * and should be replaced by a full-blown parsing algorithm or * regular expression. * * @since 1.0.0 * * @param array $an_array [description]. */ public function discard_invalid_selectors( array &$an_array ): void { foreach ( $an_array as $selector => $decls ) { $ok = true; $selectors = array_map( 'trim', explode( ',', $selector ) ); foreach ( $selectors as $s ) { $simple_selectors = preg_split( '/\s*[+>~\s]\s*/', $s ); foreach ( $simple_selectors as $ss ) { if ( '' === $ss ) { $ok = false; } // Could also check $ss for internal structure, but that probably would be too slow. } } if ( ! $ok ) { unset( $an_array[ $selector ] ); } } } /** * Dissolves properties like padding:10px 10px 10px to padding-top:10px;padding-bottom:10px;... * * @since 1.0.0 * * @param string $property [description]. * @param string $value [description]. * @return array [description] */ public function dissolve_4value_shorthands( string $property, string $value ): array { $return = array(); $shorthands = &$this->parser->data['csstidy']['shorthands']; if ( ! is_array( $shorthands[ $property ] ) ) { $return[ $property ] = $value; return $return; } $important = ''; if ( $this->parser->is_important( $value ) ) { $value = $this->parser->gvw_important( $value ); $important = ' !important'; } $values = explode( ' ', $value ); if ( 4 === count( $values ) ) { for ( $i = 0; $i < 4; $i++ ) { $return[ $shorthands[ $property ][ $i ] ] = $values[ $i ] . $important; } } elseif ( 3 === count( $values ) ) { $return[ $shorthands[ $property ][0] ] = $values[0] . $important; $return[ $shorthands[ $property ][1] ] = $values[1] . $important; $return[ $shorthands[ $property ][3] ] = $values[1] . $important; $return[ $shorthands[ $property ][2] ] = $values[2] . $important; } elseif ( 2 === count( $values ) ) { for ( $i = 0; $i < 4; $i++ ) { $return[ $shorthands[ $property ][ $i ] ] = ( 0 !== $i % 2 ) ? $values[1] . $important : $values[0] . $important; } } else { for ( $i = 0; $i < 4; $i++ ) { $return[ $shorthands[ $property ][ $i ] ] = $values[0] . $important; } } return $return; } /** * Explodes a string as explode() does, however, not if $sep is escaped or within a string. * * @since 1.0.0 * * @param string $sep Separator. * @param string $a_string String. * @return array [description] */ public function explode_ws( string $sep, string $a_string ): array { $status = 'st'; $to = ''; $output = array(); $num = 0; for ( $i = 0, $len = strlen( $a_string ); $i < $len; $i++ ) { switch ( $status ) { case 'st': if ( $a_string[ $i ] === $sep && ! $this->parser->escaped( $a_string, $i ) ) { ++$num; } elseif ( '"' === $a_string[ $i ] || "'" === $a_string[ $i ] || ( '(' === $a_string[ $i ] && ! $this->parser->escaped( $a_string, $i ) ) ) { $status = 'str'; $to = ( '(' === $a_string[ $i ] ) ? ')' : $a_string[ $i ]; ( isset( $output[ $num ] ) ) ? $output[ $num ] .= $a_string[ $i ] : $output[ $num ] = $a_string[ $i ]; } else { ( isset( $output[ $num ] ) ) ? $output[ $num ] .= $a_string[ $i ] : $output[ $num ] = $a_string[ $i ]; } break; case 'str': if ( $a_string[ $i ] === $to && ! $this->parser->escaped( $a_string, $i ) ) { $status = 'st'; } ( isset( $output[ $num ] ) ) ? $output[ $num ] .= $a_string[ $i ] : $output[ $num ] = $a_string[ $i ]; break; } } if ( isset( $output[0] ) ) { return $output; } else { return array( $output ); } } /** * Merges Shorthand properties again, the opposite of dissolve_4value_shorthands(). * * @since 1.0.0 * * @param array $an_array [description]. * @return array [description] */ public function merge_4value_shorthands( array $an_array ): array { $return = $an_array; $shorthands = &$this->parser->data['csstidy']['shorthands']; foreach ( $shorthands as $key => $value ) { if ( 0 !== $value && isset( $an_array[ $value[0] ], $an_array[ $value[1] ], $an_array[ $value[2] ], $an_array[ $value[3] ] ) ) { $return[ $key ] = ''; $important = ''; for ( $i = 0; $i < 4; $i++ ) { $val = $an_array[ $value[ $i ] ]; if ( $this->parser->is_important( $val ) ) { $important = ' !important'; $return[ $key ] .= $this->parser->gvw_important( $val ) . ' '; } else { $return[ $key ] .= $val . ' '; } unset( $return[ $value[ $i ] ] ); } $return[ $key ] = $this->shorthand( trim( $return[ $key ] . $important ) ); } } return $return; } /** * Dissolve background property. * * @todo Full CSS3 compliance. * * @since 1.0.0 * * @param string $str_value String value. * @return array Array. */ public function dissolve_short_bg( string $str_value ): array { // Don't try to explode background gradient! if ( false !== stripos( $str_value, 'gradient(' ) ) { return array( 'background' => $str_value ); } $background_prop_default = &$this->parser->data['csstidy']['background_prop_default']; $repeat = array( 'repeat', 'repeat-x', 'repeat-y', 'no-repeat', 'space' ); $attachment = array( 'scroll', 'fixed', 'local' ); $clip = array( 'border', 'padding' ); $origin = array( 'border', 'padding', 'content' ); $pos = array( 'top', 'center', 'bottom', 'left', 'right' ); $important = ''; $return = array( 'background-image' => null, 'background-size' => null, 'background-repeat' => null, 'background-position' => null, 'background-attachment' => null, 'background-clip' => null, 'background-origin' => null, 'background-color' => null, ); if ( $this->parser->is_important( $str_value ) ) { $important = ' !important'; $str_value = $this->parser->gvw_important( $str_value ); } $have = array(); $str_value = $this->explode_ws( ',', $str_value ); $str_value_count = count( $str_value ); for ( $i = 0; $i < $str_value_count; $i++ ) { $have['clip'] = false; $have['pos'] = false; $have['color'] = false; $have['bg'] = false; if ( is_array( $str_value[ $i ] ) ) { $str_value[ $i ] = $str_value[ $i ][0]; } $str_value[ $i ] = $this->explode_ws( ' ', trim( $str_value[ $i ] ) ); $str_value_i_count = count( $str_value[ $i ] ); for ( $j = 0; $j < $str_value_i_count; $j++ ) { if ( false === $have['bg'] && ( str_starts_with( $str_value[ $i ][ $j ], 'url(' ) || 'none' === $str_value[ $i ][ $j ] ) ) { $return['background-image'] .= $str_value[ $i ][ $j ] . ','; $have['bg'] = true; } elseif ( in_array( $str_value[ $i ][ $j ], $repeat, true ) ) { $return['background-repeat'] .= $str_value[ $i ][ $j ] . ','; } elseif ( in_array( $str_value[ $i ][ $j ], $attachment, true ) ) { $return['background-attachment'] .= $str_value[ $i ][ $j ] . ','; } elseif ( in_array( $str_value[ $i ][ $j ], $clip, true ) && ! $have['clip'] ) { $return['background-clip'] .= $str_value[ $i ][ $j ] . ','; $have['clip'] = true; } elseif ( in_array( $str_value[ $i ][ $j ], $origin, true ) ) { $return['background-origin'] .= $str_value[ $i ][ $j ] . ','; } elseif ( '(' === $str_value[ $i ][ $j ][0] ) { $return['background-size'] .= substr( $str_value[ $i ][ $j ], 1, -1 ) . ','; } elseif ( in_array( $str_value[ $i ][ $j ], $pos, true ) || is_numeric( $str_value[ $i ][ $j ][0] ) || is_null( $str_value[ $i ][ $j ][0] ) || '-' === $str_value[ $i ][ $j ][0] || '.' === $str_value[ $i ][ $j ][0] ) { $return['background-position'] .= $str_value[ $i ][ $j ]; if ( ! $have['pos'] ) { $return['background-position'] .= ' '; } else { $return['background-position'] .= ','; } $have['pos'] = true; } elseif ( ! $have['color'] ) { $return['background-color'] .= $str_value[ $i ][ $j ] . ','; $have['color'] = true; } } } foreach ( $background_prop_default as $bg_prop => $default_value ) { if ( null !== $return[ $bg_prop ] ) { $return[ $bg_prop ] = substr( $return[ $bg_prop ], 0, -1 ) . $important; } else { $return[ $bg_prop ] = $default_value . $important; } } return $return; } /** * Merges all background properties. * * @todo Full CSS3 compliance. * * @since 1.0.0 * * @param array $input_css CSS. * @return array Array. */ public function merge_bg( array $input_css ): array { $background_prop_default = &$this->parser->data['csstidy']['background_prop_default']; // Max number of background images. CSS3 not yet fully implemented. $number_of_values = @max( count( $this->explode_ws( ',', $input_css['background-image'] ) ), count( $this->explode_ws( ',', $input_css['background-color'] ) ), 1 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged // Array with background images to check if BG image exists. $bg_img_array = @$this->explode_ws( ',', $this->parser->gvw_important( $input_css['background-image'] ) ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged $new_bg_value = ''; $important = ''; // If background properties is here and not empty, don't try anything. if ( isset( $input_css['background'] ) && $input_css['background'] ) { return $input_css; } for ( $i = 0; $i < $number_of_values; $i++ ) { foreach ( $background_prop_default as $bg_property => $default_value ) { // Skip if property does not exist. if ( ! isset( $input_css[ $bg_property ] ) ) { continue; } $cur_value = $input_css[ $bg_property ]; // Skip all optimisation if gradient() somewhere. if ( false !== stripos( $cur_value, 'gradient(' ) ) { return $input_css; } // Skip some properties if there is no background image. if ( ( ! isset( $bg_img_array[ $i ] ) || 'none' === $bg_img_array[ $i ] ) && ( 'background-size' === $bg_property || 'background-position' === $bg_property || 'background-attachment' === $bg_property || 'background-repeat' === $bg_property ) ) { continue; } // Remove !important. if ( $this->parser->is_important( $cur_value ) ) { $important = ' !important'; $cur_value = $this->parser->gvw_important( $cur_value ); } // Do not add default values. if ( $cur_value === $default_value ) { continue; } $temp = $this->explode_ws( ',', $cur_value ); if ( isset( $temp[ $i ] ) ) { if ( 'background-size' === $bg_property ) { $new_bg_value .= '(' . $temp[ $i ] . ') '; } else { $new_bg_value .= $temp[ $i ] . ' '; } } } $new_bg_value = trim( $new_bg_value ); if ( $i !== $number_of_values - 1 ) { $new_bg_value .= ','; } } // Delete all background properties. foreach ( $background_prop_default as $bg_property => $default_value ) { unset( $input_css[ $bg_property ] ); } // Add new background property. if ( '' !== $new_bg_value ) { $input_css['background'] = $new_bg_value . $important; } elseif ( isset( $input_css['background'] ) ) { $input_css['background'] = 'none'; } return $input_css; } /** * Dissolve font property. * * @since 1.0.0 * * @param string $str_value [description]. * @return array [description] */ public function dissolve_short_font( string $str_value ): array { $font_prop_default = &$this->parser->data['csstidy']['font_prop_default']; $font_weight = array( 'normal', 'bold', 'bolder', 'lighter', 100, 200, 300, 400, 500, 600, 700, 800, 900 ); $font_variant = array( 'normal', 'small-caps' ); $font_style = array( 'normal', 'italic', 'oblique' ); $important = ''; $return = array( 'font-style' => null, 'font-variant' => null, 'font-weight' => null, 'font-size' => null, 'line-height' => null, 'font-family' => null, ); if ( $this->parser->is_important( $str_value ) ) { $important = ' !important'; $str_value = $this->parser->gvw_important( $str_value ); } $have = array(); $have['style'] = false; $have['variant'] = false; $have['weight'] = false; $have['size'] = false; // Detects if font-family consists of several words w/o quotes. $multiwords = false; // Workaround with multiple font-families. $str_value = $this->explode_ws( ',', trim( $str_value ) ); $str_value[0] = $this->explode_ws( ' ', trim( $str_value[0] ) ); $str_value_0_count = count( $str_value[0] ); for ( $j = 0; $j < $str_value_0_count; $j++ ) { if ( false === $have['weight'] && in_array( $str_value[0][ $j ], $font_weight, false ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.FoundNonStrictFalse $return['font-weight'] = $str_value[0][ $j ]; $have['weight'] = true; } elseif ( false === $have['variant'] && in_array( $str_value[0][ $j ], $font_variant, true ) ) { $return['font-variant'] = $str_value[0][ $j ]; $have['variant'] = true; } elseif ( false === $have['style'] && in_array( $str_value[0][ $j ], $font_style, true ) ) { $return['font-style'] = $str_value[0][ $j ]; $have['style'] = true; } elseif ( false === $have['size'] && ( is_numeric( $str_value[0][ $j ][0] ) || is_null( $str_value[0][ $j ][0] ) || '.' === $str_value[0][ $j ][0] ) ) { $size = $this->explode_ws( '/', trim( $str_value[0][ $j ] ) ); $return['font-size'] = $size[0]; if ( isset( $size[1] ) ) { $return['line-height'] = $size[1]; } else { $return['line-height'] = ''; // Don't add 'normal'! } $have['size'] = true; } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found if ( isset( $return['font-family'] ) ) { $return['font-family'] .= ' ' . $str_value[0][ $j ]; $multiwords = true; } else { $return['font-family'] = $str_value[0][ $j ]; } } } // Add quotes if we have several words in font-family. if ( $multiwords ) { $return['font-family'] = '"' . $return['font-family'] . '"'; } $i = 1; while ( isset( $str_value[ $i ] ) ) { $return['font-family'] .= ',' . trim( $str_value[ $i ] ); ++$i; } // Fix for font-size 100 and higher. if ( false === $have['size'] && isset( $return['font-weight'] ) && is_numeric( $return['font-weight'][0] ) ) { $return['font-size'] = $return['font-weight']; unset( $return['font-weight'] ); } foreach ( $font_prop_default as $font_prop => $default_value ) { if ( null !== $return[ $font_prop ] ) { $return[ $font_prop ] = $return[ $font_prop ] . $important; } else { $return[ $font_prop ] = $default_value . $important; } } return $return; } /** * Merges all fonts properties. * * @since 1.0.0 * * @param array $input_css [description]. * @return array [description] */ public function merge_font( array $input_css ): array { $font_prop_default = &$this->parser->data['csstidy']['font_prop_default']; $new_font_value = ''; $important = ''; // Skip if no font-family and font-size set. if ( isset( $input_css['font-family'], $input_css['font-size'] ) && 'inherit' !== $input_css['font-family'] ) { // Fix several words in font-family - add quotes. $families = explode( ',', $input_css['font-family'] ); $result_families = array(); foreach ( $families as $family ) { $family = trim( $family ); $len = strlen( $family ); if ( str_contains( $family, ' ' ) && ! ( ( '"' === $family[0] && '"' === $family[ $len - 1 ] ) || ( "'" === $family[0] && "'" === $family[ $len - 1 ] ) ) ) { $family = '"' . $family . '"'; } $result_families[] = $family; } $input_css['font-family'] = implode( ',', $result_families ); foreach ( $font_prop_default as $font_property => $default_value ) { // Skip if property does not exist. if ( ! isset( $input_css[ $font_property ] ) ) { continue; } $cur_value = $input_css[ $font_property ]; // Skip if default value is used. if ( $cur_value === $default_value ) { continue; } // Remove !important. if ( $this->parser->is_important( $cur_value ) ) { $important = ' !important'; $cur_value = $this->parser->gvw_important( $cur_value ); } $new_font_value .= $cur_value; // Add delimiter. $new_font_value .= ( 'font-size' === $font_property && isset( $input_css['line-height'] ) ) ? '/' : ' '; } $new_font_value = trim( $new_font_value ); // Delete all font properties. foreach ( $font_prop_default as $font_property => $default_value ) { if ( 'font' !== $font_property || ! $new_font_value ) { unset( $input_css[ $font_property ] ); } } // Add new font property. if ( '' !== $new_font_value ) { $input_css['font'] = $new_font_value . $important; } } return $input_css; } } // class TablePress_CSSTidy_Optimise PK!8u #libraries/csstidy/class.csstidy.phpnu[data = require __DIR__ . '/data.inc.php'; $this->settings['remove_bslash'] = true; $this->settings['compress_colors'] = true; $this->settings['compress_font-weight'] = true; $this->settings['lowercase_s'] = false; /* * 1 common shorthands optimization * 2 + font property optimization * 3 + background property optimization */ $this->settings['optimise_shorthands'] = 1; $this->settings['remove_last_;'] = true; // Rewrite all properties with lower case, better for later gzipping. $this->settings['case_properties'] = 1; /* * Sort properties in alphabetic order, better for later gzipping, * but can cause trouble in case of overriding same properties or using hacks. */ $this->settings['sort_properties'] = false; /* * 1, 3, 5, etc -- Enable sorting selectors inside @media: a{}b{}c{}. * 2, 5, 8, etc -- Enable sorting selectors inside one CSS declaration: a,b,c{}. * Preserve order by default cause it can break functionality. */ $this->settings['sort_selectors'] = 0; // Is dangerous to be used: CSS is broken sometimes. $this->settings['merge_selectors'] = 0; // Whether to preserve browser hacks. $this->settings['discard_invalid_selectors'] = false; $this->settings['discard_invalid_properties'] = false; $this->settings['css_level'] = 'CSS3.0'; $this->settings['preserve_css'] = false; $this->settings['timestamp'] = false; $this->settings['template'] = ''; // say that property exists. $this->set_cfg( 'template', 'default' ); // Call load_template. $this->print = new TablePress_CSSTidy_Print( $this ); $this->optimise = new TablePress_CSSTidy_Optimise( $this ); $this->tokens_list = &$this->data['csstidy']['tokens']; } /** * Gets the value of a setting. * * @since 1.0.0 * * @param string $setting Setting to get. * @return string|int|bool Value of the setting. */ public function get_cfg( string $setting ) /* : string|int|bool */ { if ( isset( $this->settings[ $setting ] ) ) { return $this->settings[ $setting ]; } return false; } /** * Sets the value of a setting. * * @since 1.0.0 * * @param string $setting Setting. * @param string|int|bool|null $value Optional. Value of the setting. * @return bool Whether the setting was set. */ public function set_cfg( string $setting, $value = null ): bool { if ( isset( $this->settings[ $setting ] ) && '' !== $value ) { $this->settings[ $setting ] = $value; if ( 'template' === $setting ) { $this->load_template( $this->settings['template'] ); } return true; } return false; } /** * Adds a token to $this->tokens. * * @since 1.0.0 * * @param mixed $type Type. * @param string $data Data. * @param bool $force Optional. Add a token even if preserve_css is off. */ public function _add_token( $type, string $data, bool $force = false ): void { if ( $this->get_cfg( 'preserve_css' ) || $force ) { // nested @...: if opening a new part we just closed, remove the previous closing instead of adding opening. if ( self::AT_START === $type && count( $this->tokens ) && ( $last = end( $this->tokens ) ) // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.Found,Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure && self::AT_END === $last[0] && trim( $data ) === $last[1] ) { array_pop( $this->tokens ); } else { $this->tokens[] = array( $type, ( self::COMMENT === $type || self::IMPORTANT_COMMENT === $type ) ? $data : trim( $data ) ); } } } /** * Adds a message to the message log. * * @since 1.0.0 * * @param string $message Message. * @param string $type Type. * @param int $line Optional. Line number. -1 will use the current line. */ public function log( string $message, string $type, int $line = -1 ): void { if ( -1 === $line ) { $line = $this->line; } $line = (int) $line; $add = array( 'm' => $message, 't' => $type, ); if ( ! isset( $this->log[ $line ] ) || ! in_array( $add, $this->log[ $line ], true ) ) { $this->log[ $line ][] = $add; } } /** * Parses Unicode notations and find a replacement character. * * @since 1.0.0 * * @param string $a_string String. * @param int $i i. * @return string [return value] */ public function _unicode( string &$a_string, int &$i ): string { ++$i; $add = ''; $replaced = false; while ( $i < strlen( $a_string ) && ( ctype_xdigit( $a_string[ $i ] ) || ctype_space( $a_string[ $i ] ) ) && strlen( $add ) < 6 ) { // phpcs:ignore Squiz.PHP.DisallowSizeFunctionsInLoops.Found $add .= $a_string[ $i ]; if ( ctype_space( $a_string[ $i ] ) ) { break; } ++$i; } if ( ( hexdec( $add ) > 47 && hexdec( $add ) < 58 ) || ( hexdec( $add ) > 64 && hexdec( $add ) < 91 ) || ( hexdec( $add ) > 96 && hexdec( $add ) < 123 ) ) { $this->log( 'Replaced unicode notation: Changed \\' . $add . ' to ' . chr( hexdec( $add ) ), 'Information' ); $add = chr( hexdec( $add ) ); $replaced = true; } else { $add = trim( '\\' . $add ); } if ( ( @ctype_xdigit( $a_string[ $i + 1 ] ) && ctype_space( $a_string[ $i ] ) && ! $replaced ) || ! ctype_space( $a_string[ $i ] ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged --$i; } if ( '\\' !== $add || ! $this->get_cfg( 'remove_bslash' ) || str_contains( $this->tokens_list, $a_string[ $i + 1 ] ) ) { return $add; } if ( '\\' === $add ) { $this->log( 'Removed unnecessary backslash', 'Information' ); } return ''; } /** * Loads a new template. * * @since 1.0.0 * * @link https://csstidy.sourceforge.net/templates.php * * @param string $content Either file name (if $from_file is true), content of a template file, "default", "low", high", or "highest". * @param bool $from_file Optional. Uses $content as filename if true. */ protected function load_template( string $content, bool $from_file = true ): void { if ( in_array( $content, array( 'default', 'low', 'high', 'highest' ), true ) ) { $this->template = $this->data['csstidy']['predefined_templates'][ $content ]; return; } if ( $from_file ) { $content = strip_tags( file_get_contents( $content ), '' ); } // Unify newlines (because the output also only uses \n). $content = str_replace( "\r\n", "\n", $content ); $this->template = explode( '|', $content ); } /** * Starts parsing from URL. * * @since 1.0.0 * * @param string $url URL. * @return bool Whether the CSS code could be parsed successfully. */ protected function parse_from_url( string $url ): bool { $data = file_get_contents( $url ); if ( false === $data ) { return false; } return $this->parse( $data ); } /** * Checks if there is a token at the current position. * * @since 1.0.0 * * @param string $a_string String. * @param int $i i. * @return bool [return value] */ protected function is_token( string &$a_string, int $i ): bool { return ( str_contains( $this->tokens_list, $a_string[ $i ] ) && ! $this->escaped( $a_string, $i ) ); } /** * Parses CSS in a string. The output is saved as an array in $this->css. * * @since 1.0.0 * * @param string $a_string The CSS code. * @return bool Whether the CSS code could be parsed successfully. */ public function parse( string $a_string ): bool { // Temporarily set locale to en_US in order to handle floats properly. $old = @setlocale( LC_ALL, 0 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged @setlocale( LC_ALL, 'C' ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged $at_rules = &$this->data['csstidy']['at_rules']; $quoted_string_properties = &$this->data['csstidy']['quoted_string_properties']; $this->css = array(); $this->print->input_css = $a_string; $a_string = str_replace( "\r\n", "\n", $a_string ) . ' '; $cur_comment = ''; $cur_at = ''; for ( $i = 0, $size = strlen( $a_string ); $i < $size; $i++ ) { if ( "\n" === $a_string[ $i ] || "\r" === $a_string[ $i ] ) { ++$this->line; } switch ( $this->status ) { /* Case in at-block */ case 'at': if ( $this->is_token( $a_string, $i ) ) { if ( '/' === $a_string[ $i ] && '*' === @$a_string[ $i + 1 ] ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged $this->status = 'ic'; ++$i; $this->from[] = 'at'; } elseif ( '{' === $a_string[ $i ] ) { $this->status = 'is'; $this->at = $this->css_new_media_section( $this->at, $cur_at ); $this->_add_token( self::AT_START, $this->at ); } elseif ( ',' === $a_string[ $i ] ) { $cur_at = trim( $cur_at ) . ','; } elseif ( '\\' === $a_string[ $i ] ) { $cur_at .= $this->_unicode( $a_string, $i ); } elseif ( in_array( $a_string[ $i ], array( '(', ')', ':', '.', '/' ), true ) ) { // Fix for complicated media, i.e @media screen and (-webkit-min-device-pixel-ratio:1.5). // '/' is included for ratios in Opera: (-o-min-device-pixel-ratio: 3/2). $cur_at .= $a_string[ $i ]; } } else { $lastpos = strlen( $cur_at ) - 1; if ( ! ( ( ctype_space( $cur_at[ $lastpos ] ) || ( $this->is_token( $cur_at, $lastpos ) && ',' === $cur_at[ $lastpos ] ) ) && ctype_space( $a_string[ $i ] ) ) ) { $cur_at .= $a_string[ $i ]; } } break; /* Case in-selector */ case 'is': if ( $this->is_token( $a_string, $i ) ) { if ( '/' === $a_string[ $i ] && '*' === @$a_string[ $i + 1 ] && '' === trim( $this->selector ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged $this->status = 'ic'; ++$i; $this->from[] = 'is'; } elseif ( '@' === $a_string[ $i ] && '' === trim( $this->selector ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged // Check for at-rule. $this->invalid_at = true; foreach ( $at_rules as $name => $type ) { if ( ! strcasecmp( substr( $a_string, $i + 1, strlen( $name ) ), $name ) ) { if ( 'at' === $type ) { $cur_at = '@' . $name; } else { $this->selector = '@' . $name; } if ( 'atis' === $type ) { $this->next_selector_at = ( $this->next_selector_at ? $this->next_selector_at : ( $this->at ? $this->at : self::DEFAULT_AT ) ); $this->at = $this->css_new_media_section( $this->at, ' ', true ); $type = 'is'; } $this->status = $type; $i += strlen( $name ); $this->invalid_at = false; break; } } if ( $this->invalid_at ) { $this->selector = '@'; $invalid_at_name = ''; for ( $j = $i + 1; $j < $size; ++$j ) { if ( ! ctype_alpha( $a_string[ $j ] ) ) { break; } $invalid_at_name .= $a_string[ $j ]; } $this->log( 'Invalid @-rule: ' . $invalid_at_name . ' (removed)', 'Warning' ); } } elseif ( ( '"' === $a_string[ $i ] || "'" === $a_string[ $i ] ) ) { $this->cur_string[] = $a_string[ $i ]; $this->status = 'instr'; $this->str_char[] = $a_string[ $i ]; $this->from[] = 'is'; /* Fixing CSS3 attribute selectors, i.e. a[href$=".mp3" */ $this->quoted_string[] = ( '=' === $a_string[ $i - 1 ] ); } elseif ( $this->invalid_at && ';' === $a_string[ $i ] ) { $this->invalid_at = false; $this->status = 'is'; if ( $this->next_selector_at ) { $this->at = $this->css_close_media_section( $this->at ); $this->at = $this->css_new_media_section( $this->at, $this->next_selector_at ); $this->next_selector_at = ''; } } elseif ( '{' === $a_string[ $i ] ) { $this->status = 'ip'; if ( '' === $this->at ) { $this->at = $this->css_new_media_section( $this->at, self::DEFAULT_AT ); } $this->selector = $this->css_new_selector( $this->at, $this->selector ); $this->_add_token( self::SEL_START, $this->selector ); $this->added = false; } elseif ( '}' === $a_string[ $i ] ) { $this->_add_token( self::AT_END, $this->at ); $this->at = $this->css_close_media_section( $this->at ); $this->selector = ''; $this->sel_separate = array(); } elseif ( ',' === $a_string[ $i ] ) { $this->selector = trim( $this->selector ) . ','; $this->sel_separate[] = strlen( $this->selector ); } elseif ( '\\' === $a_string[ $i ] ) { $this->selector .= $this->_unicode( $a_string, $i ); } elseif ( '*' === $a_string[ $i ] && @in_array( $a_string[ $i + 1 ], array( '.', '#', '[', ':' ), true ) && ( 0 === $i || '/' !== $a_string[ $i - 1 ] ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,Generic.CodeAnalysis.EmptyStatement.DetectedElseif // Remove unnecessary universal selector, FS#147, but not comment in selector. } else { $this->selector .= $a_string[ $i ]; } } else { $lastpos = strlen( $this->selector ) - 1; if ( -1 === $lastpos || ! ( ( ctype_space( $this->selector[ $lastpos ] ) || ( $this->is_token( $this->selector, $lastpos ) && ',' === $this->selector[ $lastpos ] ) ) && ctype_space( $a_string[ $i ] ) ) ) { $this->selector .= $a_string[ $i ]; } } break; /* Case in-property */ case 'ip': if ( $this->is_token( $a_string, $i ) ) { if ( ( ':' === $a_string[ $i ] || '=' === $a_string[ $i ] ) && '' !== $this->property ) { $this->status = 'iv'; if ( ! $this->get_cfg( 'discard_invalid_properties' ) || $this->property_is_valid( $this->property ) ) { $this->property = $this->css_new_property( $this->at, $this->selector, $this->property ); $this->property = strtolower( $this->property ); $this->_add_token( self::PROPERTY, $this->property ); } } elseif ( '/' === $a_string[ $i ] && '*' === @$a_string[ $i + 1 ] && '' === $this->property ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged $this->status = 'ic'; ++$i; $this->from[] = 'ip'; } elseif ( '}' === $a_string[ $i ] ) { $this->explode_selectors(); $this->status = 'is'; $this->invalid_at = false; $this->_add_token( self::SEL_END, $this->selector ); $this->selector = ''; $this->property = ''; if ( $this->next_selector_at ) { $this->at = $this->css_close_media_section( $this->at ); $this->at = $this->css_new_media_section( $this->at, $this->next_selector_at ); $this->next_selector_at = ''; } } elseif ( ';' === $a_string[ $i ] ) { $this->property = ''; } elseif ( '\\' === $a_string[ $i ] ) { $this->property .= $this->_unicode( $a_string, $i ); } elseif ( ( '' === $this->property && ! ctype_space( $a_string[ $i ] ) ) || ( '/' === $this->property || '/' === $a_string[ $i ] ) ) { // else this is dumb IE a hack, keep it. // including /. $this->property .= $a_string[ $i ]; } } elseif ( ! ctype_space( $a_string[ $i ] ) ) { $this->property .= $a_string[ $i ]; } break; /* Case in-value */ case 'iv': $pn = ( ( ( "\n" === $a_string[ $i ] || "\r" === $a_string[ $i ] ) && $this->property_is_next( $a_string, $i + 1 ) ) || ( strlen( $a_string ) - 1 ) === $i ); if ( ( $this->is_token( $a_string, $i ) || $pn ) && ( ! ( ',' === $a_string[ $i ] && ! ctype_space( $a_string[ $i + 1 ] ) ) ) ) { if ( '/' === $a_string[ $i ] && '*' === @$a_string[ $i + 1 ] ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged $this->status = 'ic'; ++$i; $this->from[] = 'iv'; } elseif ( ( '"' === $a_string[ $i ] || "'" === $a_string[ $i ] || '(' === $a_string[ $i ] ) ) { $this->cur_string[] = $a_string[ $i ]; $this->str_char[] = ( '(' === $a_string[ $i ] ) ? ')' : $a_string[ $i ]; $this->status = 'instr'; $this->from[] = 'iv'; $this->quoted_string[] = in_array( strtolower( $this->property ), $quoted_string_properties, true ); } elseif ( ',' === $a_string[ $i ] ) { $this->sub_value = trim( $this->sub_value ) . ','; } elseif ( '\\' === $a_string[ $i ] ) { $this->sub_value .= $this->_unicode( $a_string, $i ); } elseif ( ';' === $a_string[ $i ] || $pn ) { if ( '@' === $this->selector[0] && isset( $at_rules[ substr( $this->selector, 1 ) ] ) && 'iv' === $at_rules[ substr( $this->selector, 1 ) ] ) { $this->status = 'is'; switch ( $this->selector ) { case '@charset': // Add quotes to charset. $this->sub_value_arr[] = '"' . trim( $this->sub_value ) . '"'; $this->charset = $this->sub_value_arr[0]; break; case '@namespace': // Add quotes to namespace. $this->sub_value_arr[] = '"' . trim( $this->sub_value ) . '"'; $this->namespace = implode( ' ', $this->sub_value_arr ); break; case '@import': $this->sub_value = trim( $this->sub_value ); if ( empty( $this->sub_value_arr ) ) { // Quote URLs in imports only if they're not already inside url() and not already quoted. if ( ! str_starts_with( $this->sub_value, 'url(' ) ) { if ( ! str_ends_with( $this->sub_value, $this->sub_value[0] ) && in_array( $this->sub_value[0], array( "'", '"' ), true ) ) { $this->sub_value = '"' . $this->sub_value . '"'; } } } $this->sub_value_arr[] = $this->sub_value; $this->import[] = implode( ' ', $this->sub_value_arr ); break; } $this->sub_value_arr = array(); $this->sub_value = ''; $this->selector = ''; $this->sel_separate = array(); } else { $this->status = 'ip'; } } elseif ( '}' !== $a_string[ $i ] ) { $this->sub_value .= $a_string[ $i ]; } if ( ( '}' === $a_string[ $i ] || ';' === $a_string[ $i ] || $pn ) && ! empty( $this->selector ) ) { if ( '' === $this->at ) { $this->at = $this->css_new_media_section( $this->at, self::DEFAULT_AT ); } // Case settings. if ( $this->get_cfg( 'lowercase_s' ) ) { $this->selector = strtolower( $this->selector ); } $this->property = strtolower( $this->property ); $this->optimise->subvalue(); if ( '' !== $this->sub_value ) { $this->sub_value_arr[] = $this->sub_value; $this->sub_value = ''; } $this->value = ''; while ( count( $this->sub_value_arr ) ) { // phpcs:ignore Squiz.PHP.DisallowSizeFunctionsInLoops.Found $sub = array_shift( $this->sub_value_arr ); if ( strstr( $this->selector, 'font-face' ) ) { $sub = $this->quote_font_format( $sub ); } if ( '' !== $sub ) { if ( strlen( $this->value ) && ( ! str_ends_with( $this->value, ',' ) || $this->get_cfg( 'preserve_css' ) ) ) { $this->value .= ' '; } $this->value .= $sub; } } $this->optimise->value(); $valid = $this->property_is_valid( $this->property ); if ( ( ! $this->invalid_at || $this->get_cfg( 'preserve_css' ) ) && ( ! $this->get_cfg( 'discard_invalid_properties' ) || $valid ) ) { $this->css_add_property( $this->at, $this->selector, $this->property, $this->value ); $this->_add_token( self::VALUE, $this->value ); $this->optimise->shorthands(); } if ( ! $valid ) { if ( $this->get_cfg( 'discard_invalid_properties' ) ) { $this->log( 'Removed invalid property: ' . $this->property, 'Warning' ); } else { $this->log( 'Invalid property in ' . strtoupper( $this->get_cfg( 'css_level' ) ) . ': ' . $this->property, 'Warning' ); } } $this->property = ''; $this->sub_value_arr = array(); $this->value = ''; } if ( '}' === $a_string[ $i ] ) { $this->explode_selectors(); $this->_add_token( self::SEL_END, $this->selector ); $this->status = 'is'; $this->invalid_at = false; $this->selector = ''; if ( $this->next_selector_at ) { $this->at = $this->css_close_media_section( $this->at ); $this->at = $this->css_new_media_section( $this->at, $this->next_selector_at ); $this->next_selector_at = ''; } } } elseif ( ! $pn ) { $this->sub_value .= $a_string[ $i ]; if ( ctype_space( $a_string[ $i ] ) || ',' === $a_string[ $i ] ) { $this->optimise->subvalue(); if ( '' !== $this->sub_value ) { $this->sub_value_arr[] = $this->sub_value; $this->sub_value = ''; } } } break; /* Case in string */ case 'instr': $_str_char = $this->str_char[ count( $this->str_char ) - 1 ]; $_cur_string = $this->cur_string[ count( $this->cur_string ) - 1 ]; $_quoted_string = $this->quoted_string[ count( $this->quoted_string ) - 1 ]; $temp_add = $a_string[ $i ]; // Add another string to the stack. Strings can't be nested inside of quotes, only parentheses, // but parentheticals can be nested more than once. if ( ')' === $_str_char && ( '(' === $a_string[ $i ] || '"' === $a_string[ $i ] || '\\' === $a_string[ $i ] ) && ! $this->escaped( $a_string, $i ) ) { $this->cur_string[] = $a_string[ $i ]; $this->str_char[] = ( '(' === $a_string[ $i ] ) ? ')' : $a_string[ $i ]; $this->from[] = 'instr'; $this->quoted_string[] = ( ')' === $_str_char && '(' !== $a_string[ $i ] && '(' === trim( $_cur_string ) ) ? $_quoted_string : ( '(' !== $a_string[ $i ] ); continue 2; } if ( ')' !== $_str_char && ( "\n" === $a_string[ $i ] || "\r" === $a_string[ $i ] ) && ! ( '\\' === $a_string[ $i - 1 ] && ! $this->escaped( $a_string, $i - 1 ) ) ) { $temp_add = '\\A'; $this->log( 'Fixed incorrect newline in string', 'Warning' ); } $_cur_string .= $temp_add; if ( $a_string[ $i ] === $_str_char && ! $this->escaped( $a_string, $i ) ) { $this->status = array_pop( $this->from ); if ( ! preg_match( '|[' . implode( '', $this->data['csstidy']['whitespace'] ) . ']|uis', $_cur_string ) && 'content' !== $this->property ) { if ( ! $_quoted_string ) { if ( ')' !== $_str_char ) { /* * Convert properties like * font-family: 'Arial'; * to * font-family: Arial; * or * url("abc") * to * url(abc) */ $_cur_string = substr( $_cur_string, 1, -1 ); } } else { $_quoted_string = false; } } array_pop( $this->cur_string ); array_pop( $this->quoted_string ); array_pop( $this->str_char ); if ( ')' === $_str_char ) { $_cur_string = '(' . trim( substr( $_cur_string, 1, -1 ) ) . ')'; } if ( 'iv' === $this->status ) { if ( ! $_quoted_string ) { if ( str_contains( $_cur_string, ',' ) ) { // We can on only remove space next to ','. $_cur_string = implode( ',', array_map( 'trim', explode( ',', $_cur_string ) ) ); } // and multiple spaces (too expensive). if ( str_contains( $_cur_string, ' ' ) ) { $_cur_string = preg_replace( ',\s+,', ' ', $_cur_string ); } } $this->sub_value .= $_cur_string; } elseif ( 'is' === $this->status ) { $this->selector .= $_cur_string; } elseif ( 'instr' === $this->status ) { $this->cur_string[ count( $this->cur_string ) - 1 ] .= $_cur_string; } } else { $this->cur_string[ count( $this->cur_string ) - 1 ] = $_cur_string; } break; /* Case in-comment */ case 'ic': if ( '*' === $a_string[ $i ] && '/' === $a_string[ $i + 1 ] ) { $this->status = array_pop( $this->from ); ++$i; if ( strlen( $cur_comment ) > 1 && str_starts_with( $cur_comment, '!' ) ) { $this->_add_token( self::IMPORTANT_COMMENT, $cur_comment ); $this->css_add_important_comment( $cur_comment ); } else { $this->_add_token( self::COMMENT, $cur_comment ); } $cur_comment = ''; } else { $cur_comment .= $a_string[ $i ]; } break; } } $this->optimise->postparse(); $this->print->_reset(); // Set locale back to original setting. @setlocale( LC_ALL, $old ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged return ! ( empty( $this->css ) && empty( $this->import ) && empty( $this->charset ) && empty( $this->tokens ) && empty( $this->namespace ) ); } /** * Quoting: format() in font-face needs quoted values for some browser (FF at least). * * @since 1.0.0 * * @param string $value Value. * @return string String. */ protected function quote_font_format( string $value ): string { if ( str_starts_with( $value, 'format' ) ) { $p = strpos( $value, ')', 7 ); $end = substr( $value, $p ); $format_strings = $this->parse_string_list( substr( $value, 7, $p - 7 ) ); if ( ! $format_strings ) { $value = ''; } else { $value = 'format('; foreach ( $format_strings as $format_string ) { $value .= '"' . str_replace( '"', '\\"', $format_string ) . '",'; } $value = substr( $value, 0, -1 ) . $end; } } return $value; } /** * Explodes selectors. * * @since 1.0.0 */ protected function explode_selectors(): void { // Explode multiple selectors. if ( 1 === $this->get_cfg( 'merge_selectors' ) ) { $new_sels = array(); $lastpos = 0; $this->sel_separate[] = strlen( $this->selector ); foreach ( $this->sel_separate as $num => $pos ) { if ( ( count( $this->sel_separate ) - 1 ) === $num ) { ++$pos; } $new_sels[] = substr( $this->selector, $lastpos, $pos - $lastpos - 1 ); $lastpos = $pos; } if ( count( $new_sels ) > 1 ) { foreach ( $new_sels as $selector ) { if ( isset( $this->css[ $this->at ][ $this->selector ] ) ) { $this->merge_css_blocks( $this->at, $selector, $this->css[ $this->at ][ $this->selector ] ); } } unset( $this->css[ $this->at ][ $this->selector ] ); } } $this->sel_separate = array(); } /** * Checks if a character is escaped. * * @since 1.0.0 * * @param string $a_string String. * @param int $pos Position. * @return bool Whether a character is escaped. */ public function escaped( string &$a_string, int $pos ): bool { return $pos ? ! ( @( '\\' !== $a_string[ $pos - 1 ] ) || $this->escaped( $a_string, $pos - 1 ) ) : false; // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged } /** * Adds an important comment to the CSS code (one we want to keep when minifying). * * @since 1.10.0 * * @param string $comment CSS Comment. */ protected function css_add_important_comment( string $comment ): void { if ( $this->get_cfg( 'preserve_css' ) || '' === trim( $comment ) ) { return; } if ( ! isset( $this->css['!'] ) ) { $this->css['!'] = ''; } else { $this->css['!'] .= "\n"; } $this->css['!'] .= $comment; } /** * Adds a property with value to the existing CSS code. * * @since 1.0.0 * * @param string $media Media. * @param string $selector Selector. * @param string $property Property. * @param string $new_val New value. */ protected function css_add_property( string $media, string $selector, string $property, string $new_val ): void { if ( $this->get_cfg( 'preserve_css' ) || '' === trim( $new_val ) ) { return; } $this->added = true; if ( isset( $this->css[ $media ][ $selector ][ $property ] ) ) { if ( ( $this->is_important( $this->css[ $media ][ $selector ][ $property ] ) && $this->is_important( $new_val ) ) || ! $this->is_important( $this->css[ $media ][ $selector ][ $property ] ) ) { $this->css[ $media ][ $selector ][ $property ] = trim( $new_val ); } } else { $this->css[ $media ][ $selector ][ $property ] = trim( $new_val ); } } /** * Checks if a current media section is the continuation of the last one. * If not increase the name of the media section to avoid a merging. * * @since 1.10.0 * * @param int|string $media Media. * @return int|string [return value] */ protected function css_check_last_media_section_or_inc( $media ) /* : int|string */ { // Are we starting? if ( empty( $this->css ) || ! is_array( $this->css ) ) { return $media; } // If the last @media is the same as this, keep it. $at = array_key_last( $this->css ); if ( $at === $media ) { return $media; } // Otherwise increase the section in the array. while ( isset( $this->css[ $media ] ) ) { if ( is_numeric( $media ) ) { ++$media; } else { $media .= ' '; } } return $media; } /** * Starts a new media section. * * Check if the media is not already known, else rename it with extra spaces to avoid merging. * * @since 1.0.0 * * @param string|int $current_media Media. * @param string $new_media Media. * @param bool $at_root Optional. Default false. * @return int|string [return value] */ protected function css_new_media_section( $current_media, string $new_media, bool $at_root = false ) /* : int|string */ { if ( $this->get_cfg( 'preserve_css' ) ) { return $new_media; } // If we already are in a media and CSS level is 3, manage nested medias. if ( $current_media && ! $at_root // Numeric $current_media means self::DEFAULT_AT or inc. && ! is_numeric( $current_media ) && str_starts_with( $this->get_cfg( 'css_level' ), 'CSS3' ) ) { $new_media = rtrim( $current_media ) . '{' . rtrim( $new_media ); } return $this->css_check_last_media_section_or_inc( $new_media ); } /** * Closes a media section. * * Finds the parent media we were in before or the root. * * @since 1.10.0 * * @param string $current_media Current Media. * @return string [return value] */ protected function css_close_media_section( string $current_media ): string { if ( $this->get_cfg( 'preserve_css' ) ) { return ''; } if ( str_contains( $current_media, '{' ) ) { $current_media = explode( '{', $current_media ); array_pop( $current_media ); $current_media = implode( '{', $current_media ); return $current_media; } return ''; } /** * Starts a new selector. * * If already referenced in this media section, rename it with extra space to avoid merging, * except if merging is required, or last selector is the same (merge siblings). * Never merge @font-face. * * @since 1.0.0 * * @param string $media Media. * @param string $selector Selector. * @return string [return value] */ protected function css_new_selector( string $media, string $selector ): string { if ( $this->get_cfg( 'preserve_css' ) ) { return $selector; } $selector = trim( $selector ); if ( ! str_starts_with( $selector, '@font-face' ) ) { if ( false !== $this->settings['merge_selectors'] ) { return $selector; } if ( empty( $this->css ) || ! isset( $this->css[ $media ] ) || ! $this->css[ $media ] ) { return $selector; } // If last is the same, keep it. $sel = array_key_last( $this->css[ $media ] ); if ( $sel === $selector ) { return $selector; } } while ( isset( $this->css[ $media ][ $selector ] ) ) { $selector .= ' '; } return $selector; } /** * Starts a new property. * * If already referenced in this selector, rename it with extra space to avoid override. * * @since 1.0.0 * * @param string $media Media. * @param string $selector Selector. * @param string $property Property. * @return string [return value] */ protected function css_new_property( string $media, string $selector, string $property ): string { if ( $this->get_cfg( 'preserve_css' ) ) { return $property; } if ( empty( $this->css ) || ! isset( $this->css[ $media ][ $selector ] ) || ! $this->css[ $media ][ $selector ] ) { return $property; } while ( isset( $this->css[ $media ][ $selector ][ $property ] ) ) { $property .= ' '; } return $property; } /** * Adds CSS to an existing media/selector. * * @since 1.0.0 * * @param string $media Media. * @param string $selector Selector. * @param array $css_add Additional CSS. */ public function merge_css_blocks( string $media, string $selector, array $css_add ): void { foreach ( $css_add as $property => $value ) { $this->css_add_property( $media, $selector, $property, $value ); } } /** * Checks if $value is !important. * * @since 1.0.0 * * @param string $value Value. * @return bool Whether the value has the !important keyword. */ public function is_important( string &$value ): bool { return ( str_contains( $value, '!' ) // Quick test. && ! strcasecmp( substr( str_replace( $this->data['csstidy']['whitespace'], '', $value ), -10, 10 ), '!important' ) ); } /** * Returns a value without !important. * * @since 1.0.0 * * @param string $value Value. * @return string Value without the !important; */ public function gvw_important( string $value ): string { if ( $this->is_important( $value ) ) { $value = trim( $value ); $value = substr( $value, 0, -9 ); $value = trim( $value ); $value = substr( $value, 0, -1 ); $value = trim( $value ); return $value; } return $value; } /** * Checks if the next word in a string from pos is a CSS property. * * @since 1.0.0 * * @param string $istring String. * @param int $pos Position. * @return bool [return value] */ protected function property_is_next( string $istring, int $pos ): bool { $all_properties = &$this->data['csstidy']['all_properties']; $istring = substr( $istring, $pos, strlen( $istring ) - $pos ); $pos = strpos( $istring, ':' ); if ( false === $pos ) { return false; } $istring = strtolower( trim( substr( $istring, 0, $pos ) ) ); if ( isset( $all_properties[ $istring ] ) ) { $this->log( 'Added semicolon to the end of declaration', 'Warning' ); return true; } return false; } /** * Checks if a property is valid. * * @since 1.0.0 * * @param string $property Property. * @return bool Whether the property is valid. */ public function property_is_valid( string $property ): bool { $property = strtolower( $property ); if ( str_starts_with( $property, '--' ) ) { $property = '--custom'; // Replace custom properties with a temporary placeholder that is marked as valid in the list of properties. } elseif ( in_array( trim( $property ), $this->data['csstidy']['multiple_properties'], true ) ) { $property = trim( $property ); } $all_properties = &$this->data['csstidy']['all_properties']; return isset( $all_properties[ $property ] ) && str_contains( $all_properties[ $property ], strtoupper( $this->get_cfg( 'css_level' ) ) ); } /** * Accepts a list of strings (e.g. the argument to format() in a @font-face src property) * and returns a list of the strings. Converts things like: * format(abc) => format("abc") * format(abc def) => format("abc","def") * format(abc "def") => format("abc","def") * format(abc, def, ghi) => format("abc","def","ghi") * format("abc",'def') => format("abc","def") * format("abc, def, ghi") => format("abc, def, ghi") * * @since 1.0.0 * * @param string $value [description]. * @return string[] [description] */ public function parse_string_list( string $value ): array { $value = trim( $value ); // Case: empty. if ( ! $value ) { return array(); } $a_strings = array(); $in_str = false; $current_string = ''; for ( $i = 0, $_len = strlen( $value ); $i < $_len; $i++ ) { if ( ( ',' === $value[ $i ] || ' ' === $value[ $i ] ) && true === $in_str ) { $in_str = false; $a_strings[] = $current_string; $current_string = ''; } elseif ( '"' === $value[ $i ] || "'" === $value[ $i ] ) { if ( $in_str === $value[ $i ] ) { $a_strings[] = $current_string; $in_str = false; $current_string = ''; continue; } elseif ( ! $in_str ) { $in_str = $value[ $i ]; } } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found if ( $in_str ) { $current_string .= $value[ $i ]; } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found if ( ! preg_match( '/[\s,]/', $value[ $i ] ) ) { $in_str = true; $current_string = $value[ $i ]; } } } } if ( $current_string ) { $a_strings[] = $current_string; } return $a_strings; } } // class TablePress_CSSTidy PK!IRlibraries/html-parser.class.phpnu[|WP_Error Array with table data and options (current table head and foot row) on success, WP_Error on error. */ public static function parse( string $html ) /* : array|WP_Error */ { if ( false === stripos( $html, '' ) ) { return new WP_Error( 'table_import_html_no_table_found' ); } // Prepend XML declaration, for better encoding support. $full_html = '' . $html; if ( function_exists( 'libxml_disable_entity_loader' ) ) { /* * Don't expand external entities, see https://websec.io/2012/08/27/Preventing-XXE-in-PHP.html. * Silence warnings as the function is deprecated in PHP 8, but can be necessary with LIBXML_NOENT being defined, see https://core.trac.wordpress.org/changeset/50714. */ @libxml_disable_entity_loader( true ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,Generic.PHP.DeprecatedFunctions.Deprecated } // No warnings/errors raised, but stored internally. libxml_use_internal_errors( true ); $dom = new DOMDocument( '1.0', 'UTF-8' ); // No strict checking for invalid HTML. $dom->strictErrorChecking = false; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase $result = $dom->loadHTML( $full_html ); if ( ! $result ) { return new WP_Error( 'table_import_html_dom_load_html_failed' ); } $dom_tables = $dom->getElementsByTagName( 'table' ); if ( 0 === count( $dom_tables ) ) { return new WP_Error( 'table_import_html_dom_get_tables' ); } libxml_clear_errors(); // Clear errors so that we only catch those inside the table in the next line. $table = simplexml_import_dom( $dom_tables->item( 0 ) ); // @phpstan-ignore argument.type if ( is_null( $table ) ) { return new WP_Error( 'table_import_html_simplexml_import_dom_failed' ); } $errors = libxml_get_errors(); libxml_clear_errors(); if ( ! empty( $errors ) ) { $output = '' . __( 'The imported file contains errors:', 'tablepress' ) . '

    '; foreach ( $errors as $error ) { switch ( $error->level ) { case LIBXML_ERR_WARNING: $output .= "Warning {$error->code}: {$error->message} in line {$error->line}, column {$error->column}
    "; break; case LIBXML_ERR_ERROR: $output .= "Error {$error->code}: {$error->message} in line {$error->line}, column {$error->column}
    "; break; case LIBXML_ERR_FATAL: $output .= "Fatal Error {$error->code}: {$error->message} in line {$error->line}, column {$error->column}
    "; break; } } wp_die( $output, 'Import Error', array( 'response' => 200, 'back_link' => true ) ); } $html_table = array( 'data' => array(), 'options' => array(), ); if ( isset( $table->thead ) ) { $head_rows = self::_import_html_rows( $table->thead[0]->tr ); // @phpstan-ignore property.nonObject $html_table['data'] = array_merge( $html_table['data'], $head_rows ); $html_table['options']['table_head'] = count( $head_rows ); } if ( isset( $table->tbody ) ) { $html_table['data'] = array_merge( $html_table['data'], self::_import_html_rows( $table->tbody[0]->tr ) ); // @phpstan-ignore property.nonObject } if ( isset( $table->tr ) ) { $html_table['data'] = array_merge( $html_table['data'], self::_import_html_rows( $table->tr ) ); } if ( isset( $table->tfoot ) ) { $foot_rows = self::_import_html_rows( $table->tfoot[0]->tr ); // @phpstan-ignore property.nonObject $html_table['data'] = array_merge( $html_table['data'], $foot_rows ); $html_table['options']['table_foot'] = count( $foot_rows ); } return $html_table; } /** * Converts table HTML rows to an array. * * @since 2.0.0 * * @param SimpleXMLElement $element XMLElement. * @return array> SimpleXMLElement exported to an array. */ protected static function _import_html_rows( SimpleXMLElement $element ): array { $rows = array(); // Container for the table data. $rowspans = array(); // Container for information about rowspans in rows that follow the currently processed row. $row_idx = 0; foreach ( $element as $row ) { // If all cells in a row should be merged with the cells in the row above, add the trigger word to each of them (should be very rare). while ( isset( $rowspans[ $row_idx ] ) && count( $rowspans[ $row_idx ] ) === count( $rows[ $row_idx - 1 ] ) ) { // phpcs:ignore Squiz.PHP.DisallowSizeFunctionsInLoops.Found $rows[] = $rowspans[ $row_idx ]; ++$row_idx; } $new_row = array(); $column_idx = 0; foreach ( $row as $cell ) { // If a cell in a row should be merged with the cell above it, add the trigger word to it. while ( isset( $rowspans[ $row_idx ][ $column_idx ] ) ) { $new_row[] = $rowspans[ $row_idx ][ $column_idx ]; ++$column_idx; } $cell_xml = $cell->asXML(); // Get content between ..., or ..., possibly with HTML. if ( false !== $cell_xml && 1 === preg_match( '#(.*)#is', $cell_xml, $matches ) ) { /* * Decode HTML entities again, as there might be some left especially in attributes of HTML tags in the cells, * see https://www.php.net/manual/en/simplexmlelement.asxml.php#107137. */ $new_row[] = html_entity_decode( $matches[1], ENT_NOQUOTES, 'UTF-8' ); // Search for colspan and rowspan attributes in the cell's HTML tag. $colspan = 1; $rowspan = 1; if ( 1 === preg_match( '##is', $cell_xml, $matches ) ) { $colspan = (int) $matches[1]; } if ( 1 === preg_match( '##is', $cell_xml, $matches ) ) { $rowspan = (int) $matches[1]; } // Add cells with the colspan trigger word, if merged cells across columns were found. for ( $i = 1; $i < $colspan; $i++ ) { $new_row[] = '#colspan#'; } // If merged cells across rows were found, add trigger words to a temporary variable. for ( $i = 1; $i < $rowspan; $i++ ) { if ( ! isset( $rowspans[ $row_idx + $i ] ) ) { $rowspans[ $row_idx + $i ] = array(); } $rowspans[ $row_idx + $i ][ $column_idx ] = '#rowspan#'; for ( $j = 1; $j < $colspan; $j++ ) { $rowspans[ $row_idx + $i ][ $column_idx + $j ] = '#span#'; } } } else { // Add an empty cell if no content could be extracted from the cell's HTML tag. $new_row[] = ''; } ++$column_idx; } // After the last cell in a row: If a cell in a row should be merged with the cell above it, add the trigger word to it. while ( isset( $rowspans[ $row_idx ][ $column_idx ] ) ) { $new_row[] = $rowspans[ $row_idx ][ $column_idx ]; ++$column_idx; } $rows[] = $new_row; ++$row_idx; } // After the last data row: If all cells in a row should be merged with the cells in the row above, add the trigger word to each of them (should be very rare). while ( isset( $rowspans[ $row_idx ] ) && count( $rowspans[ $row_idx ] ) === count( $rows[ $row_idx - 1 ] ) ) { // phpcs:ignore Squiz.PHP.DisallowSizeFunctionsInLoops.Found $rows[] = $rowspans[ $row_idx ]; ++$row_idx; } return $rows; } } // class HTML_Parser PK!libraries/index.phpnu[ * Maintained at https://code.google.com/archive/p/php-excel-reader/ * Licensed under MIT license * * Format parsing and MUCH more contributed by Matt Roxburgh * * Cleanup and changes for TablePress by Tobias Bäthge * -------------------------------------------------------------------------- */ define( 'NUM_BIG_BLOCK_DEPOT_BLOCKS_POS', 0x2c ); define( 'SMALL_BLOCK_DEPOT_BLOCK_POS', 0x3c ); define( 'ROOT_START_BLOCK_POS', 0x30 ); define( 'BIG_BLOCK_SIZE', 0x200 ); define( 'SMALL_BLOCK_SIZE', 0x40 ); define( 'EXTENSION_BLOCK_POS', 0x44 ); define( 'NUM_EXTENSION_BLOCK_POS', 0x48 ); define( 'PROPERTY_STORAGE_BLOCK_SIZE', 0x80 ); define( 'BIG_BLOCK_DEPOT_BLOCKS_POS', 0x4c ); define( 'SMALL_BLOCK_THRESHOLD', 0x1000 ); // property storage offsets define( 'SIZE_OF_NAME_POS', 0x40 ); define( 'TYPE_POS', 0x42 ); define( 'START_BLOCK_POS', 0x74 ); define( 'SIZE_POS', 0x78 ); define( 'IDENTIFIER_OLE', pack( 'CCCCCCCC', 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1 ) ); /** * OLERead class */ class OLERead { /** * [$data description] * * @since 1.0.0 */ protected string $data = ''; /** * [$error description] * * @since 1.0.0 */ public int $error; /** * [$bigBlockChain description] * * @since 1.0.0 */ protected array $bigBlockChain = array(); /** * [$smallBlockChain description] * * @since 1.0.0 */ protected array $smallBlockChain = array(); /** * [$entry description] * * @since 1.0.0 * @var [type] */ protected $entry; /** * [$props description] * * @since 1.0.0 * @var [type] */ protected $props; /** * [$wrkbook description] * * @since 1.0.0 * @var [type] */ protected $wrkbook; /** * [$rootentry description] * * @since 1.0.0 * @var [type] */ protected $rootentry; /** * Class constructor. * * @since 1.0.0 */ public function __construct() { // Unused. } /** * [read description] * * @since 1.0.0 * * @param string $data [description] * @return [type] [description] */ public function read( $data ) { $this->data = $data; if ( ! $this->data ) { $this->error = 1; return false; } if ( str_starts_with( $this->data, IDENTIFIER_OLE ) ) { $this->error = 2; return false; } $numBigBlockDepotBlocks = $this->_GetInt4d( $this->data, NUM_BIG_BLOCK_DEPOT_BLOCKS_POS ); $sbdStartBlock = $this->_GetInt4d( $this->data, SMALL_BLOCK_DEPOT_BLOCK_POS ); $rootStartBlock = $this->_GetInt4d( $this->data, ROOT_START_BLOCK_POS ); $extensionBlock = $this->_GetInt4d( $this->data, EXTENSION_BLOCK_POS ); $numExtensionBlocks = $this->_GetInt4d( $this->data, NUM_EXTENSION_BLOCK_POS ); $bigBlockDepotBlocks = array(); $pos = BIG_BLOCK_DEPOT_BLOCKS_POS; $bbdBlocks = $numBigBlockDepotBlocks; if ( 0 !== $numExtensionBlocks ) { $bbdBlocks = ( BIG_BLOCK_SIZE - BIG_BLOCK_DEPOT_BLOCKS_POS ) / 4; } for ( $i = 0; $i < $bbdBlocks; $i++ ) { $bigBlockDepotBlocks[ $i ] = $this->_GetInt4d( $this->data, $pos ); $pos += 4; } for ( $j = 0; $j < $numExtensionBlocks; $j++ ) { $pos = ( $extensionBlock + 1 ) * BIG_BLOCK_SIZE; $blocksToRead = min( $numBigBlockDepotBlocks - $bbdBlocks, BIG_BLOCK_SIZE / 4 - 1 ); for ( $i = $bbdBlocks; $i < $bbdBlocks + $blocksToRead; $i++ ) { $bigBlockDepotBlocks[ $i ] = $this->_GetInt4d( $this->data, $pos ); $pos += 4; } $bbdBlocks += $blocksToRead; if ( $bbdBlocks < $numBigBlockDepotBlocks ) { $extensionBlock = $this->_GetInt4d( $this->data, $pos ); } } // readBigBlockDepot() $index = 0; $this->bigBlockChain = array(); for ( $i = 0; $i < $numBigBlockDepotBlocks; $i++ ) { $pos = ( $bigBlockDepotBlocks[ $i ] + 1 ) * BIG_BLOCK_SIZE; for ( $j = 0; $j < BIG_BLOCK_SIZE / 4; $j++ ) { $this->bigBlockChain[ $index ] = $this->_GetInt4d( $this->data, $pos ); $pos += 4; ++$index; } } // readSmallBlockDepot(); $index = 0; $sbdBlock = $sbdStartBlock; $this->smallBlockChain = array(); while ( -2 !== $sbdBlock ) { $pos = ( $sbdBlock + 1 ) * BIG_BLOCK_SIZE; for ( $j = 0; $j < BIG_BLOCK_SIZE / 4; $j++ ) { $this->smallBlockChain[ $index ] = $this->_GetInt4d( $this->data, $pos ); $pos += 4; ++$index; } $sbdBlock = $this->bigBlockChain[ $sbdBlock ]; } // readData(rootStartBlock) $block = $rootStartBlock; $this->entry = $this->_readData( $block ); $this->_readPropertySets(); } /** * [_readData description] * * @since 1.0.0 * * @param [type] $bl [description] * @return string [description] */ protected function _readData( $bl ): string { $block = $bl; $data = ''; while ( -2 !== $block ) { $pos = ( $block + 1 ) * BIG_BLOCK_SIZE; $data = $data . substr( $this->data, $pos, BIG_BLOCK_SIZE ); $block = $this->bigBlockChain[ $block ]; } return $data; } /** * [_readPropertySets description] * * @since 1.0.0 * * @return [type] [description] */ protected function _readPropertySets() { $offset = 0; while ( $offset < strlen( $this->entry ) ) { $d = substr( $this->entry, $offset, PROPERTY_STORAGE_BLOCK_SIZE ); $nameSize = ord( $d[ SIZE_OF_NAME_POS ] ) | ( ord( $d[ SIZE_OF_NAME_POS + 1 ] ) << 8 ); $type = ord( $d[ TYPE_POS ] ); $startBlock = $this->_GetInt4d( $d, START_BLOCK_POS ); $size = $this->_GetInt4d( $d, SIZE_POS ); $name = ''; for ( $i = 0; $i < $nameSize; $i++ ) { $name .= $d[ $i ]; } $name = str_replace( "\x00", '', $name ); $this->props[] = array( 'name' => $name, 'type' => $type, 'startBlock' => $startBlock, 'size' => $size, ); if ( 'workbook' === strtolower( $name ) || 'book' === strtolower( $name ) ) { $this->wrkbook = count( $this->props ) - 1; } if ( 'Root Entry' === $name ) { $this->rootentry = count( $this->props ) - 1; } $offset += PROPERTY_STORAGE_BLOCK_SIZE; } } /** * [getWorkBook description] * * @since 1.0.0 * * @return [type] [description] */ public function getWorkBook() { if ( $this->props[ $this->wrkbook ]['size'] < SMALL_BLOCK_THRESHOLD ) { $rootdata = $this->_readData( $this->props[ $this->rootentry ]['startBlock'] ); $streamData = ''; $block = $this->props[ $this->wrkbook ]['startBlock']; while ( -2 !== $block ) { $pos = $block * SMALL_BLOCK_SIZE; $streamData .= substr( $rootdata, $pos, SMALL_BLOCK_SIZE ); $block = $this->smallBlockChain[ $block ]; } return $streamData; } else { $numBlocks = $this->props[ $this->wrkbook ]['size'] / BIG_BLOCK_SIZE; if ( 0 !== $this->props[ $this->wrkbook ]['size'] % BIG_BLOCK_SIZE ) { ++$numBlocks; } if ( 0 === $numBlocks ) { return ''; } $streamData = ''; $block = $this->props[ $this->wrkbook ]['startBlock']; while ( -2 !== $block ) { $pos = ( $block + 1 ) * BIG_BLOCK_SIZE; $streamData .= substr( $this->data, $pos, BIG_BLOCK_SIZE ); $block = $this->bigBlockChain[ $block ]; } return $streamData; } } /** * [_GetInt4d description] * * @since 1.0.0 * * @param [type] $data [description] * @param [type] $pos [description] * @return int [description] */ protected function _GetInt4d( $data, $pos ): int { $value = ord( $data[ $pos ] ) | ( ord( $data[ $pos + 1 ] ) << 8 ) | ( ord( $data[ $pos + 2 ] ) << 16 ) | ( ord( $data[ $pos + 3 ] ) << 24 ); if ( $value >= 4294967294 ) { $value = -2; } return $value; } } // class OLERead define( 'SPREADSHEET_EXCEL_READER_BIFF8', 0x600 ); define( 'SPREADSHEET_EXCEL_READER_BIFF7', 0x500 ); define( 'SPREADSHEET_EXCEL_READER_WORKBOOKGLOBALS', 0x5 ); define( 'SPREADSHEET_EXCEL_READER_WORKSHEET', 0x10 ); define( 'SPREADSHEET_EXCEL_READER_TYPE_BOF', 0x809 ); define( 'SPREADSHEET_EXCEL_READER_TYPE_EOF', 0x0a ); define( 'SPREADSHEET_EXCEL_READER_TYPE_BOUNDSHEET', 0x85 ); define( 'SPREADSHEET_EXCEL_READER_TYPE_DIMENSION', 0x200 ); define( 'SPREADSHEET_EXCEL_READER_TYPE_ROW', 0x208 ); define( 'SPREADSHEET_EXCEL_READER_TYPE_DBCELL', 0xd7 ); define( 'SPREADSHEET_EXCEL_READER_TYPE_FILEPASS', 0x2f ); define( 'SPREADSHEET_EXCEL_READER_TYPE_NOTE', 0x1c ); define( 'SPREADSHEET_EXCEL_READER_TYPE_TXO', 0x1b6 ); define( 'SPREADSHEET_EXCEL_READER_TYPE_RK', 0x7e ); define( 'SPREADSHEET_EXCEL_READER_TYPE_RK2', 0x27e ); define( 'SPREADSHEET_EXCEL_READER_TYPE_MULRK', 0xbd ); define( 'SPREADSHEET_EXCEL_READER_TYPE_MULBLANK', 0xbe ); define( 'SPREADSHEET_EXCEL_READER_TYPE_INDEX', 0x20b ); define( 'SPREADSHEET_EXCEL_READER_TYPE_SST', 0xfc ); define( 'SPREADSHEET_EXCEL_READER_TYPE_EXTSST', 0xff ); define( 'SPREADSHEET_EXCEL_READER_TYPE_CONTINUE', 0x3c ); define( 'SPREADSHEET_EXCEL_READER_TYPE_LABEL', 0x204 ); define( 'SPREADSHEET_EXCEL_READER_TYPE_LABELSST', 0xfd ); define( 'SPREADSHEET_EXCEL_READER_TYPE_NUMBER', 0x203 ); define( 'SPREADSHEET_EXCEL_READER_TYPE_NAME', 0x18 ); define( 'SPREADSHEET_EXCEL_READER_TYPE_ARRAY', 0x221 ); define( 'SPREADSHEET_EXCEL_READER_TYPE_STRING', 0x207 ); define( 'SPREADSHEET_EXCEL_READER_TYPE_FORMULA', 0x406 ); define( 'SPREADSHEET_EXCEL_READER_TYPE_FORMULA2', 0x6 ); define( 'SPREADSHEET_EXCEL_READER_TYPE_FORMAT', 0x41e ); define( 'SPREADSHEET_EXCEL_READER_TYPE_XF', 0xe0 ); define( 'SPREADSHEET_EXCEL_READER_TYPE_BOOLERR', 0x205 ); define( 'SPREADSHEET_EXCEL_READER_TYPE_FONT', 0x0031 ); define( 'SPREADSHEET_EXCEL_READER_TYPE_PALETTE', 0x0092 ); define( 'SPREADSHEET_EXCEL_READER_TYPE_UNKNOWN', 0xffff ); define( 'SPREADSHEET_EXCEL_READER_TYPE_NINETEENFOUR', 0x22 ); define( 'SPREADSHEET_EXCEL_READER_TYPE_MERGEDCELLS', 0xE5 ); define( 'SPREADSHEET_EXCEL_READER_UTCOFFSETDAYS', 25569 ); define( 'SPREADSHEET_EXCEL_READER_UTCOFFSETDAYS1904', 24107 ); define( 'SPREADSHEET_EXCEL_READER_MSINADAY', 86400 ); define( 'SPREADSHEET_EXCEL_READER_TYPE_HYPER', 0x01b8 ); define( 'SPREADSHEET_EXCEL_READER_TYPE_COLINFO', 0x7d ); define( 'SPREADSHEET_EXCEL_READER_TYPE_DEFCOLWIDTH', 0x55 ); define( 'SPREADSHEET_EXCEL_READER_TYPE_STANDARDWIDTH', 0x99 ); define( 'SPREADSHEET_EXCEL_READER_DEF_NUM_FORMAT', '%s' ); /** * Main Class */ class Spreadsheet_Excel_Reader { /* * The following four public constants were added to make data retrieval easier. */ /** * [$colnames description] * * @since 1.0.0 */ public array $colnames = array(); /** * [$colindexes description] * * @since 1.0.0 */ public array $colindexes = array(); /** * [$standardColWidth description] * * @since 1.0.0 */ public int $standardColWidth = 0; /** * [$defaultColWidth description] * * @since 1.0.0 */ public int $defaultColWidth = 0; /** * [$store_extended_info description] * * @since 1.0.0 * @var [type] */ protected $store_extended_info; /** * [$_encoderFunction description] * * @since 1.0.0 * @var [type] */ protected $_encoderFunction; /** * [$nineteenFour description] * * @since 1.0.0 * @var [type] */ protected $nineteenFour; /** * [$sn description] * * @since 1.0.0 * @var [type] */ protected $sn; /** * [myHex description] * * @since 1.0.0 * * @param [type] $d [description] * @return string [description] */ protected function myHex( $d ): string { if ( $d < 16 ) { return '0' . dechex( $d ); } return dechex( $d ); } /** * [dumpHexData description] * * @since 1.0.0 * * @param [type] $data [description] * @param [type] $pos [description] * @param [type] $length [description] * @return string [description] */ protected function dumpHexData( $data, $pos, $length ): string { $info = ''; for ( $i = 0; $i <= $length; $i++ ) { if ( 0 !== $i ) { $info .= ' '; } $info .= $this->myHex( ord( $data[ $pos + $i ] ) ) . ( ord( $data[ $pos + $i ] ) > 31 ? '[' . $data[ $pos + $i ] . ']' : '' ); } return $info; } /** * [getCol description] * * @since 1.0.0 * * @param [type] $col [description] * @return [type] [description] */ protected function getCol( $col ) { if ( is_string( $col ) ) { $col = strtolower( $col ); if ( array_key_exists( $col, $this->colnames ) ) { $col = $this->colnames[ $col ]; } } return $col; } // PUBLIC API FUNCTIONS // -------------------- /** * [val description] * * @since 1.0.0 * * @param [type] $row [description] * @param [type] $col [description] * @param int $sheet Optional. [description] * @return [type] [description] */ public function val( $row, $col, $sheet = 0 ) { $col = $this->getCol( $col ); if ( array_key_exists( $row, $this->sheets[ $sheet ]['cells'] ) && array_key_exists( $col, $this->sheets[ $sheet ]['cells'][ $row ] ) ) { return $this->sheets[ $sheet ]['cells'][ $row ][ $col ]; } return ''; } /** * [value description] * * @since 1.0.0 * * @param [type] $row [description] * @param [type] $col [description] * @param int $sheet Optional. [description] * @return [type] [description] */ public function value( $row, $col, $sheet = 0 ) { return $this->val( $row, $col, $sheet ); } /** * [info description] * * @since 1.0.0 * * @param [type] $row [description] * @param [type] $col [description] * @param string $type Optional. [description] * @param int $sheet Optional. [description] * @return [type] [description] */ public function info( $row, $col, $type = '', $sheet = 0 ) { $col = $this->getCol( $col ); if ( array_key_exists( 'cellsInfo', $this->sheets[ $sheet ] ) && array_key_exists( $row, $this->sheets[ $sheet ]['cellsInfo'] ) && array_key_exists( $col, $this->sheets[ $sheet ]['cellsInfo'][ $row ] ) && array_key_exists( $type, $this->sheets[ $sheet ]['cellsInfo'][ $row ][ $col ] ) ) { return $this->sheets[ $sheet ]['cellsInfo'][ $row ][ $col ][ $type ]; } return ''; } /** * [type description] * * @since 1.0.0 * * @param [type] $row [description] * @param [type] $col [description] * @param int $sheet Optional. [description] * @return [type] [description] */ public function type( $row, $col, $sheet = 0 ) { return $this->info( $row, $col, 'type', $sheet ); } /** * [raw description] * * @since 1.0.0 * * @param [type] $row [description] * @param [type] $col [description] * @param int $sheet Optional. [description] * @return [type] [description] */ public function raw( $row, $col, $sheet = 0 ) { return $this->info( $row, $col, 'raw', $sheet ); } /** * [rowspan description] * * @since 1.0.0 * * @param [type] $row [description] * @param [type] $col [description] * @param int $sheet Optional. [description] * @return int [description] */ public function rowspan( $row, $col, $sheet = 0 ): int { $value = $this->info( $row, $col, 'rowspan', $sheet ); if ( '' === $value ) { return 1; } else { $value = (int) $value; } return $value; } /** * [colspan description] * * @since 1.0.0 * * @param [type] $row [description] * @param [type] $col [description] * @param int $sheet Optional. [description] * @return int [description] */ public function colspan( $row, $col, $sheet = 0 ): int { $value = $this->info( $row, $col, 'colspan', $sheet ); if ( '' === $value ) { return 1; } else { $value = (int) $value; } return $value; } /** * [hyperlink description] * * @since 1.0.0 * * @param [type] $row [description] * @param [type] $col [description] * @param int $sheet Optional. [description] * @return [type] [description] */ public function hyperlink( $row, $col, $sheet = 0 ) { $link = $this->sheets[ $sheet ]['cellsInfo'][ $row ][ $col ]['hyperlink']; if ( $link ) { return $link['link']; } return ''; } /** * [rowcount description] * * @since 1.0.0 * * @param int $sheet Optional. [description] * @return [type] [description] */ public function rowcount( $sheet = 0 ) { return $this->sheets[ $sheet ]['numRows']; } /** * [colcount description] * * @since 1.0.0 * * @param int $sheet Optional. [description] * @return [type] [description] */ public function colcount( $sheet = 0 ) { return $this->sheets[ $sheet ]['numCols']; } /** * [colwidth description] * * @since 1.0.0 * * @param [type] $col [description] * @param int $sheet Optional. [description] * @return [type] [description] */ public function colwidth( $col, $sheet = 0 ) { // Col width is actually the width of the number 0. So we have to estimate and come close return $this->colInfo[ $sheet ][ $col ]['width'] / 9142 * 200; } /** * [colhidden description] * * @since 1.0.0 * * @param [type] $col [description] * @param int $sheet Optional. [description] * @return bool [description] */ public function colhidden( $col, $sheet = 0 ): bool { return (bool) $this->colInfo[ $sheet ][ $col ]['hidden']; } /** * [rowheight description] * * @since 1.0.0 * * @param [type] $row [description] * @param int $sheet Optional. [description] * @return [type] [description] */ public function rowheight( $row, $sheet = 0 ) { return $this->rowInfo[ $sheet ][ $row ]['height']; } /** * [rowhidden description] * * @since 1.0.0 * * @param [type] $row [description] * @param int $sheet Optional. [description] * @return bool [description] */ public function rowhidden( $row, $sheet = 0 ): bool { return (bool) $this->rowInfo[ $sheet ][ $row ]['hidden']; } // GET THE CSS FOR FORMATTING // ========================== /** * [style description] * * @since 1.0.0 * * @param [type] $row [description] * @param [type] $col [description] * @param int $sheet Optional. [description] * @return string [description] */ public function style( $row, $col, $sheet = 0 ): string { $css = ''; $font = $this->font( $row, $col, $sheet ); if ( '' !== $font ) { $css .= "font-family:{$font};"; } $align = $this->align( $row, $col, $sheet ); if ( '' !== $align ) { $css .= "text-align:{$align};"; } $height = $this->height( $row, $col, $sheet ); if ( '' !== $height ) { $css .= "font-size:{$height}px;"; } $bgcolor = $this->bgColor( $row, $col, $sheet ); if ( '' !== $bgcolor ) { $bgcolor = $this->colors[ $bgcolor ]; $css .= "background-color:{$bgcolor};"; } $color = $this->color( $row, $col, $sheet ); if ( '' !== $color ) { $css .= "color:{$color};"; } $bold = $this->bold( $row, $col, $sheet ); if ( $bold ) { $css .= 'font-weight:bold;'; } $italic = $this->italic( $row, $col, $sheet ); if ( $italic ) { $css .= 'font-style:italic;'; } $underline = $this->underline( $row, $col, $sheet ); if ( $underline ) { $css .= 'text-decoration:underline;'; } // Borders $bLeft = $this->borderLeft( $row, $col, $sheet ); $bRight = $this->borderRight( $row, $col, $sheet ); $bTop = $this->borderTop( $row, $col, $sheet ); $bBottom = $this->borderBottom( $row, $col, $sheet ); $bLeftCol = $this->borderLeftColor( $row, $col, $sheet ); $bRightCol = $this->borderRightColor( $row, $col, $sheet ); $bTopCol = $this->borderTopColor( $row, $col, $sheet ); $bBottomCol = $this->borderBottomColor( $row, $col, $sheet ); // Try to output the minimal required style. if ( '' !== $bLeft && $bLeft === $bRight && $bRight === $bTop && $bTop === $bBottom ) { $css .= 'border:' . $this->lineStylesCss[ $bLeft ] . ';'; } else { if ( '' !== $bLeft ) { $css .= 'border-left:' . $this->lineStylesCss[ $bLeft ] . ';'; } if ( '' !== $bRight ) { $css .= 'border-right:' . $this->lineStylesCss[ $bRight ] . ';'; } if ( '' !== $bTop ) { $css .= 'border-top:' . $this->lineStylesCss[ $bTop ] . ';'; } if ( '' !== $bBottom ) { $css .= 'border-bottom:' . $this->lineStylesCss[ $bBottom ] . ';'; } } // Only output border colors if there is an actual border specified. if ( '' !== $bLeft && '' !== $bLeftCol ) { $css .= "border-left-color:{$bLeftCol};"; } if ( '' !== $bRight && '' !== $bRightCol ) { $css .= "border-right-color:{$bRightCol};"; } if ( '' !== $bTop && '' !== $bTopCol ) { $css .= "border-top-color:{$bTopCol};"; } if ( '' !== $bBottom && '' !== $bBottomCol ) { $css .= "border-bottom-color:{$bBottomCol};"; } return $css; } // FORMAT PROPERTIES // ================= /** * [format description] * * @since 1.0.0 * * @param [type] $row [description] * @param [type] $col [description] * @param int $sheet Optional. [description] * @return [type] [description] */ public function format( $row, $col, $sheet = 0 ) { return $this->info( $row, $col, 'format', $sheet ); } /** * [formatIndex description] * * @since 1.0.0 * * @param [type] $row [description] * @param [type] $col [description] * @param int $sheet Optional. [description] * @return [type] [description] */ public function formatIndex( $row, $col, $sheet = 0 ) { return $this->info( $row, $col, 'formatIndex', $sheet ); } /** * [formatColor description] * * @since 1.0.0 * * @param [type] $row [description] * @param [type] $col [description] * @param int $sheet Optional. [description] * @return [type] [description] */ public function formatColor( $row, $col, $sheet = 0 ) { return $this->info( $row, $col, 'formatColor', $sheet ); } // CELL (XF) PROPERTIES // ==================== /** * [xfRecord description] * * @since 1.0.0 * * @param [type] $row [description] * @param [type] $col [description] * @param int $sheet Optional. [description] * @return [type] [description] */ public function xfRecord( $row, $col, $sheet = 0 ) { $xfIndex = $this->info( $row, $col, 'xfIndex', $sheet ); if ( '' !== $xfIndex ) { return $this->xfRecords[ $xfIndex ]; } return null; } /** * [xfProperty description] * * @since 1.0.0 * * @param [type] $row [description] * @param [type] $col [description] * @param [type] $sheet [description] * @param [type] $prop [description] * @return [type] [description] */ public function xfProperty( $row, $col, $sheet, $prop ) { $xfRecord = $this->xfRecord( $row, $col, $sheet ); if ( null !== $xfRecord ) { return $xfRecord[ $prop ]; } return ''; } /** * [align description] * * @since 1.0.0 * * @param [type] $row [description] * @param [type] $col [description] * @param int $sheet Optional. [description] * @return [type] [description] */ public function align( $row, $col, $sheet = 0 ) { return $this->xfProperty( $row, $col, $sheet, 'align' ); } /** * [bgColor description] * * @since 1.0.0 * * @param [type] $row [description] * @param [type] $col [description] * @param int $sheet Optional. [description] * @return [type] [description] */ public function bgColor( $row, $col, $sheet = 0 ) { return $this->xfProperty( $row, $col, $sheet, 'bgColor' ); } /** * [borderLeft description] * * @since 1.0.0 * * @param [type] $row [description] * @param [type] $col [description] * @param int $sheet Optional. [description] * @return [type] [description] */ public function borderLeft( $row, $col, $sheet = 0 ) { return $this->xfProperty( $row, $col, $sheet, 'borderLeft' ); } /** * [borderRight description] * * @since 1.0.0 * * @param [type] $row [description] * @param [type] $col [description] * @param int $sheet Optional. [description] * @return [type] [description] */ public function borderRight( $row, $col, $sheet = 0 ) { return $this->xfProperty( $row, $col, $sheet, 'borderRight' ); } /** * [borderTop description] * * @since 1.0.0 * * @param [type] $row [description] * @param [type] $col [description] * @param int $sheet Optional. [description] * @return [type] [description] */ public function borderTop( $row, $col, $sheet = 0 ) { return $this->xfProperty( $row, $col, $sheet, 'borderTop' ); } /** * [borderBottom description] * * @since 1.0.0 * * @param [type] $row [description] * @param [type] $col [description] * @param int $sheet Optional. [description] * @return [type] [description] */ public function borderBottom( $row, $col, $sheet = 0 ) { return $this->xfProperty( $row, $col, $sheet, 'borderBottom' ); } /** * [borderLeftColor description] * * @since 1.0.0 * * @param [type] $row [description] * @param [type] $col [description] * @param int $sheet Optional. [description] * @return [type] [description] */ public function borderLeftColor( $row, $col, $sheet = 0 ) { return $this->colors[ $this->xfProperty( $row, $col, $sheet, 'borderLeftColor' ) ]; } /** * [borderRightColor description] * * @since 1.0.0 * * @param [type] $row [description] * @param [type] $col [description] * @param int $sheet Optional. [description] * @return [type] [description] */ public function borderRightColor( $row, $col, $sheet = 0 ) { return $this->colors[ $this->xfProperty( $row, $col, $sheet, 'borderRightColor' ) ]; } /** * [borderTopColor description] * * @since 1.0.0 * * @param [type] $row [description] * @param [type] $col [description] * @param int $sheet Optional. [description] * @return [type] [description] */ public function borderTopColor( $row, $col, $sheet = 0 ) { return $this->colors[ $this->xfProperty( $row, $col, $sheet, 'borderTopColor' ) ]; } /** * [borderBottomColor description] * * @since 1.0.0 * * @param [type] $row [description] * @param [type] $col [description] * @param int $sheet Optional. [description] * @return [type] [description] */ public function borderBottomColor( $row, $col, $sheet = 0 ) { return $this->colors[ $this->xfProperty( $row, $col, $sheet, 'borderBottomColor' ) ]; } // FONT PROPERTIES // =============== /** * [fontRecord description] * * @since 1.0.0 * * @param [type] $row [description] * @param [type] $col [description] * @param int $sheet Optional. [description] * @return [type] [description] */ public function fontRecord( $row, $col, $sheet = 0 ) { $xfRecord = $this->xfRecord( $row, $col, $sheet ); if ( null !== $xfRecord ) { $font = $xfRecord['fontIndex']; if ( null !== $font ) { return $this->fontRecords[ $font ]; } } return null; } /** * [fontProperty description] * * @since 1.0.0 * * @param [type] $row [description] * @param [type] $col [description] * @param int $sheet [description] * @param string $prop [description] * @return [type] [description] */ public function fontProperty( $row, $col, $sheet, $prop ) { $font = $this->fontRecord( $row, $col, $sheet ); if ( null !== $font ) { return $font[ $prop ]; } return false; } /** * [fontIndex description] * * @since 1.0.0 * * @param [type] $row [description] * @param [type] $col [description] * @param int $sheet Optional. [description] * @return [type] [description] */ public function fontIndex( $row, $col, $sheet = 0 ) { return $this->xfProperty( $row, $col, $sheet, 'fontIndex' ); } /** * [color description] * * @since 1.0.0 * * @param [type] $row [description] * @param [type] $col [description] * @param int $sheet Optional. [description] * @return [type] [description] */ public function color( $row, $col, $sheet = 0 ) { $formatColor = $this->formatColor( $row, $col, $sheet ); if ( '' !== $formatColor ) { return $formatColor; } $ci = $this->fontProperty( $row, $col, $sheet, 'color' ); return $this->rawColor( $ci ); } /** * [rawColor description] * * @since 1.0.0 * * @param [type] $ci [description] * @return [type] [description] */ public function rawColor( $ci ) { if ( 0x7FFF !== $ci && '' !== $ci ) { return $this->colors[ $ci ]; } return ''; } /** * [bold description] * * @since 1.0.0 * * @param [type] $row [description] * @param [type] $col [description] * @param int $sheet Optional. [description] * @return [type] [description] */ public function bold( $row, $col, $sheet = 0 ) { return $this->fontProperty( $row, $col, $sheet, 'bold' ); } /** * [italic description] * * @since 1.0.0 * * @param [type] $row [description] * @param [type] $col [description] * @param int $sheet Optional. [description] * @return [type] [description] */ public function italic( $row, $col, $sheet = 0 ) { return $this->fontProperty( $row, $col, $sheet, 'italic' ); } /** * [underline description] * * @since 1.0.0 * * @param [type] $row [description] * @param [type] $col [description] * @param int $sheet Optional. [description] * @return [type] [description] */ public function underline( $row, $col, $sheet = 0 ) { return $this->fontProperty( $row, $col, $sheet, 'under' ); } /** * [height description] * * @since 1.0.0 * * @param [type] $row [description] * @param [type] $col [description] * @param int $sheet Optional. [description] * @return [type] [description] */ public function height( $row, $col, $sheet = 0 ) { return $this->fontProperty( $row, $col, $sheet, 'height' ); } /** * [font description] * * @since 1.0.0 * * @param [type] $row [description] * @param [type] $col [description] * @param int $sheet Optional. [description] * @return [type] [description] */ public function font( $row, $col, $sheet = 0 ) { return $this->fontProperty( $row, $col, $sheet, 'font' ); } // DUMP AN HTML TABLE OF THE ENTIRE XLS DATA // ========================================= /** * [dump description] * * @since 1.0.0 * * @param bool $row_numbers Optional. [description] * @param bool $col_letters Optional. [description] * @param int $sheet Optional. [description] * @param string $table_class Optional. [description] * @return string [description] */ public function dump( $row_numbers = false, $col_letters = false, $sheet = 0, $table_class = 'excel' ): string { $out = ""; if ( $col_letters ) { $out .= "\n\t"; if ( $row_numbers ) { $out .= "\n\t\t"; } for ( $i = 1; $i <= $this->colcount( $sheet ); $i++ ) { $style = 'width:' . ( $this->colwidth( $i, $sheet ) ) . 'px;'; if ( $this->colhidden( $i, $sheet ) ) { $style .= 'display:none;'; } $out .= "\n\t\t'; } $out .= "\n"; } $out .= "\n"; for ( $row = 1; $row <= $this->rowcount( $sheet ); $row++ ) { $rowheight = $this->rowheight( $row, $sheet ); $style = 'height:' . ( $rowheight * ( 4 / 3 ) ) . 'px;'; if ( $this->rowhidden( $row, $sheet ) ) { $style .= 'display:none;'; } $out .= "\n\t"; if ( $row_numbers ) { $out .= "\n\t\t"; } for ( $col = 1; $col <= $this->colcount( $sheet ); $col++ ) { // Account for Rowspans/Colspans $rowspan = $this->rowspan( $row, $col, $sheet ); $colspan = $this->colspan( $row, $col, $sheet ); for ( $i = 0; $i < $rowspan; $i++ ) { for ( $j = 0; $j < $colspan; $j++ ) { if ( $i > 0 || $j > 0 ) { $this->sheets[ $sheet ]['cellsInfo'][ $row + $i ][ $col + $j ]['dontprint'] = 1; } } } if ( ! $this->sheets[ $sheet ]['cellsInfo'][ $row ][ $col ]['dontprint'] ) { $style = $this->style( $row, $col, $sheet ); if ( $this->colhidden( $col, $sheet ) ) { $style .= 'display:none;'; } $out .= "\n\t\t'; } } $out .= "\n"; } $out .= '
     " . strtoupper( $this->colindexes[ $i ] ) . '
    {$row} 1 ? " colspan={$colspan}" : '' ) . ( $rowspan > 1 ? " rowspan={$rowspan}" : '' ) . '>'; $val = $this->val( $row, $col, $sheet ); if ( '' === $val ) { $val = ' '; } else { $val = htmlentities( $val ); $link = $this->hyperlink( $row, $col, $sheet ); if ( '' !== $link ) { $val = "{$val}"; } } $out .= '' . nl2br( $val ) . ''; $out .= '
    '; return $out; } // -------------- // END PUBLIC API protected array $boundsheets = array(); protected array $formatRecords = array(); protected array $fontRecords = array(); protected array $xfRecords = array(); protected array $colInfo = array(); protected array $rowInfo = array(); protected array $sst = array(); protected array $sheets = array(); protected $data; protected $_ole; protected string $_defaultEncoding = 'UTF-8'; protected string $_defaultFormat = SPREADSHEET_EXCEL_READER_DEF_NUM_FORMAT; protected array $_columnsFormat = array(); protected int $_rowoffset = 1; protected int $_coloffset = 1; /** * List of default date formats used by Excel * * @since 1.0.0 */ protected array $dateFormats = array( 0xe => 'm/d/Y', 0xf => 'M-d-Y', 0x10 => 'd-M', 0x11 => 'M-Y', 0x12 => 'h:i a', 0x13 => 'h:i:s a', 0x14 => 'H:i', 0x15 => 'H:i:s', 0x16 => 'd/m/Y H:i', 0x2d => 'i:s', 0x2e => 'H:i:s', 0x2f => 'i:s.S', ); /** * Default number formats used by Excel * * @since 1.0.0 */ protected array $numberFormats = array( 0x1 => '0', 0x2 => '0.00', 0x3 => '#,##0', 0x4 => '#,##0.00', 0x5 => '\$#,##0;(\$#,##0)', 0x6 => '\$#,##0;[Red](\$#,##0)', 0x7 => '\$#,##0.00;(\$#,##0.00)', 0x8 => '\$#,##0.00;[Red](\$#,##0.00)', 0x9 => '0%', 0xa => '0.00%', 0xb => '0.00E+00', 0x25 => '#,##0;(#,##0)', 0x26 => '#,##0;[Red](#,##0)', 0x27 => '#,##0.00;(#,##0.00)', 0x28 => '#,##0.00;[Red](#,##0.00)', 0x29 => '#,##0;(#,##0)', // Not exact 0x2a => '\$#,##0;(\$#,##0)', // Not exact 0x2b => '#,##0.00;(#,##0.00)', // Not exact 0x2c => '\$#,##0.00;(\$#,##0.00)', // Not exact 0x30 => '##0.0E+0', ); /** * [$colors description] * * @since 1.0.0 */ protected array $colors = array( 0x00 => '#000000', 0x01 => '#FFFFFF', 0x02 => '#FF0000', 0x03 => '#00FF00', 0x04 => '#0000FF', 0x05 => '#FFFF00', 0x06 => '#FF00FF', 0x07 => '#00FFFF', 0x08 => '#000000', 0x09 => '#FFFFFF', 0x0A => '#FF0000', 0x0B => '#00FF00', 0x0C => '#0000FF', 0x0D => '#FFFF00', 0x0E => '#FF00FF', 0x0F => '#00FFFF', 0x10 => '#800000', 0x11 => '#008000', 0x12 => '#000080', 0x13 => '#808000', 0x14 => '#800080', 0x15 => '#008080', 0x16 => '#C0C0C0', 0x17 => '#808080', 0x18 => '#9999FF', 0x19 => '#993366', 0x1A => '#FFFFCC', 0x1B => '#CCFFFF', 0x1C => '#660066', 0x1D => '#FF8080', 0x1E => '#0066CC', 0x1F => '#CCCCFF', 0x20 => '#000080', 0x21 => '#FF00FF', 0x22 => '#FFFF00', 0x23 => '#00FFFF', 0x24 => '#800080', 0x25 => '#800000', 0x26 => '#008080', 0x27 => '#0000FF', 0x28 => '#00CCFF', 0x29 => '#CCFFFF', 0x2A => '#CCFFCC', 0x2B => '#FFFF99', 0x2C => '#99CCFF', 0x2D => '#FF99CC', 0x2E => '#CC99FF', 0x2F => '#FFCC99', 0x30 => '#3366FF', 0x31 => '#33CCCC', 0x32 => '#99CC00', 0x33 => '#FFCC00', 0x34 => '#FF9900', 0x35 => '#FF6600', 0x36 => '#666699', 0x37 => '#969696', 0x38 => '#003366', 0x39 => '#339966', 0x3A => '#003300', 0x3B => '#333300', 0x3C => '#993300', 0x3D => '#993366', 0x3E => '#333399', 0x3F => '#333333', 0x40 => '#000000', 0x41 => '#FFFFFF', 0x43 => '#000000', 0x4D => '#000000', 0x4E => '#FFFFFF', 0x4F => '#000000', 0x50 => '#FFFFFF', 0x51 => '#000000', 0x7FFF => '#000000', ); /** * [$lineStyles description] * * @since 1.0.0 */ protected array $lineStyles = array( 0x00 => '', 0x01 => 'Thin', 0x02 => 'Medium', 0x03 => 'Dashed', 0x04 => 'Dotted', 0x05 => 'Thick', 0x06 => 'Double', 0x07 => 'Hair', 0x08 => 'Medium dashed', 0x09 => 'Thin dash-dotted', 0x0A => 'Medium dash-dotted', 0x0B => 'Thin dash-dot-dotted', 0x0C => 'Medium dash-dot-dotted', 0x0D => 'Slanted medium dash-dotted', ); /** * [$lineStylesCss description] * * @since 1.0.0 */ protected array $lineStylesCss = array( 'Thin' => '1px solid', 'Medium' => '2px solid', 'Dashed' => '1px dashed', 'Dotted' => '1px dotted', 'Thick' => '3px solid', 'Double' => 'double', 'Hair' => '1px solid', 'Medium dashed' => '2px dashed', 'Thin dash-dotted' => '1px dashed', 'Medium dash-dotted' => '2px dashed', 'Thin dash-dot-dotted' => '1px dashed', 'Medium dash-dot-dotted' => '2px dashed', 'Slanted medium dash-dotted' => '2px dashed', ); /** * [read16bitstring description] * * @since 1.0.0 * * @param [type] $data [description] * @param [type] $start [description] * @return string [description] */ protected function read16bitstring( $data, $start ): string { $len = 0; while ( ord( $data[ $start + $len ] ) + ord( $data[ $start + $len + 1 ] ) > 0 ) { ++$len; } return substr( $data, $start, $len ); } /** * [_format_value description] * ADDED by Matt Kruse for better formatting * * @since 1.0.0 * * @param [type] $format [description] * @param [type] $num [description] * @param [type] $f [description] * @return array [description] */ protected function _format_value( $format, $num, $f ): array { // 49 = TEXT format // https://code.google.com/archive/p/php-excel-reader/issues/7 if ( ( ! $f && '%s' === $format ) || ( 49 === (int) $f ) || ( 'GENERAL' === $format ) ) { return array( 'string' => $num, 'formatColor' => null, ); } // Custom pattern can be POSITIVE;NEGATIVE;ZERO // The "text" option as 4th parameter is not handled $parts = explode( ';', $format ); $pattern = $parts[0]; // Negative pattern if ( count( $parts ) > 2 && 0 === (int) $num ) { $pattern = $parts[2]; } // Zero pattern if ( count( $parts ) > 1 && $num < 0 ) { $pattern = $parts[1]; $num = abs( $num ); } $color = ''; $matches = array(); $color_regex = '/^\[(BLACK|BLUE|CYAN|GREEN|MAGENTA|RED|WHITE|YELLOW)\]/i'; if ( preg_match( $color_regex, $pattern, $matches ) ) { $color = strtolower( $matches[1] ); $pattern = preg_replace( $color_regex, '', $pattern ); } // In Excel formats, "_" is used to add spacing, which we can't do in HTML. $pattern = preg_replace( '/_./', '', $pattern ); // Some non-number characters are escaped with \, which we don't need. $pattern = preg_replace( '/\\\/', '', $pattern ); // Some non-number strings are quoted, so we'll get rid of the quotes. $pattern = preg_replace( '/"/', '', $pattern ); // TEMPORARY - Convert # to 0. $pattern = preg_replace( '/\#/', '0', $pattern ); // Find out if we need comma formatting. $has_commas = preg_match( '/,/', $pattern ); if ( $has_commas ) { $pattern = preg_replace( '/,/', '', $pattern ); } // Handle Percentages. if ( preg_match( '/\d(\%)([^\%]|$)/', $pattern, $matches ) ) { $num *= 100; $pattern = preg_replace( '/(\d)(\%)([^\%]|$)/', '$1%$3', $pattern ); } // Handle the number itself. $number_regex = '/(\d+)(\.?)(\d*)/'; if ( preg_match( $number_regex, $pattern, $matches ) ) { // $left = $matches[1]; // $dec = $matches[2]; $right = $matches[3]; if ( $has_commas ) { $formatted = number_format( $num, strlen( $right ) ); } else { $sprintf_pattern = '%1.' . strlen( $right ) . 'f'; $formatted = sprintf( $sprintf_pattern, $num ); } $pattern = preg_replace( $number_regex, $formatted, $pattern ); } return array( 'string' => $pattern, 'formatColor' => $color, ); } /** * [__construct description] * * @since 1.0.0 * * @param string $data Optional. [description] * @param bool $store_extended_info Optional. [description] * @param string $outputEncoding Optional. [description] */ public function __construct( $data = '', $store_extended_info = false, $outputEncoding = '' ) { $this->_ole = new OLERead(); $this->setUTFEncoder( 'iconv' ); if ( '' !== $outputEncoding ) { $this->setOutputEncoding( $outputEncoding ); } for ( $i = 1; $i < 245; $i++ ) { $name = strtolower( ( ( ( $i - 1 ) / 26 >= 1 ) ? chr( ( $i - 1 ) / 26 + 64 ) : '' ) . chr( ( $i - 1 ) % 26 + 65 ) ); $this->colnames[ $name ] = $i; $this->colindexes[ $i ] = $name; } $this->store_extended_info = $store_extended_info; if ( '' !== $data ) { $this->read( $data ); } } /** * Set the encoding method. * * @since 1.0.0 * * @param [type] $encoding [description] */ public function setOutputEncoding( $encoding ): void { $this->_defaultEncoding = $encoding; } /** * [setUTFEncoder description] * $encoder = 'iconv' or 'mb' * set iconv if you would like use 'iconv' for encode UTF-16LE to your encoding * set mb if you would like use 'mb_convert_encoding' for encode UTF-16LE to your encoding * * @since 1.0.0 * * @param string $encoder Optional. [description] */ public function setUTFEncoder( $encoder = 'iconv' ): void { $this->_encoderFunction = ''; if ( 'iconv' === $encoder ) { $this->_encoderFunction = function_exists( 'iconv' ) ? 'iconv' : ''; } elseif ( 'mb' === $encoder ) { $this->_encoderFunction = function_exists( 'mb_convert_encoding' ) ? 'mb_convert_encoding' : ''; } } /** * [setRowColOffset description] * * @since 1.0.0 * * @param [type] $iOffset [description] */ public function setRowColOffset( $iOffset ): void { $this->_rowoffset = $iOffset; $this->_coloffset = $iOffset; } /** * Set the default number format. * * @since 1.0.0 * * @param [type] $sFormat [description] */ public function setDefaultFormat( $sFormat ): void { $this->_defaultFormat = $sFormat; } /** * Force a column to use a certain format. * * @since 1.0.0 * * @param [type] $column [description] * @param [type] $sFormat [description] */ public function setColumnFormat( $column, $sFormat ): void { $this->_columnsFormat[ $column ] = $sFormat; } /** * Read the spreadsheet file using OLE, then parse. * * @since 1.0.0 * * @param string $data [description] */ public function read( $data ): void { $res = $this->_ole->read( $data ); // oops, something goes wrong (Darko Miljanovic) if ( false === $res ) { // check error code if ( 1 === $this->_ole->error ) { die( 'Data is not readable' ); } elseif ( 2 === $this->_ole->error ) { die( 'OLE error' ); } // check other error codes here (e.g. bad fileformat, etc...) } $this->data = $this->_ole->getWorkBook(); $this->_parse(); } /** * Parse a workbook. * * @since 1.0.0 * * @return [type] [description] */ protected function _parse() { $pos = 0; $data = $this->data; // $code = $this->v( $data, $pos ); $length = $this->v( $data, $pos + 2 ); $version = $this->v( $data, $pos + 4 ); $substreamType = $this->v( $data, $pos + 6 ); if ( SPREADSHEET_EXCEL_READER_BIFF8 !== $version && SPREADSHEET_EXCEL_READER_BIFF7 !== $version ) { return false; } if ( SPREADSHEET_EXCEL_READER_WORKBOOKGLOBALS !== $substreamType ) { return false; } $pos += $length + 4; $code = $this->v( $data, $pos ); $length = $this->v( $data, $pos + 2 ); while ( SPREADSHEET_EXCEL_READER_TYPE_EOF !== $code ) { switch ( $code ) { case SPREADSHEET_EXCEL_READER_TYPE_SST: $spos = $pos + 4; $limitpos = $spos + $length; $uniqueStrings = $this->_GetInt4d( $data, $spos + 4 ); $spos += 8; for ( $i = 0; $i < $uniqueStrings; $i++ ) { // Read in the number of characters if ( $spos === $limitpos ) { $opcode = $this->v( $data, $spos ); $conlength = $this->v( $data, $spos + 2 ); if ( 0x3c !== $opcode ) { return -1; } $spos += 4; $limitpos = $spos + $conlength; } $numChars = ord( $data[ $spos ] ) | ( ord( $data[ $spos + 1 ] ) << 8 ); $spos += 2; $optionFlags = ord( $data[ $spos ] ); ++$spos; $asciiEncoding = ( 0 === ( $optionFlags & 0x01 ) ); $extendedString = ( 0 !== ( $optionFlags & 0x04 ) ); // See if string contains formatting information. $richString = ( 0 !== ( $optionFlags & 0x08 ) ); if ( $richString ) { // Read in the crun $formattingRuns = $this->v( $data, $spos ); $spos += 2; } if ( $extendedString ) { // Read in cchExtRst $extendedRunLength = $this->_GetInt4d( $data, $spos ); $spos += 4; } $len = ( $asciiEncoding ) ? $numChars : $numChars * 2; if ( $spos + $len < $limitpos ) { $retstr = substr( $data, $spos, $len ); $spos += $len; } else { // found continue $retstr = substr( $data, $spos, $limitpos - $spos ); $bytesRead = $limitpos - $spos; $charsLeft = $numChars - ( ( $asciiEncoding ) ? $bytesRead : ( $bytesRead / 2 ) ); $spos = $limitpos; while ( $charsLeft > 0 ) { $opcode = $this->v( $data, $spos ); $conlength = $this->v( $data, $spos + 2 ); if ( 0x3c !== $opcode ) { return -1; } $spos += 4; $limitpos = $spos + $conlength; $option = ord( $data[ $spos ] ); $spos += 1; if ( $asciiEncoding && 0 === $option ) { $len = min( $charsLeft, $limitpos - $spos ); // min( $charsLeft, $conlength ); $retstr .= substr( $data, $spos, $len ); $charsLeft -= $len; $asciiEncoding = true; } elseif ( ! $asciiEncoding && 0 !== $option ) { $len = min( $charsLeft * 2, $limitpos - $spos ); // min( $charsLeft, $conlength ); $retstr .= substr( $data, $spos, $len ); $charsLeft -= $len / 2; $asciiEncoding = false; } elseif ( ! $asciiEncoding && 0 === $option ) { // Bummer - the string starts off as Unicode, but after the // continuation it is in straightforward ASCII encoding $len = min( $charsLeft, $limitpos - $spos ); // min( $charsLeft, $conlength ); for ( $j = 0; $j < $len; $j++ ) { $retstr .= $data[ $spos + $j ] . chr( 0 ); } $charsLeft -= $len; $asciiEncoding = false; } else { $newstr = ''; for ( $j = 0; $j < strlen( $retstr ); $j++ ) { $newstr = $retstr[ $j ] . chr( 0 ); } $retstr = $newstr; $len = min( $charsLeft * 2, $limitpos - $spos ); // min( $charsLeft, $conlength ); $retstr .= substr( $data, $spos, $len ); $charsLeft -= $len / 2; $asciiEncoding = false; } $spos += $len; } } $retstr = ( $asciiEncoding ) ? $retstr : $this->_encodeUTF16( $retstr ); if ( $richString ) { $spos += 4 * $formattingRuns; } // For extended strings, skip over the extended string data if ( $extendedString ) { $spos += $extendedRunLength; } $this->sst[] = $retstr; } break; case SPREADSHEET_EXCEL_READER_TYPE_FILEPASS: return false; // break; // unreachable case SPREADSHEET_EXCEL_READER_TYPE_NAME: break; case SPREADSHEET_EXCEL_READER_TYPE_FORMAT: $indexCode = $this->v( $data, $pos + 4 ); if ( SPREADSHEET_EXCEL_READER_BIFF8 === $version ) { $numchars = $this->v( $data, $pos + 6 ); if ( 0 === ord( $data[ $pos + 8 ] ) ) { $formatString = substr( $data, $pos + 9, $numchars ); } else { $formatString = substr( $data, $pos + 9, $numchars * 2 ); } } else { $numchars = ord( $data[ $pos + 6 ] ); $formatString = substr( $data, $pos + 7, $numchars * 2 ); } $this->formatRecords[ $indexCode ] = $formatString; break; case SPREADSHEET_EXCEL_READER_TYPE_FONT: $height = $this->v( $data, $pos + 4 ); $option = $this->v( $data, $pos + 6 ); $color = $this->v( $data, $pos + 8 ); $weight = $this->v( $data, $pos + 10 ); $under = ord( $data[ $pos + 14 ] ); // Font name $numchars = ord( $data[ $pos + 18 ] ); if ( 0 === ( ord( $data[ $pos + 19 ] ) & 1 ) ) { $font = substr( $data, $pos + 20, $numchars ); } else { $font = substr( $data, $pos + 20, $numchars * 2 ); $font = $this->_encodeUTF16( $font ); } $this->fontRecords[] = array( 'height' => $height / 20, 'italic' => (bool) ( $option & 2 ), 'color' => $color, 'under' => ( 0 !== $under ), 'bold' => ( 700 === $weight ), 'font' => $font, 'raw' => $this->dumpHexData( $data, $pos + 3, $length ), ); break; case SPREADSHEET_EXCEL_READER_TYPE_PALETTE: $colors = ord( $data[ $pos + 4 ] ) | ord( $data[ $pos + 5 ] ) << 8; for ( $coli = 0; $coli < $colors; $coli++ ) { $colOff = $pos + 2 + ( $coli * 4 ); $colr = ord( $data[ $colOff ] ); $colg = ord( $data[ $colOff + 1 ] ); $colb = ord( $data[ $colOff + 2 ] ); $this->colors[ 0x07 + $coli ] = '#' . $this->myhex( $colr ) . $this->myhex( $colg ) . $this->myhex( $colb ); } break; case SPREADSHEET_EXCEL_READER_TYPE_XF: $fontIndexCode = ( ord( $data[ $pos + 4 ] ) | ord( $data[ $pos + 5 ] ) << 8 ) - 1; $fontIndexCode = max( 0, $fontIndexCode ); $indexCode = ord( $data[ $pos + 6 ] ) | ord( $data[ $pos + 7 ] ) << 8; $alignbit = ord( $data[ $pos + 10 ] ) & 3; $bgi = ( ord( $data[ $pos + 22 ] ) | ord( $data[ $pos + 23 ] ) << 8 ) & 0x3FFF; $bgcolor = ( $bgi & 0x7F ); // $bgcolor = ( $bgi & 0x3f80 ) >> 7; $align = ''; if ( 3 === $alignbit ) { $align = 'right'; } elseif ( 2 === $alignbit ) { $align = 'center'; } $fillPattern = ( ord( $data[ $pos + 21 ] ) & 0xFC ) >> 2; if ( 0 === $fillPattern ) { $bgcolor = ''; } $xf = array(); $xf['formatIndex'] = $indexCode; $xf['align'] = $align; $xf['fontIndex'] = $fontIndexCode; $xf['bgColor'] = $bgcolor; $xf['fillPattern'] = $fillPattern; $border = ord( $data[ $pos + 14 ] ) | ( ord( $data[ $pos + 15 ] ) << 8 ) | ( ord( $data[ $pos + 16 ] ) << 16 ) | ( ord( $data[ $pos + 17 ] ) << 24 ); $xf['borderLeft'] = $this->lineStyles[ ( $border & 0xF ) ]; $xf['borderRight'] = $this->lineStyles[ ( $border & 0xF0 ) >> 4 ]; $xf['borderTop'] = $this->lineStyles[ ( $border & 0xF00 ) >> 8 ]; $xf['borderBottom'] = $this->lineStyles[ ( $border & 0xF000 ) >> 12 ]; $xf['borderLeftColor'] = ( $border & 0x7F0000 ) >> 16; $xf['borderRightColor'] = ( $border & 0x3F800000 ) >> 23; $border = ( ord( $data[ $pos + 18 ] ) | ord( $data[ $pos + 19 ] ) << 8 ); $xf['borderTopColor'] = ( $border & 0x7F ); $xf['borderBottomColor'] = ( $border & 0x3F80 ) >> 7; if ( array_key_exists( $indexCode, $this->dateFormats ) ) { $xf['type'] = 'date'; $xf['format'] = $this->dateFormats[ $indexCode ]; if ( '' === $align ) { $xf['align'] = 'right'; } } elseif ( array_key_exists( $indexCode, $this->numberFormats ) ) { $xf['type'] = 'number'; $xf['format'] = $this->numberFormats[ $indexCode ]; if ( '' === $align ) { $xf['align'] = 'right'; } } else { $isdate = false; $formatstr = ''; if ( $indexCode > 0 ) { if ( isset( $this->formatRecords[ $indexCode ] ) ) { $formatstr = $this->formatRecords[ $indexCode ]; } if ( '' !== $formatstr ) { $tmp = preg_replace( '/\;.*/', '', $formatstr ); $tmp = preg_replace( '/^\[[^\]]*\]/', '', $tmp ); if ( 0 === preg_match( '/[^hmsday\/\-:\s\\\,AMP]/i', $tmp ) ) { // found day and time format $isdate = true; $formatstr = $tmp; $formatstr = str_replace( array( 'AM/PM', 'mmmm', 'mmm' ), array( 'a', 'F', 'M' ), $formatstr ); // m/mm are used for both minutes and months - oh SNAP! // This mess tries to fix for that. // 'm' = minutes only if following h/hh or preceding s/ss $formatstr = preg_replace( '/(h:?)mm?/', '$1i', $formatstr ); $formatstr = preg_replace( '/mm?(:?s)/', '1$1', $formatstr ); // A single 'm' = n in PHP $formatstr = preg_replace( '/(^|[^m])m([^m]|$)/', '$1n$2', $formatstr ); $formatstr = preg_replace( '/(^|[^m])m([^m]|$)/', '$1n$2', $formatstr ); // else it's months $formatstr = str_replace( 'mm', 'm', $formatstr ); // Convert single 'd' to 'j' $formatstr = preg_replace( '/(^|[^d])d([^d]|$)/', '$1j$2', $formatstr ); $formatstr = str_replace( array( 'dddd', 'ddd', 'dd', 'yyyy', 'yy', 'hh', 'h' ), array( 'l', 'D', 'd', 'Y', 'y', 'H', 'g' ), $formatstr ); $formatstr = preg_replace( '/ss?/', 's', $formatstr ); } } } if ( $isdate ) { $xf['type'] = 'date'; $xf['format'] = $formatstr; if ( '' === $align ) { $xf['align'] = 'right'; } } else { // If the format string has a 0 or # in it, we'll assume it's a number. if ( preg_match( '/[0#]/', $formatstr ) ) { $xf['type'] = 'number'; if ( '' === $align ) { $xf['align'] = 'right'; } } else { $xf['type'] = 'other'; } $xf['format'] = $formatstr; $xf['code'] = $indexCode; } } $this->xfRecords[] = $xf; break; case SPREADSHEET_EXCEL_READER_TYPE_NINETEENFOUR: $this->nineteenFour = ( 1 === ord( $data[ $pos + 4 ] ) ); break; case SPREADSHEET_EXCEL_READER_TYPE_BOUNDSHEET: $rec_offset = $this->_GetInt4d( $data, $pos + 4 ); // $rec_typeFlag = ord( $data[ $pos + 8 ] ); // $rec_visibilityFlag = ord( $data[ $pos + 9 ] ); $rec_length = ord( $data[ $pos + 10 ] ); if ( SPREADSHEET_EXCEL_READER_BIFF8 === $version ) { $chartype = ord( $data[ $pos + 11 ] ); if ( 0 === $chartype ) { $rec_name = substr( $data, $pos + 12, $rec_length ); } else { $rec_name = $this->_encodeUTF16( substr( $data, $pos + 12, 2 * $rec_length ) ); } } elseif ( SPREADSHEET_EXCEL_READER_BIFF7 === $version ) { $rec_name = substr( $data, $pos + 11, $rec_length ); } $this->boundsheets[] = array( 'name' => $rec_name, 'offset' => $rec_offset, ); break; } // switch $pos += $length + 4; $code = ord( $data[ $pos ] ) | ord( $data[ $pos + 1 ] ) << 8; $length = ord( $data[ $pos + 2 ] ) | ord( $data[ $pos + 3 ] ) << 8; } // while foreach ( $this->boundsheets as $key => $val ) { $this->sn = $key; $this->_parsesheet( $val['offset'] ); } return true; } /** * Parse a worksheet. * * @since 1.0.0 * * @param [type] $spos [description] * @return [type] [description] */ protected function _parsesheet( $spos ) { $cont = true; $data = $this->data; // read BOF // $code = ord( $data[ $spos ] ) | ord( $data[ $spos + 1 ] ) << 8; $length = ord( $data[ $spos + 2 ] ) | ord( $data[ $spos + 3 ] ) << 8; $version = ord( $data[ $spos + 4 ] ) | ord( $data[ $spos + 5 ] ) << 8; $substreamType = ord( $data[ $spos + 6 ] ) | ord( $data[ $spos + 7 ] ) << 8; if ( SPREADSHEET_EXCEL_READER_BIFF8 !== $version && SPREADSHEET_EXCEL_READER_BIFF7 !== $version ) { return -1; } if ( SPREADSHEET_EXCEL_READER_WORKSHEET !== $substreamType ) { return -2; } $spos += $length + 4; while ( $cont ) { $lowcode = ord( $data[ $spos ] ); if ( SPREADSHEET_EXCEL_READER_TYPE_EOF === $lowcode ) { break; } $code = $lowcode | ord( $data[ $spos + 1 ] ) << 8; $length = ord( $data[ $spos + 2 ] ) | ord( $data[ $spos + 3 ] ) << 8; $spos += 4; $this->sheets[ $this->sn ]['maxrow'] = $this->_rowoffset - 1; $this->sheets[ $this->sn ]['maxcol'] = $this->_coloffset - 1; unset( $this->rectype ); switch ( $code ) { case SPREADSHEET_EXCEL_READER_TYPE_DIMENSION: if ( ! isset( $this->numRows ) ) { if ( 10 === $length || SPREADSHEET_EXCEL_READER_BIFF7 === $version ) { $this->sheets[ $this->sn ]['numRows'] = ord( $data[ $spos + 2 ] ) | ord( $data[ $spos + 3 ] ) << 8; $this->sheets[ $this->sn ]['numCols'] = ord( $data[ $spos + 6 ] ) | ord( $data[ $spos + 7 ] ) << 8; } else { $this->sheets[ $this->sn ]['numRows'] = ord( $data[ $spos + 4 ] ) | ord( $data[ $spos + 5 ] ) << 8; $this->sheets[ $this->sn ]['numCols'] = ord( $data[ $spos + 10 ] ) | ord( $data[ $spos + 11 ] ) << 8; } } break; case SPREADSHEET_EXCEL_READER_TYPE_MERGEDCELLS: $cellRanges = ord( $data[ $spos ] ) | ord( $data[ $spos + 1 ] ) << 8; for ( $i = 0; $i < $cellRanges; $i++ ) { $fr = ord( $data[ $spos + 8 * $i + 2 ] ) | ord( $data[ $spos + 8 * $i + 3 ] ) << 8; $lr = ord( $data[ $spos + 8 * $i + 4 ] ) | ord( $data[ $spos + 8 * $i + 5 ] ) << 8; $fc = ord( $data[ $spos + 8 * $i + 6 ] ) | ord( $data[ $spos + 8 * $i + 7 ] ) << 8; $lc = ord( $data[ $spos + 8 * $i + 8 ] ) | ord( $data[ $spos + 8 * $i + 9 ] ) << 8; if ( $lr - $fr > 0 ) { $this->sheets[ $this->sn ]['cellsInfo'][ $fr + 1 ][ $fc + 1 ]['rowspan'] = $lr - $fr + 1; } if ( $lc - $fc > 0 ) { $this->sheets[ $this->sn ]['cellsInfo'][ $fr + 1 ][ $fc + 1 ]['colspan'] = $lc - $fc + 1; } } break; case SPREADSHEET_EXCEL_READER_TYPE_RK: case SPREADSHEET_EXCEL_READER_TYPE_RK2: $row = ord( $data[ $spos ] ) | ord( $data[ $spos + 1 ] ) << 8; $column = ord( $data[ $spos + 2 ] ) | ord( $data[ $spos + 3 ] ) << 8; $rknum = $this->_GetInt4d( $data, $spos + 6 ); $numValue = $this->_GetIEEE754( $rknum ); $info = $this->_getCellDetails( $spos, $numValue, $column ); $this->addcell( $row, $column, $info['string'], $info ); break; case SPREADSHEET_EXCEL_READER_TYPE_LABELSST: $row = ord( $data[ $spos ] ) | ord( $data[ $spos + 1 ] ) << 8; $column = ord( $data[ $spos + 2 ] ) | ord( $data[ $spos + 3 ] ) << 8; $xfindex = ord( $data[ $spos + 4 ] ) | ord( $data[ $spos + 5 ] ) << 8; $index = $this->_GetInt4d( $data, $spos + 6 ); $this->addcell( $row, $column, $this->sst[ $index ], array( 'xfIndex' => $xfindex ) ); break; case SPREADSHEET_EXCEL_READER_TYPE_MULRK: $row = ord( $data[ $spos ] ) | ord( $data[ $spos + 1 ] ) << 8; $colFirst = ord( $data[ $spos + 2 ] ) | ord( $data[ $spos + 3 ] ) << 8; $colLast = ord( $data[ $spos + $length - 2 ] ) | ord( $data[ $spos + $length - 1 ] ) << 8; $columns = $colLast - $colFirst + 1; $tmppos = $spos + 4; for ( $i = 0; $i < $columns; $i++ ) { $numValue = $this->_GetIEEE754( $this->_GetInt4d( $data, $tmppos + 2 ) ); $info = $this->_getCellDetails( $tmppos - 4, $numValue, $colFirst + $i + 1 ); $tmppos += 6; $this->addcell( $row, $colFirst + $i, $info['string'], $info ); } break; case SPREADSHEET_EXCEL_READER_TYPE_NUMBER: $row = ord( $data[ $spos ] ) | ord( $data[ $spos + 1 ] ) << 8; $column = ord( $data[ $spos + 2 ] ) | ord( $data[ $spos + 3 ] ) << 8; $tmp = unpack( 'ddouble', substr( $data, $spos + 6, 8 ) ); // It machine machine dependent if ( $this->isDate( $spos ) ) { $numValue = $tmp['double']; } else { $numValue = $this->createNumber( $spos ); } $info = $this->_getCellDetails( $spos, $numValue, $column ); $this->addcell( $row, $column, $info['string'], $info ); break; case SPREADSHEET_EXCEL_READER_TYPE_FORMULA: case SPREADSHEET_EXCEL_READER_TYPE_FORMULA2: $row = ord( $data[ $spos ] ) | ord( $data[ $spos + 1 ] ) << 8; $column = ord( $data[ $spos + 2 ] ) | ord( $data[ $spos + 3 ] ) << 8; if ( 0 === ord( $data[ $spos + 6 ] ) && 255 === ord( $data[ $spos + 12 ] ) && 255 === ord( $data[ $spos + 13 ] ) ) { // String formula. Result follows in a STRING record // This row/col are stored to be referenced in that record // https://code.google.com/archive/p/php-excel-reader/issues/4 $previousRow = $row; $previousCol = $column; } elseif ( 1 === ord( $data[ $spos + 6 ] ) && 255 === ord( $data[ $spos + 12 ] ) && 255 === ord( $data[ $spos + 13 ] ) ) { // Boolean formula. Result is in +2; 0 = false,1 = true // https://code.google.com/archive/p/php-excel-reader/issues/4 if ( 1 === ord( $this->data[ $spos + 8 ] ) ) { $this->addcell( $row, $column, 'TRUE' ); } else { $this->addcell( $row, $column, 'FALSE' ); } } elseif ( 2 === ord( $data[ $spos + 6 ] ) && 255 === ord( $data[ $spos + 12 ] ) && 255 === ord( $data[ $spos + 13 ] ) ) { // Error formula. Error code is in +2; } elseif ( 3 === ord( $data[ $spos + 6 ] ) && 255 === ord( $data[ $spos + 12 ] ) && 255 === ord( $data[ $spos + 13 ] ) ) { // Formula result is a null string. $this->addcell( $row, $column, '' ); } else { // Result is a number, so first 14 bytes are just like a _NUMBER record $tmp = unpack( 'ddouble', substr( $data, $spos + 6, 8 ) ); // machine dependent if ( $this->isDate( $spos ) ) { $numValue = $tmp['double']; } else { $numValue = $this->createNumber( $spos ); } $info = $this->_getCellDetails( $spos, $numValue, $column ); $this->addcell( $row, $column, $info['string'], $info ); } break; case SPREADSHEET_EXCEL_READER_TYPE_BOOLERR: $row = ord( $data[ $spos ] ) | ord( $data[ $spos + 1 ] ) << 8; $column = ord( $data[ $spos + 2 ] ) | ord( $data[ $spos + 3 ] ) << 8; $a_string = ord( $data[ $spos + 6 ] ); $this->addcell( $row, $column, $a_string ); break; case SPREADSHEET_EXCEL_READER_TYPE_STRING: // https://code.google.com/archive/p/php-excel-reader/issues/4 if ( SPREADSHEET_EXCEL_READER_BIFF8 === $version ) { // Unicode 16 string, like an SST record $xpos = $spos; $numChars = ord( $data[ $xpos ] ) | ( ord( $data[ $xpos + 1 ] ) << 8 ); $xpos += 2; $optionFlags = ord( $data[ $xpos ] ); ++$xpos; $asciiEncoding = ( 0 === ( $optionFlags & 0x01 ) ); $extendedString = ( 0 !== ( $optionFlags & 0x04 ) ); // See if string contains formatting information $richString = ( 0 !== ( $optionFlags & 0x08 ) ); if ( $richString ) { // Read in the crun // $formattingRuns = ord( $data[ $xpos ] ) | ( ord( $data[ $xpos + 1 ] ) << 8 ); $xpos += 2; } if ( $extendedString ) { // Read in cchExtRst // $extendedRunLength =$this->_GetInt4d( $this->data, $xpos ); $xpos += 4; } $len = ( $asciiEncoding ) ? $numChars : $numChars * 2; $retstr = substr( $data, $xpos, $len ); $xpos += $len; $retstr = ( $asciiEncoding ) ? $retstr : $this->_encodeUTF16( $retstr ); } elseif ( SPREADSHEET_EXCEL_READER_BIFF7 === $version ) { // Simple byte string $xpos = $spos; $numChars = ord( $data[ $xpos ] ) | ( ord( $data[ $xpos + 1 ] ) << 8 ); $xpos += 2; $retstr = substr( $data, $xpos, $numChars ); } $this->addcell( $previousRow, $previousCol, $retstr ); break; case SPREADSHEET_EXCEL_READER_TYPE_ROW: $row = ord( $data[ $spos ] ) | ord( $data[ $spos + 1 ] ) << 8; $rowInfo = ord( $data[ $spos + 6 ] ) | ( ( ord( $data[ $spos + 7 ] ) << 8 ) & 0x7FFF ); if ( ( $rowInfo & 0x8000 ) > 0 ) { $rowHeight = -1; } else { $rowHeight = $rowInfo & 0x7FFF; } $rowHidden = ( ord( $data[ $spos + 12 ] ) & 0x20 ) >> 5; $this->rowInfo[ $this->sn ][ $row + 1 ] = array( 'height' => $rowHeight / 20, 'hidden' => $rowHidden, ); break; case SPREADSHEET_EXCEL_READER_TYPE_DBCELL: break; case SPREADSHEET_EXCEL_READER_TYPE_MULBLANK: $row = ord( $data[ $spos ] ) | ord( $data[ $spos + 1 ] ) << 8; $column = ord( $data[ $spos + 2 ] ) | ord( $data[ $spos + 3 ] ) << 8; $cols = ( $length / 2 ) - 3; for ( $c = 0; $c < $cols; $c++ ) { $xfindex = ord( $data[ $spos + 4 + ( $c * 2 ) ] ) | ord( $data[ $spos + 5 + ( $c * 2 ) ] ) << 8; $this->addcell( $row, $column + $c, '', array( 'xfIndex' => $xfindex ) ); } break; case SPREADSHEET_EXCEL_READER_TYPE_LABEL: $row = ord( $data[ $spos ] ) | ord( $data[ $spos + 1 ] ) << 8; $column = ord( $data[ $spos + 2 ] ) | ord( $data[ $spos + 3 ] ) << 8; $this->addcell( $row, $column, substr( $data, $spos + 8, ord( $data[ $spos + 6 ] ) | ord( $data[ $spos + 7 ] ) << 8 ) ); break; case SPREADSHEET_EXCEL_READER_TYPE_EOF: $cont = false; break; case SPREADSHEET_EXCEL_READER_TYPE_HYPER: // Only handle hyperlinks to a URL $row = ord( $this->data[ $spos ] ) | ord( $this->data[ $spos + 1 ] ) << 8; $row2 = ord( $this->data[ $spos + 2 ] ) | ord( $this->data[ $spos + 3 ] ) << 8; $column = ord( $this->data[ $spos + 4 ] ) | ord( $this->data[ $spos + 5 ] ) << 8; $column2 = ord( $this->data[ $spos + 6 ] ) | ord( $this->data[ $spos + 7 ] ) << 8; $linkdata = array(); $flags = ord( $this->data[ $spos + 28 ] ); $udesc = ''; $ulink = ''; $uloc = 32; $linkdata['flags'] = $flags; if ( ( $flags & 1 ) > 0 ) { // is a type we understand // is there a description ? if ( 0x14 === ( $flags & 0x14 ) ) { // has a description $uloc += 4; $descLen = ord( $this->data[ $spos + 32 ] ) | ord( $this->data[ $spos + 33 ] ) << 8; $udesc = substr( $this->data, $spos + $uloc, $descLen * 2 ); $uloc += 2 * $descLen; } $ulink = $this->read16bitstring( $this->data, $spos + $uloc + 20 ); if ( '' === $udesc ) { $udesc = $ulink; } } $linkdata['desc'] = $udesc; $linkdata['link'] = $this->_encodeUTF16( $ulink ); for ( $r = $row; $r <= $row2; $r++ ) { for ( $c = $column; $c <= $column2; $c++ ) { $this->sheets[ $this->sn ]['cellsInfo'][ $r + 1 ][ $c + 1 ]['hyperlink'] = $linkdata; } } break; case SPREADSHEET_EXCEL_READER_TYPE_DEFCOLWIDTH: $this->defaultColWidth = ord( $data[ $spos + 4 ] ) | ord( $data[ $spos + 5 ] ) << 8; break; case SPREADSHEET_EXCEL_READER_TYPE_STANDARDWIDTH: $this->standardColWidth = ord( $data[ $spos + 4 ] ) | ord( $data[ $spos + 5 ] ) << 8; break; case SPREADSHEET_EXCEL_READER_TYPE_COLINFO: $colfrom = ord( $data[ $spos + 0 ] ) | ord( $data[ $spos + 1 ] ) << 8; $colto = ord( $data[ $spos + 2 ] ) | ord( $data[ $spos + 3 ] ) << 8; $cw = ord( $data[ $spos + 4 ] ) | ord( $data[ $spos + 5 ] ) << 8; $cxf = ord( $data[ $spos + 6 ] ) | ord( $data[ $spos + 7 ] ) << 8; $co = ord( $data[ $spos + 8 ] ); for ( $coli = $colfrom; $coli <= $colto; $coli++ ) { $this->colInfo[ $this->sn ][ $coli + 1 ] = array( 'width' => $cw, 'xf' => $cxf, 'hidden' => ( $co & 0x01 ), 'collapsed' => ( $co & 0x1000 ) >> 12, ); } break; default: break; } // switch $spos += $length; } // while if ( ! isset( $this->sheets[ $this->sn ]['numRows'] ) ) { $this->sheets[ $this->sn ]['numRows'] = $this->sheets[ $this->sn ]['maxrow']; } if ( ! isset( $this->sheets[ $this->sn ]['numCols'] ) ) { $this->sheets[ $this->sn ]['numCols'] = $this->sheets[ $this->sn ]['maxcol']; } } /** * [isDate description] * * @since 1.0.0 * * @param [type] $spos [description] * @return bool [description] */ protected function isDate( $spos ): bool { $xfindex = ord( $this->data[ $spos + 4 ] ) | ord( $this->data[ $spos + 5 ] ) << 8; return ( 'date' === $this->xfRecords[ $xfindex ]['type'] ); } /** * [gmgetdate description] * * @since 1.0.0 * * @param int $ts Optional. Timestamp. Defaults to current Unix timestamp. * @return array [description] */ protected function gmgetdate( $ts = null ): array { $k = array( 'seconds', 'minutes', 'hours', 'mday', 'wday', 'mon', 'year', 'yday', 'weekday', 'month', 0 ); return array_combine( $k, explode( ':', gmdate( 's:i:G:j:w:n:Y:z:l:F:U', is_null( $ts ) ? time() : $ts ) ) ); } /** * Gets the details for a particular cell. * * @since 1.0.0 * * @param [type] $spos [description] * @param [type] $numValue [description] * @param [type] $column [description] * @return array [description] */ protected function _getCellDetails( $spos, $numValue, $column ): array { $xfindex = ord( $this->data[ $spos + 4 ] ) | ord( $this->data[ $spos + 5 ] ) << 8; $xfrecord = $this->xfRecords[ $xfindex ]; $type = $xfrecord['type']; $format = $xfrecord['format']; $formatIndex = $xfrecord['formatIndex']; $fontIndex = $xfrecord['fontIndex']; $formatColor = ''; if ( isset( $this->_columnsFormat[ $column + 1 ] ) ) { $format = $this->_columnsFormat[ $column + 1 ]; } if ( 'date' === $type ) { // See https://groups.google.com/forum/#!topic/php-excel-reader-discuss/nD-XkNEtjhA $rectype = 'date'; // Convert numeric value into a date $utcDays = floor( $numValue - ( $this->nineteenFour ? SPREADSHEET_EXCEL_READER_UTCOFFSETDAYS1904 : SPREADSHEET_EXCEL_READER_UTCOFFSETDAYS ) ); $utcValue = ( $utcDays ) * SPREADSHEET_EXCEL_READER_MSINADAY; $dateinfo = $this->gmgetdate( $utcValue ); $raw = $numValue; $fractionalDay = $numValue - floor( $numValue ) + 0.0000001; // The 0.0000001 is to fix for php/excel fractional diffs $totalseconds = floor( SPREADSHEET_EXCEL_READER_MSINADAY * $fractionalDay ); $secs = $totalseconds % 60; $totalseconds -= $secs; $hours = intdiv( $totalseconds, 60 * 60 ); $mins = intdiv( $totalseconds, 60 ) % 60; $a_string = date( $format, mktime( $hours, $mins, $secs, $dateinfo['mon'], $dateinfo['mday'], $dateinfo['year'] ) ); } elseif ( 'number' === $type ) { $rectype = 'number'; $formatted = $this->_format_value( $format, $numValue, $formatIndex ); $a_string = $formatted['string']; $formatColor = $formatted['formatColor']; $raw = $numValue; } else { if ( '' === $format ) { $format = $this->_defaultFormat; } $rectype = 'unknown'; $formatted = $this->_format_value( $format, $numValue, $formatIndex ); $a_string = $formatted['string']; $formatColor = $formatted['formatColor']; $raw = $numValue; } return array( 'string' => $string, 'raw' => $raw, 'rectype' => $rectype, 'format' => $format, 'formatIndex' => $formatIndex, 'fontIndex' => $fontIndex, 'formatColor' => $formatColor, 'xfIndex' => $xfindex, ); } /** * [createNumber description] * * @since 1.0.0 * * @param [type] $spos [description] * @return [type] [description] */ protected function createNumber( $spos ) { $rknumhigh = $this->_GetInt4d( $this->data, $spos + 10 ); $rknumlow = $this->_GetInt4d( $this->data, $spos + 6 ); $sign = ( $rknumhigh & 0x80000000 ) >> 31; $exp = ( $rknumhigh & 0x7ff00000 ) >> 20; $mantissa = ( 0x100000 | ( $rknumhigh & 0x000fffff ) ); $mantissalow1 = ( $rknumlow & 0x80000000 ) >> 31; $mantissalow2 = ( $rknumlow & 0x7fffffff ); $value = $mantissa / pow( 2, ( 20 - ( $exp - 1023 ) ) ); if ( 0 !== $mantissalow1 ) { $value += 1 / pow( 2, ( 21 - ( $exp - 1023 ) ) ); } $value += $mantissalow2 / pow( 2, ( 52 - ( $exp - 1023 ) ) ); if ( $sign ) { $value *= -1; } return $value; } /** * [addcell description] * * @since 1.0.0 * * @param [type] $row [description] * @param [type] $col [description] * @param [type] $a_string [description] * @param array $info Optional. [description] * @return [type] [description] */ protected function addcell( $row, $col, $string, $info = null ) { $this->sheets[ $this->sn ]['maxrow'] = max( $this->sheets[ $this->sn ]['maxrow'], $row + $this->_rowoffset ); $this->sheets[ $this->sn ]['maxcol'] = max( $this->sheets[ $this->sn ]['maxcol'], $col + $this->_coloffset ); $this->sheets[ $this->sn ]['cells'][ $row + $this->_rowoffset ][ $col + $this->_coloffset ] = $string; if ( $this->store_extended_info && $info ) { foreach ( $info as $key => $val ) { $this->sheets[ $this->sn ]['cellsInfo'][ $row + $this->_rowoffset ][ $col + $this->_coloffset ][ $key ] = $val; } } } /** * [_GetIEEE754 description] * * @since 1.0.0 * * @param [type] $rknum [description] * @return [type] [description] */ protected function _GetIEEE754( $rknum ) { if ( 0 !== ( $rknum & 0x02 ) ) { $value = $rknum >> 2; } else { // info on IEEE754 encoding from // http://research.microsoft.com/~hollasch/cgindex/coding/ieeefloat.html // The RK format calls for using only the most significant 30 bits of the // 64 bit floating point value. The other 34 bits are assumed to be 0 // So, we use the upper 30 bits of $rknum as follows... $sign = ( $rknum & 0x80000000 ) >> 31; $exp = ( $rknum & 0x7ff00000 ) >> 20; $mantissa = ( 0x100000 | ( $rknum & 0x000ffffc ) ); $value = $mantissa / pow( 2, ( 20 - ( $exp - 1023 ) ) ); if ( $sign ) { $value *= -1; } } if ( 0 !== ( $rknum & 0x01 ) ) { $value /= 100; } return $value; } /** * [_encodeUTF16 description] * * @since 1.0.0 * * @param [type] $a_string [description] * @return [type] [description] */ protected function _encodeUTF16( $a_string ) { if ( $this->_defaultEncoding ) { switch ( $this->_encoderFunction ) { case 'iconv': $a_string = iconv( 'UTF-16LE', $this->_defaultEncoding, $a_string ); break; case 'mb_convert_encoding': $a_string = mb_convert_encoding( $string, $this->_defaultEncoding, 'UTF-16LE' ); break; } } return $string; } /** * [_GetInt4d description] * * @since 1.0.0 * * @param [type] $data [description] * @param [type] $pos [description] * @return int [description] */ protected function _GetInt4d( $data, $pos ): int { $value = ord( $data[ $pos ] ) | ( ord( $data[ $pos + 1 ] ) << 8 ) | ( ord( $data[ $pos + 2 ] ) << 16 ) | ( ord( $data[ $pos + 3 ] ) << 24 ); if ( $value >= 4294967294 ) { $value = -2; } return $value; } /** * [v description] * * @since 1.0.0 * * @param [type] $data [description] * @param [type] $pos [description] * @return int [description] */ protected function v( $data, $pos ): int { return ord( $data[ $pos ] ) | ord( $data[ $pos + 1 ] ) << 8; } } // class Spreadsheet_Excel_Reader PK!gb&libraries/vendor/autoload-classmap.phpnu[ $strauss_src . '/PhpSpreadsheet/Reader/Gnumeric/Properties.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Gnumeric\Styles' => $strauss_src . '/PhpSpreadsheet/Reader/Gnumeric/Styles.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Gnumeric\PageSetup' => $strauss_src . '/PhpSpreadsheet/Reader/Gnumeric/PageSetup.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xls\DataValidationHelper' => $strauss_src . '/PhpSpreadsheet/Reader/Xls/DataValidationHelper.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xls\Color\BuiltIn' => $strauss_src . '/PhpSpreadsheet/Reader/Xls/Color/BuiltIn.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xls\Color\BIFF8' => $strauss_src . '/PhpSpreadsheet/Reader/Xls/Color/BIFF8.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xls\Color\BIFF5' => $strauss_src . '/PhpSpreadsheet/Reader/Xls/Color/BIFF5.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xls\ListFunctions' => $strauss_src . '/PhpSpreadsheet/Reader/Xls/ListFunctions.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xls\Style\Border' => $strauss_src . '/PhpSpreadsheet/Reader/Xls/Style/Border.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xls\Style\CellFont' => $strauss_src . '/PhpSpreadsheet/Reader/Xls/Style/CellFont.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xls\Style\CellAlignment' => $strauss_src . '/PhpSpreadsheet/Reader/Xls/Style/CellAlignment.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xls\Style\FillPattern' => $strauss_src . '/PhpSpreadsheet/Reader/Xls/Style/FillPattern.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xls\ErrorCode' => $strauss_src . '/PhpSpreadsheet/Reader/Xls/ErrorCode.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xls\Escher' => $strauss_src . '/PhpSpreadsheet/Reader/Xls/Escher.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xls\Biff8' => $strauss_src . '/PhpSpreadsheet/Reader/Xls/Biff8.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xls\Biff5' => $strauss_src . '/PhpSpreadsheet/Reader/Xls/Biff5.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xls\RC4' => $strauss_src . '/PhpSpreadsheet/Reader/Xls/RC4.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xls\Mappings' => $strauss_src . '/PhpSpreadsheet/Reader/Xls/Mappings.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xls\Color' => $strauss_src . '/PhpSpreadsheet/Reader/Xls/Color.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xls\LoadSpreadsheet' => $strauss_src . '/PhpSpreadsheet/Reader/Xls/LoadSpreadsheet.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xls\MD5' => $strauss_src . '/PhpSpreadsheet/Reader/Xls/MD5.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xls\ConditionalFormatting' => $strauss_src . '/PhpSpreadsheet/Reader/Xls/ConditionalFormatting.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Ods\DefinedNames' => $strauss_src . '/PhpSpreadsheet/Reader/Ods/DefinedNames.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Ods\Properties' => $strauss_src . '/PhpSpreadsheet/Reader/Ods/Properties.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Ods\AutoFilter' => $strauss_src . '/PhpSpreadsheet/Reader/Ods/AutoFilter.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Ods\PageSettings' => $strauss_src . '/PhpSpreadsheet/Reader/Ods/PageSettings.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Ods\FormulaTranslator' => $strauss_src . '/PhpSpreadsheet/Reader/Ods/FormulaTranslator.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Ods\BaseLoader' => $strauss_src . '/PhpSpreadsheet/Reader/Ods/BaseLoader.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Gnumeric' => $strauss_src . '/PhpSpreadsheet/Reader/Gnumeric.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner' => $strauss_src . '/PhpSpreadsheet/Reader/Security/XmlScanner.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xlsx\Properties' => $strauss_src . '/PhpSpreadsheet/Reader/Xlsx/Properties.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xlsx\AutoFilter' => $strauss_src . '/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xlsx\Theme' => $strauss_src . '/PhpSpreadsheet/Reader/Xlsx/Theme.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xlsx\SheetViewOptions' => $strauss_src . '/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xlsx\Chart' => $strauss_src . '/PhpSpreadsheet/Reader/Xlsx/Chart.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xlsx\Styles' => $strauss_src . '/PhpSpreadsheet/Reader/Xlsx/Styles.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xlsx\SharedFormula' => $strauss_src . '/PhpSpreadsheet/Reader/Xlsx/SharedFormula.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xlsx\WorkbookView' => $strauss_src . '/PhpSpreadsheet/Reader/Xlsx/WorkbookView.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xlsx\SheetViews' => $strauss_src . '/PhpSpreadsheet/Reader/Xlsx/SheetViews.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xlsx\DataValidations' => $strauss_src . '/PhpSpreadsheet/Reader/Xlsx/DataValidations.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xlsx\ColumnAndRowAttributes' => $strauss_src . '/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xlsx\Hyperlinks' => $strauss_src . '/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xlsx\BaseParserClass' => $strauss_src . '/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces' => $strauss_src . '/PhpSpreadsheet/Reader/Xlsx/Namespaces.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xlsx\ConditionalStyles' => $strauss_src . '/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xlsx\TableReader' => $strauss_src . '/PhpSpreadsheet/Reader/Xlsx/TableReader.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xlsx\PageSetup' => $strauss_src . '/PhpSpreadsheet/Reader/Xlsx/PageSetup.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xml' => $strauss_src . '/PhpSpreadsheet/Reader/Xml.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\IReadFilter' => $strauss_src . '/PhpSpreadsheet/Reader/IReadFilter.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\IReader' => $strauss_src . '/PhpSpreadsheet/Reader/IReader.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xml\Properties' => $strauss_src . '/PhpSpreadsheet/Reader/Xml/Properties.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xml\Style\Border' => $strauss_src . '/PhpSpreadsheet/Reader/Xml/Style/Border.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xml\Style\Alignment' => $strauss_src . '/PhpSpreadsheet/Reader/Xml/Style/Alignment.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xml\Style\Font' => $strauss_src . '/PhpSpreadsheet/Reader/Xml/Style/Font.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xml\Style\Fill' => $strauss_src . '/PhpSpreadsheet/Reader/Xml/Style/Fill.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xml\Style\NumberFormat' => $strauss_src . '/PhpSpreadsheet/Reader/Xml/Style/NumberFormat.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xml\Style\StyleBase' => $strauss_src . '/PhpSpreadsheet/Reader/Xml/Style/StyleBase.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xml\DataValidations' => $strauss_src . '/PhpSpreadsheet/Reader/Xml/DataValidations.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xml\PageSettings' => $strauss_src . '/PhpSpreadsheet/Reader/Xml/PageSettings.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xml\Style' => $strauss_src . '/PhpSpreadsheet/Reader/Xml/Style.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\BaseReader' => $strauss_src . '/PhpSpreadsheet/Reader/BaseReader.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\XlsBase' => $strauss_src . '/PhpSpreadsheet/Reader/XlsBase.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xls' => $strauss_src . '/PhpSpreadsheet/Reader/Xls.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Html' => $strauss_src . '/PhpSpreadsheet/Reader/Html.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Csv\Delimiter' => $strauss_src . '/PhpSpreadsheet/Reader/Csv/Delimiter.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\DefaultReadFilter' => $strauss_src . '/PhpSpreadsheet/Reader/DefaultReadFilter.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Exception' => $strauss_src . '/PhpSpreadsheet/Reader/Exception.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Ods' => $strauss_src . '/PhpSpreadsheet/Reader/Ods.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Csv' => $strauss_src . '/PhpSpreadsheet/Reader/Csv.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Xlsx' => $strauss_src . '/PhpSpreadsheet/Reader/Xlsx.php', 'TablePress\PhpOffice\PhpSpreadsheet\Reader\Slk' => $strauss_src . '/PhpSpreadsheet/Reader/Slk.php', 'TablePress\PhpOffice\PhpSpreadsheet\Collection\Cells' => $strauss_src . '/PhpSpreadsheet/Collection/Cells.php', 'TablePress\PhpOffice\PhpSpreadsheet\Collection\Memory\SimpleCache1' => $strauss_src . '/PhpSpreadsheet/Collection/Memory/SimpleCache1.php', 'TablePress\PhpOffice\PhpSpreadsheet\Collection\Memory\SimpleCache3' => $strauss_src . '/PhpSpreadsheet/Collection/Memory/SimpleCache3.php', 'TablePress\PhpOffice\PhpSpreadsheet\Collection\CellsFactory' => $strauss_src . '/PhpSpreadsheet/Collection/CellsFactory.php', 'TablePress\PhpOffice\PhpSpreadsheet\Settings' => $strauss_src . '/PhpSpreadsheet/Settings.php', 'TablePress\PhpOffice\PhpSpreadsheet\IComparable' => $strauss_src . '/PhpSpreadsheet/IComparable.php', 'TablePress\PhpOffice\PhpSpreadsheet\NamedRange' => $strauss_src . '/PhpSpreadsheet/NamedRange.php', 'TablePress\PhpOffice\PhpSpreadsheet\Spreadsheet' => $strauss_src . '/PhpSpreadsheet/Spreadsheet.php', 'TablePress\PhpOffice\PhpSpreadsheet\Worksheet\PageMargins' => $strauss_src . '/PhpSpreadsheet/Worksheet/PageMargins.php', 'TablePress\PhpOffice\PhpSpreadsheet\Worksheet\Drawing\Shadow' => $strauss_src . '/PhpSpreadsheet/Worksheet/Drawing/Shadow.php', 'TablePress\PhpOffice\PhpSpreadsheet\Worksheet\AutoFit' => $strauss_src . '/PhpSpreadsheet/Worksheet/AutoFit.php', 'TablePress\PhpOffice\PhpSpreadsheet\Worksheet\HeaderFooter' => $strauss_src . '/PhpSpreadsheet/Worksheet/HeaderFooter.php', 'TablePress\PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter' => $strauss_src . '/PhpSpreadsheet/Worksheet/AutoFilter.php', 'TablePress\PhpOffice\PhpSpreadsheet\Worksheet\RowIterator' => $strauss_src . '/PhpSpreadsheet/Worksheet/RowIterator.php', 'TablePress\PhpOffice\PhpSpreadsheet\Worksheet\Drawing' => $strauss_src . '/PhpSpreadsheet/Worksheet/Drawing.php', 'TablePress\PhpOffice\PhpSpreadsheet\Worksheet\Dimension' => $strauss_src . '/PhpSpreadsheet/Worksheet/Dimension.php', 'TablePress\PhpOffice\PhpSpreadsheet\Worksheet\ProtectedRange' => $strauss_src . '/PhpSpreadsheet/Worksheet/ProtectedRange.php', 'TablePress\PhpOffice\PhpSpreadsheet\Worksheet\SheetView' => $strauss_src . '/PhpSpreadsheet/Worksheet/SheetView.php', 'TablePress\PhpOffice\PhpSpreadsheet\Worksheet\Pane' => $strauss_src . '/PhpSpreadsheet/Worksheet/Pane.php', 'TablePress\PhpOffice\PhpSpreadsheet\Worksheet\HeaderFooterDrawing' => $strauss_src . '/PhpSpreadsheet/Worksheet/HeaderFooterDrawing.php', 'TablePress\PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing' => $strauss_src . '/PhpSpreadsheet/Worksheet/MemoryDrawing.php', 'TablePress\PhpOffice\PhpSpreadsheet\Worksheet\Row' => $strauss_src . '/PhpSpreadsheet/Worksheet/Row.php', 'TablePress\PhpOffice\PhpSpreadsheet\Worksheet\PageBreak' => $strauss_src . '/PhpSpreadsheet/Worksheet/PageBreak.php', 'TablePress\PhpOffice\PhpSpreadsheet\Worksheet\ColumnCellIterator' => $strauss_src . '/PhpSpreadsheet/Worksheet/ColumnCellIterator.php', 'TablePress\PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule' => $strauss_src . '/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.php', 'TablePress\PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column' => $strauss_src . '/PhpSpreadsheet/Worksheet/AutoFilter/Column.php', 'TablePress\PhpOffice\PhpSpreadsheet\Worksheet\RowCellIterator' => $strauss_src . '/PhpSpreadsheet/Worksheet/RowCellIterator.php', 'TablePress\PhpOffice\PhpSpreadsheet\Worksheet\ColumnIterator' => $strauss_src . '/PhpSpreadsheet/Worksheet/ColumnIterator.php', 'TablePress\PhpOffice\PhpSpreadsheet\Worksheet\Table\TableDxfsStyle' => $strauss_src . '/PhpSpreadsheet/Worksheet/Table/TableDxfsStyle.php', 'TablePress\PhpOffice\PhpSpreadsheet\Worksheet\Table\TableStyle' => $strauss_src . '/PhpSpreadsheet/Worksheet/Table/TableStyle.php', 'TablePress\PhpOffice\PhpSpreadsheet\Worksheet\Table\Column' => $strauss_src . '/PhpSpreadsheet/Worksheet/Table/Column.php', 'TablePress\PhpOffice\PhpSpreadsheet\Worksheet\RowDimension' => $strauss_src . '/PhpSpreadsheet/Worksheet/RowDimension.php', 'TablePress\PhpOffice\PhpSpreadsheet\Worksheet\Validations' => $strauss_src . '/PhpSpreadsheet/Worksheet/Validations.php', 'TablePress\PhpOffice\PhpSpreadsheet\Worksheet\Protection' => $strauss_src . '/PhpSpreadsheet/Worksheet/Protection.php', 'TablePress\PhpOffice\PhpSpreadsheet\Worksheet\CellIterator' => $strauss_src . '/PhpSpreadsheet/Worksheet/CellIterator.php', 'TablePress\PhpOffice\PhpSpreadsheet\Worksheet\Column' => $strauss_src . '/PhpSpreadsheet/Worksheet/Column.php', 'TablePress\PhpOffice\PhpSpreadsheet\Worksheet\BaseDrawing' => $strauss_src . '/PhpSpreadsheet/Worksheet/BaseDrawing.php', 'TablePress\PhpOffice\PhpSpreadsheet\Worksheet\Table' => $strauss_src . '/PhpSpreadsheet/Worksheet/Table.php', 'TablePress\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet' => $strauss_src . '/PhpSpreadsheet/Worksheet/Worksheet.php', 'TablePress\PhpOffice\PhpSpreadsheet\Worksheet\ColumnDimension' => $strauss_src . '/PhpSpreadsheet/Worksheet/ColumnDimension.php', 'TablePress\PhpOffice\PhpSpreadsheet\Worksheet\Iterator' => $strauss_src . '/PhpSpreadsheet/Worksheet/Iterator.php', 'TablePress\PhpOffice\PhpSpreadsheet\Worksheet\PageSetup' => $strauss_src . '/PhpSpreadsheet/Worksheet/PageSetup.php', 'TablePress\PhpOffice\PhpSpreadsheet\Chart\Renderer\MtJpGraphRenderer' => $strauss_src . '/PhpSpreadsheet/Chart/Renderer/MtJpGraphRenderer.php', 'TablePress\PhpOffice\PhpSpreadsheet\Chart\Renderer\JpGraph' => $strauss_src . '/PhpSpreadsheet/Chart/Renderer/JpGraph.php', 'TablePress\PhpOffice\PhpSpreadsheet\Chart\Renderer\JpGraphRendererBase' => $strauss_src . '/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php', 'TablePress\PhpOffice\PhpSpreadsheet\Chart\Renderer\IRenderer' => $strauss_src . '/PhpSpreadsheet/Chart/Renderer/IRenderer.php', 'TablePress\PhpOffice\PhpSpreadsheet\Chart\DataSeries' => $strauss_src . '/PhpSpreadsheet/Chart/DataSeries.php', 'TablePress\PhpOffice\PhpSpreadsheet\Chart\Title' => $strauss_src . '/PhpSpreadsheet/Chart/Title.php', 'TablePress\PhpOffice\PhpSpreadsheet\Chart\GridLines' => $strauss_src . '/PhpSpreadsheet/Chart/GridLines.php', 'TablePress\PhpOffice\PhpSpreadsheet\Chart\Legend' => $strauss_src . '/PhpSpreadsheet/Chart/Legend.php', 'TablePress\PhpOffice\PhpSpreadsheet\Chart\Properties' => $strauss_src . '/PhpSpreadsheet/Chart/Properties.php', 'TablePress\PhpOffice\PhpSpreadsheet\Chart\AxisText' => $strauss_src . '/PhpSpreadsheet/Chart/AxisText.php', 'TablePress\PhpOffice\PhpSpreadsheet\Chart\Chart' => $strauss_src . '/PhpSpreadsheet/Chart/Chart.php', 'TablePress\PhpOffice\PhpSpreadsheet\Chart\PlotArea' => $strauss_src . '/PhpSpreadsheet/Chart/PlotArea.php', 'TablePress\PhpOffice\PhpSpreadsheet\Chart\Layout' => $strauss_src . '/PhpSpreadsheet/Chart/Layout.php', 'TablePress\PhpOffice\PhpSpreadsheet\Chart\TrendLine' => $strauss_src . '/PhpSpreadsheet/Chart/TrendLine.php', 'TablePress\PhpOffice\PhpSpreadsheet\Chart\Exception' => $strauss_src . '/PhpSpreadsheet/Chart/Exception.php', 'TablePress\PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues' => $strauss_src . '/PhpSpreadsheet/Chart/DataSeriesValues.php', 'TablePress\PhpOffice\PhpSpreadsheet\Chart\Axis' => $strauss_src . '/PhpSpreadsheet/Chart/Axis.php', 'TablePress\PhpOffice\PhpSpreadsheet\Chart\ChartColor' => $strauss_src . '/PhpSpreadsheet/Chart/ChartColor.php', 'TablePress\PhpOffice\PhpSpreadsheet\Theme' => $strauss_src . '/PhpSpreadsheet/Theme.php', 'TablePress\PhpOffice\PhpSpreadsheet\HashTable' => $strauss_src . '/PhpSpreadsheet/HashTable.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\Border' => $strauss_src . '/PhpSpreadsheet/Style/Border.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\Alignment' => $strauss_src . '/PhpSpreadsheet/Style/Alignment.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard' => $strauss_src . '/PhpSpreadsheet/Style/ConditionalFormatting/Wizard.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalDataBar' => $strauss_src . '/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBar.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard\Expression' => $strauss_src . '/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Expression.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard\WizardAbstract' => $strauss_src . '/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardAbstract.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard\Errors' => $strauss_src . '/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Errors.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard\CellValue' => $strauss_src . '/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/CellValue.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard\Blanks' => $strauss_src . '/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Blanks.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard\TextValue' => $strauss_src . '/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/TextValue.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard\WizardInterface' => $strauss_src . '/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardInterface.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard\DateValue' => $strauss_src . '/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/DateValue.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard\Duplicates' => $strauss_src . '/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Duplicates.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\StyleMerger' => $strauss_src . '/PhpSpreadsheet/Style/ConditionalFormatting/StyleMerger.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\CellStyleAssessor' => $strauss_src . '/PhpSpreadsheet/Style/ConditionalFormatting/CellStyleAssessor.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalDataBarExtension' => $strauss_src . '/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBarExtension.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\CellMatcher' => $strauss_src . '/PhpSpreadsheet/Style/ConditionalFormatting/CellMatcher.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalFormatValueObject' => $strauss_src . '/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalFormattingRuleExtension' => $strauss_src . '/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalColorScale' => $strauss_src . '/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalColorScale.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\Supervisor' => $strauss_src . '/PhpSpreadsheet/Style/Supervisor.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\RgbTint' => $strauss_src . '/PhpSpreadsheet/Style/RgbTint.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\Font' => $strauss_src . '/PhpSpreadsheet/Style/Font.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\Fill' => $strauss_src . '/PhpSpreadsheet/Style/Fill.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\Color' => $strauss_src . '/PhpSpreadsheet/Style/Color.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat\PercentageFormatter' => $strauss_src . '/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard\DateTime' => $strauss_src . '/PhpSpreadsheet/Style/NumberFormat/Wizard/DateTime.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard\Time' => $strauss_src . '/PhpSpreadsheet/Style/NumberFormat/Wizard/Time.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard\CurrencyNegative' => $strauss_src . '/PhpSpreadsheet/Style/NumberFormat/Wizard/CurrencyNegative.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard\Number' => $strauss_src . '/PhpSpreadsheet/Style/NumberFormat/Wizard/Number.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard\Percentage' => $strauss_src . '/PhpSpreadsheet/Style/NumberFormat/Wizard/Percentage.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard\Wizard' => $strauss_src . '/PhpSpreadsheet/Style/NumberFormat/Wizard/Wizard.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard\Accounting' => $strauss_src . '/PhpSpreadsheet/Style/NumberFormat/Wizard/Accounting.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard\CurrencyBase' => $strauss_src . '/PhpSpreadsheet/Style/NumberFormat/Wizard/CurrencyBase.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard\Scientific' => $strauss_src . '/PhpSpreadsheet/Style/NumberFormat/Wizard/Scientific.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard\Date' => $strauss_src . '/PhpSpreadsheet/Style/NumberFormat/Wizard/Date.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard\NumberBase' => $strauss_src . '/PhpSpreadsheet/Style/NumberFormat/Wizard/NumberBase.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard\DateTimeWizard' => $strauss_src . '/PhpSpreadsheet/Style/NumberFormat/Wizard/DateTimeWizard.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard\Currency' => $strauss_src . '/PhpSpreadsheet/Style/NumberFormat/Wizard/Currency.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard\Locale' => $strauss_src . '/PhpSpreadsheet/Style/NumberFormat/Wizard/Locale.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard\Duration' => $strauss_src . '/PhpSpreadsheet/Style/NumberFormat/Wizard/Duration.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat\Formatter' => $strauss_src . '/PhpSpreadsheet/Style/NumberFormat/Formatter.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat\FractionFormatter' => $strauss_src . '/PhpSpreadsheet/Style/NumberFormat/FractionFormatter.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat\DateFormatter' => $strauss_src . '/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat\BaseFormatter' => $strauss_src . '/PhpSpreadsheet/Style/NumberFormat/BaseFormatter.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat\NumberFormatter' => $strauss_src . '/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat' => $strauss_src . '/PhpSpreadsheet/Style/NumberFormat.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\Protection' => $strauss_src . '/PhpSpreadsheet/Style/Protection.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\Borders' => $strauss_src . '/PhpSpreadsheet/Style/Borders.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\Style' => $strauss_src . '/PhpSpreadsheet/Style/Style.php', 'TablePress\PhpOffice\PhpSpreadsheet\Style\Conditional' => $strauss_src . '/PhpSpreadsheet/Style/Conditional.php', 'TablePress\PhpOffice\PhpSpreadsheet\NamedFormula' => $strauss_src . '/PhpSpreadsheet/NamedFormula.php', 'TablePress\PhpOffice\PhpSpreadsheet\Shared\XMLWriter' => $strauss_src . '/PhpSpreadsheet/Shared/XMLWriter.php', 'TablePress\PhpOffice\PhpSpreadsheet\Shared\Trend\PolynomialBestFit' => $strauss_src . '/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php', 'TablePress\PhpOffice\PhpSpreadsheet\Shared\Trend\BestFit' => $strauss_src . '/PhpSpreadsheet/Shared/Trend/BestFit.php', 'TablePress\PhpOffice\PhpSpreadsheet\Shared\Trend\PowerBestFit' => $strauss_src . '/PhpSpreadsheet/Shared/Trend/PowerBestFit.php', 'TablePress\PhpOffice\PhpSpreadsheet\Shared\Trend\LogarithmicBestFit' => $strauss_src . '/PhpSpreadsheet/Shared/Trend/LogarithmicBestFit.php', 'TablePress\PhpOffice\PhpSpreadsheet\Shared\Trend\LinearBestFit' => $strauss_src . '/PhpSpreadsheet/Shared/Trend/LinearBestFit.php', 'TablePress\PhpOffice\PhpSpreadsheet\Shared\Trend\Trend' => $strauss_src . '/PhpSpreadsheet/Shared/Trend/Trend.php', 'TablePress\PhpOffice\PhpSpreadsheet\Shared\Trend\ExponentialBestFit' => $strauss_src . '/PhpSpreadsheet/Shared/Trend/ExponentialBestFit.php', 'TablePress\PhpOffice\PhpSpreadsheet\Shared\File' => $strauss_src . '/PhpSpreadsheet/Shared/File.php', 'TablePress\PhpOffice\PhpSpreadsheet\Shared\PasswordHasher' => $strauss_src . '/PhpSpreadsheet/Shared/PasswordHasher.php', 'TablePress\PhpOffice\PhpSpreadsheet\Shared\Drawing' => $strauss_src . '/PhpSpreadsheet/Shared/Drawing.php', 'TablePress\PhpOffice\PhpSpreadsheet\Shared\OLE' => $strauss_src . '/PhpSpreadsheet/Shared/OLE.php', 'TablePress\PhpOffice\PhpSpreadsheet\Shared\OLERead' => $strauss_src . '/PhpSpreadsheet/Shared/OLERead.php', 'TablePress\PhpOffice\PhpSpreadsheet\Shared\IntOrFloat' => $strauss_src . '/PhpSpreadsheet/Shared/IntOrFloat.php', 'TablePress\PhpOffice\PhpSpreadsheet\Shared\OLE\PPS\File' => $strauss_src . '/PhpSpreadsheet/Shared/OLE/PPS/File.php', 'TablePress\PhpOffice\PhpSpreadsheet\Shared\OLE\PPS\Root' => $strauss_src . '/PhpSpreadsheet/Shared/OLE/PPS/Root.php', 'TablePress\PhpOffice\PhpSpreadsheet\Shared\OLE\ChainedBlockStream' => $strauss_src . '/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php', 'TablePress\PhpOffice\PhpSpreadsheet\Shared\OLE\PPS' => $strauss_src . '/PhpSpreadsheet/Shared/OLE/PPS.php', 'TablePress\PhpOffice\PhpSpreadsheet\Shared\Escher' => $strauss_src . '/PhpSpreadsheet/Shared/Escher.php', 'TablePress\PhpOffice\PhpSpreadsheet\Shared\Font' => $strauss_src . '/PhpSpreadsheet/Shared/Font.php', 'TablePress\PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer' => $strauss_src . '/PhpSpreadsheet/Shared/Escher/DgContainer.php', 'TablePress\PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer' => $strauss_src . '/PhpSpreadsheet/Shared/Escher/DggContainer.php', 'TablePress\PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer\SpContainer' => $strauss_src . '/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer/SpContainer.php', 'TablePress\PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer' => $strauss_src . '/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer.php', 'TablePress\PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer' => $strauss_src . '/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer.php', 'TablePress\PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE\Blip' => $strauss_src . '/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE/Blip.php', 'TablePress\PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE' => $strauss_src . '/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE.php', 'TablePress\PhpOffice\PhpSpreadsheet\Shared\Xls' => $strauss_src . '/PhpSpreadsheet/Shared/Xls.php', 'TablePress\PhpOffice\PhpSpreadsheet\Shared\Date' => $strauss_src . '/PhpSpreadsheet/Shared/Date.php', 'TablePress\PhpOffice\PhpSpreadsheet\Shared\TimeZone' => $strauss_src . '/PhpSpreadsheet/Shared/TimeZone.php', 'TablePress\PhpOffice\PhpSpreadsheet\Shared\CodePage' => $strauss_src . '/PhpSpreadsheet/Shared/CodePage.php', 'TablePress\PhpOffice\PhpSpreadsheet\Shared\StringHelper' => $strauss_src . '/PhpSpreadsheet/Shared/StringHelper.php', 'TablePress\PhpOffice\PhpSpreadsheet\Document\Properties' => $strauss_src . '/PhpSpreadsheet/Document/Properties.php', 'TablePress\PhpOffice\PhpSpreadsheet\Document\Security' => $strauss_src . '/PhpSpreadsheet/Document/Security.php', 'TablePress\PhpOffice\PhpSpreadsheet\IOFactory' => $strauss_src . '/PhpSpreadsheet/IOFactory.php', 'TablePress\PhpOffice\PhpSpreadsheet\Cell\AddressHelper' => $strauss_src . '/PhpSpreadsheet/Cell/AddressHelper.php', 'TablePress\PhpOffice\PhpSpreadsheet\Cell\ColumnRange' => $strauss_src . '/PhpSpreadsheet/Cell/ColumnRange.php', 'TablePress\PhpOffice\PhpSpreadsheet\Cell\DataValidator' => $strauss_src . '/PhpSpreadsheet/Cell/DataValidator.php', 'TablePress\PhpOffice\PhpSpreadsheet\Cell\DefaultValueBinder' => $strauss_src . '/PhpSpreadsheet/Cell/DefaultValueBinder.php', 'TablePress\PhpOffice\PhpSpreadsheet\Cell\IgnoredErrors' => $strauss_src . '/PhpSpreadsheet/Cell/IgnoredErrors.php', 'TablePress\PhpOffice\PhpSpreadsheet\Cell\StringValueBinder' => $strauss_src . '/PhpSpreadsheet/Cell/StringValueBinder.php', 'TablePress\PhpOffice\PhpSpreadsheet\Cell\CellAddress' => $strauss_src . '/PhpSpreadsheet/Cell/CellAddress.php', 'TablePress\PhpOffice\PhpSpreadsheet\Cell\IValueBinder' => $strauss_src . '/PhpSpreadsheet/Cell/IValueBinder.php', 'TablePress\PhpOffice\PhpSpreadsheet\Cell\Cell' => $strauss_src . '/PhpSpreadsheet/Cell/Cell.php', 'TablePress\PhpOffice\PhpSpreadsheet\Cell\RowRange' => $strauss_src . '/PhpSpreadsheet/Cell/RowRange.php', 'TablePress\PhpOffice\PhpSpreadsheet\Cell\AdvancedValueBinder' => $strauss_src . '/PhpSpreadsheet/Cell/AdvancedValueBinder.php', 'TablePress\PhpOffice\PhpSpreadsheet\Cell\Hyperlink' => $strauss_src . '/PhpSpreadsheet/Cell/Hyperlink.php', 'TablePress\PhpOffice\PhpSpreadsheet\Cell\CellRange' => $strauss_src . '/PhpSpreadsheet/Cell/CellRange.php', 'TablePress\PhpOffice\PhpSpreadsheet\Cell\Coordinate' => $strauss_src . '/PhpSpreadsheet/Cell/Coordinate.php', 'TablePress\PhpOffice\PhpSpreadsheet\Cell\DataType' => $strauss_src . '/PhpSpreadsheet/Cell/DataType.php', 'TablePress\PhpOffice\PhpSpreadsheet\Cell\DataValidation' => $strauss_src . '/PhpSpreadsheet/Cell/DataValidation.php', 'TablePress\PhpOffice\PhpSpreadsheet\Cell\AddressRange' => $strauss_src . '/PhpSpreadsheet/Cell/AddressRange.php', 'TablePress\PhpOffice\PhpSpreadsheet\CellReferenceHelper' => $strauss_src . '/PhpSpreadsheet/CellReferenceHelper.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Functions' => $strauss_src . '/PhpSpreadsheet/Calculation/Functions.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Token\Stack' => $strauss_src . '/PhpSpreadsheet/Calculation/Token/Stack.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Category' => $strauss_src . '/PhpSpreadsheet/Calculation/Category.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Database\DGet' => $strauss_src . '/PhpSpreadsheet/Calculation/Database/DGet.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Database\DatabaseAbstract' => $strauss_src . '/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Database\DStDev' => $strauss_src . '/PhpSpreadsheet/Calculation/Database/DStDev.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Database\DMin' => $strauss_src . '/PhpSpreadsheet/Calculation/Database/DMin.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Database\DStDevP' => $strauss_src . '/PhpSpreadsheet/Calculation/Database/DStDevP.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Database\DAverage' => $strauss_src . '/PhpSpreadsheet/Calculation/Database/DAverage.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Database\DProduct' => $strauss_src . '/PhpSpreadsheet/Calculation/Database/DProduct.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Database\DVar' => $strauss_src . '/PhpSpreadsheet/Calculation/Database/DVar.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Database\DVarP' => $strauss_src . '/PhpSpreadsheet/Calculation/Database/DVarP.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Database\DCountA' => $strauss_src . '/PhpSpreadsheet/Calculation/Database/DCountA.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Database\DMax' => $strauss_src . '/PhpSpreadsheet/Calculation/Database/DMax.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Database\DCount' => $strauss_src . '/PhpSpreadsheet/Calculation/Database/DCount.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Database\DSum' => $strauss_src . '/PhpSpreadsheet/Calculation/Database/DSum.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\FunctionArray' => $strauss_src . '/PhpSpreadsheet/Calculation/FunctionArray.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError' => $strauss_src . '/PhpSpreadsheet/Calculation/Information/ExcelError.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Information\Value' => $strauss_src . '/PhpSpreadsheet/Calculation/Information/Value.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Information\ErrorValue' => $strauss_src . '/PhpSpreadsheet/Calculation/Information/ErrorValue.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Calculation' => $strauss_src . '/PhpSpreadsheet/Calculation/Calculation.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\LookupRef\ExcelMatch' => $strauss_src . '/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\LookupRef\LookupBase' => $strauss_src . '/PhpSpreadsheet/Calculation/LookupRef/LookupBase.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Selection' => $strauss_src . '/PhpSpreadsheet/Calculation/LookupRef/Selection.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Lookup' => $strauss_src . '/PhpSpreadsheet/Calculation/LookupRef/Lookup.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Address' => $strauss_src . '/PhpSpreadsheet/Calculation/LookupRef/Address.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Unique' => $strauss_src . '/PhpSpreadsheet/Calculation/LookupRef/Unique.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\LookupRef\VLookup' => $strauss_src . '/PhpSpreadsheet/Calculation/LookupRef/VLookup.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Formula' => $strauss_src . '/PhpSpreadsheet/Calculation/LookupRef/Formula.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Matrix' => $strauss_src . '/PhpSpreadsheet/Calculation/LookupRef/Matrix.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\LookupRef\HLookup' => $strauss_src . '/PhpSpreadsheet/Calculation/LookupRef/HLookup.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Hyperlink' => $strauss_src . '/PhpSpreadsheet/Calculation/LookupRef/Hyperlink.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Sort' => $strauss_src . '/PhpSpreadsheet/Calculation/LookupRef/Sort.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Filter' => $strauss_src . '/PhpSpreadsheet/Calculation/LookupRef/Filter.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\LookupRef\LookupRefValidations' => $strauss_src . '/PhpSpreadsheet/Calculation/LookupRef/LookupRefValidations.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Offset' => $strauss_src . '/PhpSpreadsheet/Calculation/LookupRef/Offset.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Indirect' => $strauss_src . '/PhpSpreadsheet/Calculation/LookupRef/Indirect.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Helpers' => $strauss_src . '/PhpSpreadsheet/Calculation/LookupRef/Helpers.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\LookupRef\RowColumnInformation' => $strauss_src . '/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\LookupRef\ChooseRowsEtc' => $strauss_src . '/PhpSpreadsheet/Calculation/LookupRef/ChooseRowsEtc.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\BinaryComparison' => $strauss_src . '/PhpSpreadsheet/Calculation/BinaryComparison.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Financial\TreasuryBill' => $strauss_src . '/PhpSpreadsheet/Calculation/Financial/TreasuryBill.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities\Yields' => $strauss_src . '/PhpSpreadsheet/Calculation/Financial/Securities/Yields.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities\AccruedInterest' => $strauss_src . '/PhpSpreadsheet/Calculation/Financial/Securities/AccruedInterest.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities\Rates' => $strauss_src . '/PhpSpreadsheet/Calculation/Financial/Securities/Rates.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities\Price' => $strauss_src . '/PhpSpreadsheet/Calculation/Financial/Securities/Price.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities\SecurityValidations' => $strauss_src . '/PhpSpreadsheet/Calculation/Financial/Securities/SecurityValidations.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Financial\Depreciation' => $strauss_src . '/PhpSpreadsheet/Calculation/Financial/Depreciation.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Financial\FinancialValidations' => $strauss_src . '/PhpSpreadsheet/Calculation/Financial/FinancialValidations.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Financial\InterestRate' => $strauss_src . '/PhpSpreadsheet/Calculation/Financial/InterestRate.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Financial\Dollar' => $strauss_src . '/PhpSpreadsheet/Calculation/Financial/Dollar.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Variable\NonPeriodic' => $strauss_src . '/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Variable\Periodic' => $strauss_src . '/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/Periodic.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\CashFlowValidations' => $strauss_src . '/PhpSpreadsheet/Calculation/Financial/CashFlow/CashFlowValidations.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Constant\Periodic\Cumulative' => $strauss_src . '/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Cumulative.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Constant\Periodic\Interest' => $strauss_src . '/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Constant\Periodic\InterestAndPrincipal' => $strauss_src . '/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/InterestAndPrincipal.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Constant\Periodic\Payments' => $strauss_src . '/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Payments.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Constant\Periodic' => $strauss_src . '/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Single' => $strauss_src . '/PhpSpreadsheet/Calculation/Financial/CashFlow/Single.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Financial\Coupons' => $strauss_src . '/PhpSpreadsheet/Calculation/Financial/Coupons.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants' => $strauss_src . '/PhpSpreadsheet/Calculation/Financial/Constants.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Financial\Amortization' => $strauss_src . '/PhpSpreadsheet/Calculation/Financial/Amortization.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Financial\Helpers' => $strauss_src . '/PhpSpreadsheet/Calculation/Financial/Helpers.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Web\Service' => $strauss_src . '/PhpSpreadsheet/Calculation/Web/Service.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\FormulaParser' => $strauss_src . '/PhpSpreadsheet/Calculation/FormulaParser.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Internal\WildcardMatch' => $strauss_src . '/PhpSpreadsheet/Calculation/Internal/WildcardMatch.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Internal\MakeMatrix' => $strauss_src . '/PhpSpreadsheet/Calculation/Internal/MakeMatrix.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Internal\ExcelArrayPseudoFunctions' => $strauss_src . '/PhpSpreadsheet/Calculation/Internal/ExcelArrayPseudoFunctions.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\TextData\Extract' => $strauss_src . '/PhpSpreadsheet/Calculation/TextData/Extract.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\TextData\CharacterConvert' => $strauss_src . '/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\TextData\Trim' => $strauss_src . '/PhpSpreadsheet/Calculation/TextData/Trim.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\TextData\Search' => $strauss_src . '/PhpSpreadsheet/Calculation/TextData/Search.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\TextData\Concatenate' => $strauss_src . '/PhpSpreadsheet/Calculation/TextData/Concatenate.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\TextData\CaseConvert' => $strauss_src . '/PhpSpreadsheet/Calculation/TextData/CaseConvert.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\TextData\Replace' => $strauss_src . '/PhpSpreadsheet/Calculation/TextData/Replace.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\TextData\Helpers' => $strauss_src . '/PhpSpreadsheet/Calculation/TextData/Helpers.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\TextData\Text' => $strauss_src . '/PhpSpreadsheet/Calculation/TextData/Text.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\TextData\Format' => $strauss_src . '/PhpSpreadsheet/Calculation/TextData/Format.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\CalculationBase' => $strauss_src . '/PhpSpreadsheet/Calculation/CalculationBase.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\Week' => $strauss_src . '/PhpSpreadsheet/Calculation/DateTimeExcel/Week.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\Difference' => $strauss_src . '/PhpSpreadsheet/Calculation/DateTimeExcel/Difference.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\TimeParts' => $strauss_src . '/PhpSpreadsheet/Calculation/DateTimeExcel/TimeParts.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\Month' => $strauss_src . '/PhpSpreadsheet/Calculation/DateTimeExcel/Month.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\Time' => $strauss_src . '/PhpSpreadsheet/Calculation/DateTimeExcel/Time.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\Days360' => $strauss_src . '/PhpSpreadsheet/Calculation/DateTimeExcel/Days360.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\DateParts' => $strauss_src . '/PhpSpreadsheet/Calculation/DateTimeExcel/DateParts.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\NetworkDays' => $strauss_src . '/PhpSpreadsheet/Calculation/DateTimeExcel/NetworkDays.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\YearFrac' => $strauss_src . '/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\Constants' => $strauss_src . '/PhpSpreadsheet/Calculation/DateTimeExcel/Constants.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\Date' => $strauss_src . '/PhpSpreadsheet/Calculation/DateTimeExcel/Date.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\WorkDay' => $strauss_src . '/PhpSpreadsheet/Calculation/DateTimeExcel/WorkDay.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\Days' => $strauss_src . '/PhpSpreadsheet/Calculation/DateTimeExcel/Days.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\DateValue' => $strauss_src . '/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\Current' => $strauss_src . '/PhpSpreadsheet/Calculation/DateTimeExcel/Current.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\Helpers' => $strauss_src . '/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\TimeValue' => $strauss_src . '/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled' => $strauss_src . '/PhpSpreadsheet/Calculation/ArrayEnabled.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\ExceptionHandler' => $strauss_src . '/PhpSpreadsheet/Calculation/ExceptionHandler.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Logical\Boolean' => $strauss_src . '/PhpSpreadsheet/Calculation/Logical/Boolean.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Logical\Operations' => $strauss_src . '/PhpSpreadsheet/Calculation/Logical/Operations.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Logical\Conditional' => $strauss_src . '/PhpSpreadsheet/Calculation/Logical/Conditional.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Lcm' => $strauss_src . '/PhpSpreadsheet/Calculation/MathTrig/Lcm.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Trig\Sine' => $strauss_src . '/PhpSpreadsheet/Calculation/MathTrig/Trig/Sine.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Trig\Cosine' => $strauss_src . '/PhpSpreadsheet/Calculation/MathTrig/Trig/Cosine.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Trig\Secant' => $strauss_src . '/PhpSpreadsheet/Calculation/MathTrig/Trig/Secant.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Trig\Cosecant' => $strauss_src . '/PhpSpreadsheet/Calculation/MathTrig/Trig/Cosecant.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Trig\Tangent' => $strauss_src . '/PhpSpreadsheet/Calculation/MathTrig/Trig/Tangent.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Trig\Cotangent' => $strauss_src . '/PhpSpreadsheet/Calculation/MathTrig/Trig/Cotangent.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Logarithms' => $strauss_src . '/PhpSpreadsheet/Calculation/MathTrig/Logarithms.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Angle' => $strauss_src . '/PhpSpreadsheet/Calculation/MathTrig/Angle.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\MathTrig\MatrixFunctions' => $strauss_src . '/PhpSpreadsheet/Calculation/MathTrig/MatrixFunctions.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Ceiling' => $strauss_src . '/PhpSpreadsheet/Calculation/MathTrig/Ceiling.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Trunc' => $strauss_src . '/PhpSpreadsheet/Calculation/MathTrig/Trunc.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Roman' => $strauss_src . '/PhpSpreadsheet/Calculation/MathTrig/Roman.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Round' => $strauss_src . '/PhpSpreadsheet/Calculation/MathTrig/Round.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Arabic' => $strauss_src . '/PhpSpreadsheet/Calculation/MathTrig/Arabic.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Gcd' => $strauss_src . '/PhpSpreadsheet/Calculation/MathTrig/Gcd.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Sign' => $strauss_src . '/PhpSpreadsheet/Calculation/MathTrig/Sign.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Exp' => $strauss_src . '/PhpSpreadsheet/Calculation/MathTrig/Exp.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Random' => $strauss_src . '/PhpSpreadsheet/Calculation/MathTrig/Random.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\MathTrig\IntClass' => $strauss_src . '/PhpSpreadsheet/Calculation/MathTrig/IntClass.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Base' => $strauss_src . '/PhpSpreadsheet/Calculation/MathTrig/Base.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\MathTrig\SeriesSum' => $strauss_src . '/PhpSpreadsheet/Calculation/MathTrig/SeriesSum.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Operations' => $strauss_src . '/PhpSpreadsheet/Calculation/MathTrig/Operations.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Absolute' => $strauss_src . '/PhpSpreadsheet/Calculation/MathTrig/Absolute.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Floor' => $strauss_src . '/PhpSpreadsheet/Calculation/MathTrig/Floor.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Sqrt' => $strauss_src . '/PhpSpreadsheet/Calculation/MathTrig/Sqrt.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Sum' => $strauss_src . '/PhpSpreadsheet/Calculation/MathTrig/Sum.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Subtotal' => $strauss_src . '/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Factorial' => $strauss_src . '/PhpSpreadsheet/Calculation/MathTrig/Factorial.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Combinations' => $strauss_src . '/PhpSpreadsheet/Calculation/MathTrig/Combinations.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Helpers' => $strauss_src . '/PhpSpreadsheet/Calculation/MathTrig/Helpers.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\MathTrig\SumSquares' => $strauss_src . '/PhpSpreadsheet/Calculation/MathTrig/SumSquares.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\Averages\Mean' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/Averages/Mean.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\Size' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/Size.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\Maximum' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/Maximum.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\StatisticalValidations' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/StatisticalValidations.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\Deviations' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/Deviations.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\VarianceBase' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/VarianceBase.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\Confidence' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/Confidence.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\MaxMinBase' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/MaxMinBase.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\Counts' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/Counts.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\StandardDeviations' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/StandardDeviations.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\Percentiles' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/Percentiles.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\Range' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/Range.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\Minimum' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/Minimum.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\Averages' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/Averages.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\Permutations' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/Permutations.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\Variances' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/Variances.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\AggregateBase' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/AggregateBase.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\Exponential' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/Distributions/Exponential.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\StudentT' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/Distributions/StudentT.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\ChiSquared' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\StandardNormal' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/Distributions/StandardNormal.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\Fisher' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/Distributions/Fisher.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\NewtonRaphson' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/Distributions/NewtonRaphson.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\LogNormal' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/Distributions/LogNormal.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\Weibull' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/Distributions/Weibull.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\HyperGeometric' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/Distributions/HyperGeometric.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\GammaBase' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\Poisson' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/Distributions/Poisson.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\DistributionValidations' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/Distributions/DistributionValidations.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\Normal' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/Distributions/Normal.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\Beta' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/Distributions/Beta.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\Binomial' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/Distributions/Binomial.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\F' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/Distributions/F.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\Gamma' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/Distributions/Gamma.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\Trends' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/Trends.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\Conditional' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/Conditional.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\Standardize' => $strauss_src . '/PhpSpreadsheet/Calculation/Statistical/Standardize.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Exception' => $strauss_src . '/PhpSpreadsheet/Calculation/Exception.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Engine\FormattedNumber' => $strauss_src . '/PhpSpreadsheet/Calculation/Engine/FormattedNumber.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Engine\Logger' => $strauss_src . '/PhpSpreadsheet/Calculation/Engine/Logger.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Engine\ArrayArgumentProcessor' => $strauss_src . '/PhpSpreadsheet/Calculation/Engine/ArrayArgumentProcessor.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Engine\ArrayArgumentHelper' => $strauss_src . '/PhpSpreadsheet/Calculation/Engine/ArrayArgumentHelper.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Engine\BranchPruner' => $strauss_src . '/PhpSpreadsheet/Calculation/Engine/BranchPruner.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Engine\CyclicReferenceStack' => $strauss_src . '/PhpSpreadsheet/Calculation/Engine/CyclicReferenceStack.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Engine\Operands\StructuredReference' => $strauss_src . '/PhpSpreadsheet/Calculation/Engine/Operands/StructuredReference.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Engine\Operands\Operand' => $strauss_src . '/PhpSpreadsheet/Calculation/Engine/Operands/Operand.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Engineering\BitWise' => $strauss_src . '/PhpSpreadsheet/Calculation/Engineering/BitWise.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Engineering\ConvertOctal' => $strauss_src . '/PhpSpreadsheet/Calculation/Engineering/ConvertOctal.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Engineering\EngineeringValidations' => $strauss_src . '/PhpSpreadsheet/Calculation/Engineering/EngineeringValidations.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Engineering\Complex' => $strauss_src . '/PhpSpreadsheet/Calculation/Engineering/Complex.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Engineering\ErfC' => $strauss_src . '/PhpSpreadsheet/Calculation/Engineering/ErfC.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Engineering\Constants' => $strauss_src . '/PhpSpreadsheet/Calculation/Engineering/Constants.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Engineering\ComplexFunctions' => $strauss_src . '/PhpSpreadsheet/Calculation/Engineering/ComplexFunctions.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Engineering\ConvertDecimal' => $strauss_src . '/PhpSpreadsheet/Calculation/Engineering/ConvertDecimal.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Engineering\ConvertUOM' => $strauss_src . '/PhpSpreadsheet/Calculation/Engineering/ConvertUOM.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Engineering\BesselI' => $strauss_src . '/PhpSpreadsheet/Calculation/Engineering/BesselI.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Engineering\ConvertHex' => $strauss_src . '/PhpSpreadsheet/Calculation/Engineering/ConvertHex.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Engineering\BesselJ' => $strauss_src . '/PhpSpreadsheet/Calculation/Engineering/BesselJ.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Engineering\ConvertBase' => $strauss_src . '/PhpSpreadsheet/Calculation/Engineering/ConvertBase.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Engineering\BesselK' => $strauss_src . '/PhpSpreadsheet/Calculation/Engineering/BesselK.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Engineering\Erf' => $strauss_src . '/PhpSpreadsheet/Calculation/Engineering/Erf.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Engineering\BesselY' => $strauss_src . '/PhpSpreadsheet/Calculation/Engineering/BesselY.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Engineering\Compare' => $strauss_src . '/PhpSpreadsheet/Calculation/Engineering/Compare.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Engineering\ComplexOperations' => $strauss_src . '/PhpSpreadsheet/Calculation/Engineering/ComplexOperations.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\Engineering\ConvertBinary' => $strauss_src . '/PhpSpreadsheet/Calculation/Engineering/ConvertBinary.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\CalculationLocale' => $strauss_src . '/PhpSpreadsheet/Calculation/CalculationLocale.php', 'TablePress\PhpOffice\PhpSpreadsheet\Calculation\FormulaToken' => $strauss_src . '/PhpSpreadsheet/Calculation/FormulaToken.php', 'TablePress\PhpOffice\PhpSpreadsheet\Comment' => $strauss_src . '/PhpSpreadsheet/Comment.php', 'TablePress\PhpOffice\PhpSpreadsheet\DefinedName' => $strauss_src . '/PhpSpreadsheet/DefinedName.php', 'TablePress\PhpOffice\PhpSpreadsheet\RichText\RichText' => $strauss_src . '/PhpSpreadsheet/RichText/RichText.php', 'TablePress\PhpOffice\PhpSpreadsheet\RichText\Run' => $strauss_src . '/PhpSpreadsheet/RichText/Run.php', 'TablePress\PhpOffice\PhpSpreadsheet\RichText\ITextElement' => $strauss_src . '/PhpSpreadsheet/RichText/ITextElement.php', 'TablePress\PhpOffice\PhpSpreadsheet\RichText\TextElement' => $strauss_src . '/PhpSpreadsheet/RichText/TextElement.php', 'TablePress\PhpOffice\PhpSpreadsheet\Exception' => $strauss_src . '/PhpSpreadsheet/Exception.php', 'TablePress\PhpOffice\PhpSpreadsheet\Helper\Size' => $strauss_src . '/PhpSpreadsheet/Helper/Size.php', 'TablePress\PhpOffice\PhpSpreadsheet\Helper\Handler' => $strauss_src . '/PhpSpreadsheet/Helper/Handler.php', 'TablePress\PhpOffice\PhpSpreadsheet\Helper\Dimension' => $strauss_src . '/PhpSpreadsheet/Helper/Dimension.php', 'TablePress\PhpOffice\PhpSpreadsheet\Helper\Downloader' => $strauss_src . '/PhpSpreadsheet/Helper/Downloader.php', 'TablePress\PhpOffice\PhpSpreadsheet\Helper\Html' => $strauss_src . '/PhpSpreadsheet/Helper/Html.php', 'TablePress\PhpOffice\PhpSpreadsheet\Helper\Sample' => $strauss_src . '/PhpSpreadsheet/Helper/Sample.php', 'TablePress\PhpOffice\PhpSpreadsheet\Helper\TextGrid' => $strauss_src . '/PhpSpreadsheet/Helper/TextGrid.php', 'TablePress\PhpOffice\PhpSpreadsheet\ReferenceHelper' => $strauss_src . '/PhpSpreadsheet/ReferenceHelper.php', 'TablePress\Complex\Functions' => $strauss_src . '/markbaker/complex/classes/Functions.php', 'TablePress\Complex\Complex' => $strauss_src . '/markbaker/complex/classes/Complex.php', 'TablePress\Complex\Operations' => $strauss_src . '/markbaker/complex/classes/Operations.php', 'TablePress\Complex\Exception' => $strauss_src . '/markbaker/complex/classes/Exception.php', 'TablePress\Matrix\Functions' => $strauss_src . '/markbaker/matrix/classes/Functions.php', 'TablePress\Matrix\Operators\Multiplication' => $strauss_src . '/markbaker/matrix/classes/Operators/Multiplication.php', 'TablePress\Matrix\Operators\Division' => $strauss_src . '/markbaker/matrix/classes/Operators/Division.php', 'TablePress\Matrix\Operators\Operator' => $strauss_src . '/markbaker/matrix/classes/Operators/Operator.php', 'TablePress\Matrix\Operators\Subtraction' => $strauss_src . '/markbaker/matrix/classes/Operators/Subtraction.php', 'TablePress\Matrix\Operators\DirectSum' => $strauss_src . '/markbaker/matrix/classes/Operators/DirectSum.php', 'TablePress\Matrix\Operators\Addition' => $strauss_src . '/markbaker/matrix/classes/Operators/Addition.php', 'TablePress\Matrix\Builder' => $strauss_src . '/markbaker/matrix/classes/Builder.php', 'TablePress\Matrix\Div0Exception' => $strauss_src . '/markbaker/matrix/classes/Div0Exception.php', 'TablePress\Matrix\Matrix' => $strauss_src . '/markbaker/matrix/classes/Matrix.php', 'TablePress\Matrix\Operations' => $strauss_src . '/markbaker/matrix/classes/Operations.php', 'TablePress\Matrix\Exception' => $strauss_src . '/markbaker/matrix/classes/Exception.php', 'TablePress\Matrix\Decomposition\LU' => $strauss_src . '/markbaker/matrix/classes/Decomposition/LU.php', 'TablePress\Matrix\Decomposition\Decomposition' => $strauss_src . '/markbaker/matrix/classes/Decomposition/Decomposition.php', 'TablePress\Matrix\Decomposition\QR' => $strauss_src . '/markbaker/matrix/classes/Decomposition/QR.php', 'TablePress\Composer\Pcre\PHPStan\PregMatchTypeSpecifyingExtension' => $strauss_src . '/composer/pcre/PHPStan/PregMatchTypeSpecifyingExtension.php', 'TablePress\Composer\Pcre\PHPStan\PregReplaceCallbackClosureTypeExtension' => $strauss_src . '/composer/pcre/PHPStan/PregReplaceCallbackClosureTypeExtension.php', 'TablePress\Composer\Pcre\PHPStan\PregMatchParameterOutTypeExtension' => $strauss_src . '/composer/pcre/PHPStan/PregMatchParameterOutTypeExtension.php', 'TablePress\Composer\Pcre\PHPStan\InvalidRegexPatternRule' => $strauss_src . '/composer/pcre/PHPStan/InvalidRegexPatternRule.php', 'TablePress\Composer\Pcre\PHPStan\PregMatchFlags' => $strauss_src . '/composer/pcre/PHPStan/PregMatchFlags.php', 'TablePress\Composer\Pcre\PHPStan\UnsafeStrictGroupsCallRule' => $strauss_src . '/composer/pcre/PHPStan/UnsafeStrictGroupsCallRule.php', 'TablePress\Composer\Pcre\MatchResult' => $strauss_src . '/composer/pcre/MatchResult.php', 'TablePress\Composer\Pcre\Regex' => $strauss_src . '/composer/pcre/Regex.php', 'TablePress\Composer\Pcre\MatchAllWithOffsetsResult' => $strauss_src . '/composer/pcre/MatchAllWithOffsetsResult.php', 'TablePress\Composer\Pcre\MatchWithOffsetsResult' => $strauss_src . '/composer/pcre/MatchWithOffsetsResult.php', 'TablePress\Composer\Pcre\MatchAllStrictGroupsResult' => $strauss_src . '/composer/pcre/MatchAllStrictGroupsResult.php', 'TablePress\Composer\Pcre\UnexpectedNullMatchException' => $strauss_src . '/composer/pcre/UnexpectedNullMatchException.php', 'TablePress\Composer\Pcre\MatchStrictGroupsResult' => $strauss_src . '/composer/pcre/MatchStrictGroupsResult.php', 'TablePress\Composer\Pcre\MatchAllResult' => $strauss_src . '/composer/pcre/MatchAllResult.php', 'TablePress\Composer\Pcre\ReplaceResult' => $strauss_src . '/composer/pcre/ReplaceResult.php', 'TablePress\Composer\Pcre\Preg' => $strauss_src . '/composer/pcre/Preg.php', 'TablePress\Composer\Pcre\PcreException' => $strauss_src . '/composer/pcre/PcreException.php', 'TablePress\Psr\Http\Message\ServerRequestInterface' => $strauss_src . '/psr/http-message/ServerRequestInterface.php', 'TablePress\Psr\Http\Message\UriInterface' => $strauss_src . '/psr/http-message/UriInterface.php', 'TablePress\Psr\Http\Message\StreamInterface' => $strauss_src . '/psr/http-message/StreamInterface.php', 'TablePress\Psr\Http\Message\UploadedFileInterface' => $strauss_src . '/psr/http-message/UploadedFileInterface.php', 'TablePress\Psr\Http\Message\RequestInterface' => $strauss_src . '/psr/http-message/RequestInterface.php', 'TablePress\Psr\Http\Message\ResponseInterface' => $strauss_src . '/psr/http-message/ResponseInterface.php', 'TablePress\Psr\Http\Message\MessageInterface' => $strauss_src . '/psr/http-message/MessageInterface.php', 'TablePress\Psr\Http\Client\NetworkExceptionInterface' => $strauss_src . '/psr/http-client/NetworkExceptionInterface.php', 'TablePress\Psr\Http\Client\RequestExceptionInterface' => $strauss_src . '/psr/http-client/RequestExceptionInterface.php', 'TablePress\Psr\Http\Client\ClientInterface' => $strauss_src . '/psr/http-client/ClientInterface.php', 'TablePress\Psr\Http\Client\ClientExceptionInterface' => $strauss_src . '/psr/http-client/ClientExceptionInterface.php', 'TablePress\Psr\SimpleCache\CacheInterface' => $strauss_src . '/psr/simple-cache/CacheInterface.php', 'TablePress\Psr\SimpleCache\InvalidArgumentException' => $strauss_src . '/psr/simple-cache/InvalidArgumentException.php', 'TablePress\Psr\SimpleCache\CacheException' => $strauss_src . '/psr/simple-cache/CacheException.php', 'TablePress\Psr\Http\Message\ResponseFactoryInterface' => $strauss_src . '/psr/http-factory/ResponseFactoryInterface.php', 'TablePress\Psr\Http\Message\StreamFactoryInterface' => $strauss_src . '/psr/http-factory/StreamFactoryInterface.php', 'TablePress\Psr\Http\Message\UploadedFileFactoryInterface' => $strauss_src . '/psr/http-factory/UploadedFileFactoryInterface.php', 'TablePress\Psr\Http\Message\UriFactoryInterface' => $strauss_src . '/psr/http-factory/UriFactoryInterface.php', 'TablePress\Psr\Http\Message\ServerRequestFactoryInterface' => $strauss_src . '/psr/http-factory/ServerRequestFactoryInterface.php', 'TablePress\Psr\Http\Message\RequestFactoryInterface' => $strauss_src . '/psr/http-factory/RequestFactoryInterface.php', );PK!+libraries/vendor/psr/simple-cache/index.phpnu[libraries/vendor/psr/simple-cache/InvalidArgumentException.phpnu[ $keys A list of keys that can be obtained in a single operation. * @param mixed $default Default value to return for keys that do not exist. * * @return iterable A list of key => value pairs. Cache keys that do not exist or are stale will have $default as value. * * @throws \TablePress\Psr\SimpleCache\InvalidArgumentException * MUST be thrown if $keys is neither an array nor a Traversable, * or if any of the $keys are not a legal value. */ public function getMultiple(iterable $keys, $default = null): iterable; /** * Persists a set of key => value pairs in the cache, with an optional TTL. * * @param iterable $values A list of key => value pairs for a multiple-set operation. * @param null|int|\DateInterval $ttl Optional. The TTL value of this item. If no value is sent and * the driver supports TTL then the library may set a default value * for it or let the driver take care of that. * * @return bool True on success and false on failure. * * @throws \TablePress\Psr\SimpleCache\InvalidArgumentException * MUST be thrown if $values is neither an array nor a Traversable, * or if any of the $values are not a legal value. */ public function setMultiple(iterable $values, $ttl = null): bool; /** * Deletes multiple cache items in a single operation. * * @param iterable $keys A list of string-based keys to be deleted. * * @return bool True if the items were successfully removed. False if there was an error. * * @throws \TablePress\Psr\SimpleCache\InvalidArgumentException * MUST be thrown if $keys is neither an array nor a Traversable, * or if any of the $keys are not a legal value. */ public function deleteMultiple(iterable $keys): bool; /** * Determines whether an item is present in the cache. * * NOTE: It is recommended that has() is only to be used for cache warming type purposes * and not to be used within your live applications operations for get/set, as this method * is subject to a race condition where your has() will return true and immediately after, * another script can remove it making the state of your app out of date. * * @param string $key The cache item key. * * @return bool * * @throws \TablePress\Psr\SimpleCache\InvalidArgumentException * MUST be thrown if the $key string is not a legal value. */ public function has(string $key): bool; } PK!(4libraries/vendor/psr/simple-cache/CacheException.phpnu[libraries/vendor/psr/http-client/RequestExceptionInterface.phpnu[libraries/vendor/psr/http-client/NetworkExceptionInterface.phpnu[getQuery()` * or from the `QUERY_STRING` server param. * * @return array */ public function getQueryParams(): array; /** * Return an instance with the specified query string arguments. * * These values SHOULD remain immutable over the course of the incoming * request. They MAY be injected during instantiation, such as from PHP's * $_GET superglobal, or MAY be derived from some other value such as the * URI. In cases where the arguments are parsed from the URI, the data * MUST be compatible with what PHP's parse_str() would return for * purposes of how duplicate query parameters are handled, and how nested * sets are handled. * * Setting query string arguments MUST NOT change the URI stored by the * request, nor the values in the server params. * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that has the * updated query string arguments. * * @param array $query Array of query string arguments, typically from * $_GET. * @return static */ public function withQueryParams(array $query): ServerRequestInterface; /** * Retrieve normalized file upload data. * * This method returns upload metadata in a normalized tree, with each leaf * an instance of Psr\Http\Message\UploadedFileInterface. * * These values MAY be prepared from $_FILES or the message body during * instantiation, or MAY be injected via withUploadedFiles(). * * @return array An array tree of UploadedFileInterface instances; an empty * array MUST be returned if no data is present. */ public function getUploadedFiles(): array; /** * Create a new instance with the specified uploaded files. * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that has the * updated body parameters. * * @param array $uploadedFiles An array tree of UploadedFileInterface instances. * @return static * @throws \InvalidArgumentException if an invalid structure is provided. */ public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface; /** * Retrieve any parameters provided in the request body. * * If the request Content-Type is either application/x-www-form-urlencoded * or multipart/form-data, and the request method is POST, this method MUST * return the contents of $_POST. * * Otherwise, this method may return any results of deserializing * the request body content; as parsing returns structured content, the * potential types MUST be arrays or objects only. A null value indicates * the absence of body content. * * @return null|array|object The deserialized body parameters, if any. * These will typically be an array or object. */ public function getParsedBody(); /** * Return an instance with the specified body parameters. * * These MAY be injected during instantiation. * * If the request Content-Type is either application/x-www-form-urlencoded * or multipart/form-data, and the request method is POST, use this method * ONLY to inject the contents of $_POST. * * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of * deserializing the request body content. Deserialization/parsing returns * structured data, and, as such, this method ONLY accepts arrays or objects, * or a null value if nothing was available to parse. * * As an example, if content negotiation determines that the request data * is a JSON payload, this method could be used to create a request * instance with the deserialized parameters. * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that has the * updated body parameters. * * @param null|array|object $data The deserialized body data. This will * typically be in an array or object. * @return static * @throws \InvalidArgumentException if an unsupported argument type is * provided. */ public function withParsedBody($data): ServerRequestInterface; /** * Retrieve attributes derived from the request. * * The request "attributes" may be used to allow injection of any * parameters derived from the request: e.g., the results of path * match operations; the results of decrypting cookies; the results of * deserializing non-form-encoded message bodies; etc. Attributes * will be application and request specific, and CAN be mutable. * * @return array Attributes derived from the request. */ public function getAttributes(): array; /** * Retrieve a single derived request attribute. * * Retrieves a single derived request attribute as described in * getAttributes(). If the attribute has not been previously set, returns * the default value as provided. * * This method obviates the need for a hasAttribute() method, as it allows * specifying a default value to return if the attribute is not found. * * @see getAttributes() * @param string $name The attribute name. * @param mixed $default Default value to return if the attribute does not exist. * @return mixed */ public function getAttribute(string $name, $default = null); /** * Return an instance with the specified derived request attribute. * * This method allows setting a single derived request attribute as * described in getAttributes(). * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that has the * updated attribute. * * @see getAttributes() * @param string $name The attribute name. * @param mixed $value The value of the attribute. * @return static */ public function withAttribute(string $name, $value): ServerRequestInterface; /** * Return an instance that removes the specified derived request attribute. * * This method allows removing a single derived request attribute as * described in getAttributes(). * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that removes * the attribute. * * @see getAttributes() * @param string $name The attribute name. * @return static */ public function withoutAttribute(string $name): ServerRequestInterface; } PK!D 7libraries/vendor/psr/http-message/ResponseInterface.phpnu[getHeaders() as $name => $values) { * echo $name . ": " . implode(", ", $values); * } * * // Emit headers iteratively: * foreach ($message->getHeaders() as $name => $values) { * foreach ($values as $value) { * header(sprintf('%s: %s', $name, $value), false); * } * } * * While header names are not case-sensitive, getHeaders() will preserve the * exact case in which headers were originally specified. * * @return string[][] Returns an associative array of the message's headers. Each * key MUST be a header name, and each value MUST be an array of strings * for that header. */ public function getHeaders(): array; /** * Checks if a header exists by the given case-insensitive name. * * @param string $name Case-insensitive header field name. * @return bool Returns true if any header names match the given header * name using a case-insensitive string comparison. Returns false if * no matching header name is found in the message. */ public function hasHeader(string $name): bool; /** * Retrieves a message header value by the given case-insensitive name. * * This method returns an array of all the header values of the given * case-insensitive header name. * * If the header does not appear in the message, this method MUST return an * empty array. * * @param string $name Case-insensitive header field name. * @return string[] An array of string values as provided for the given * header. If the header does not appear in the message, this method MUST * return an empty array. */ public function getHeader(string $name): array; /** * Retrieves a comma-separated string of the values for a single header. * * This method returns all of the header values of the given * case-insensitive header name as a string concatenated together using * a comma. * * NOTE: Not all header values may be appropriately represented using * comma concatenation. For such headers, use getHeader() instead * and supply your own delimiter when concatenating. * * If the header does not appear in the message, this method MUST return * an empty string. * * @param string $name Case-insensitive header field name. * @return string A string of values as provided for the given header * concatenated together using a comma. If the header does not appear in * the message, this method MUST return an empty string. */ public function getHeaderLine(string $name): string; /** * Return an instance with the provided value replacing the specified header. * * While header names are case-insensitive, the casing of the header will * be preserved by this function, and returned from getHeaders(). * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that has the * new and/or updated header and value. * * @param string $name Case-insensitive header field name. * @param string|string[] $value Header value(s). * @return static * @throws \InvalidArgumentException for invalid header names or values. */ public function withHeader(string $name, $value): MessageInterface; /** * Return an instance with the specified header appended with the given value. * * Existing values for the specified header will be maintained. The new * value(s) will be appended to the existing list. If the header did not * exist previously, it will be added. * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that has the * new header and/or value. * * @param string $name Case-insensitive header field name to add. * @param string|string[] $value Header value(s). * @return static * @throws \InvalidArgumentException for invalid header names or values. */ public function withAddedHeader(string $name, $value): MessageInterface; /** * Return an instance without the specified header. * * Header resolution MUST be done without case-sensitivity. * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that removes * the named header. * * @param string $name Case-insensitive header field name to remove. * @return static */ public function withoutHeader(string $name): MessageInterface; /** * Gets the body of the message. * * @return StreamInterface Returns the body as a stream. */ public function getBody(): StreamInterface; /** * Return an instance with the specified message body. * * The body MUST be a StreamInterface object. * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return a new instance that has the * new body stream. * * @param StreamInterface $body Body. * @return static * @throws \InvalidArgumentException When the body is not valid. */ public function withBody(StreamInterface $body): MessageInterface; } PK!!..2libraries/vendor/psr/http-message/UriInterface.phpnu[ * [user-info@]host[:port] * * * If the port component is not set or is the standard port for the current * scheme, it SHOULD NOT be included. * * @see https://tools.ietf.org/html/rfc3986#section-3.2 * @return string The URI authority, in "[user-info@]host[:port]" format. */ public function getAuthority(): string; /** * Retrieve the user information component of the URI. * * If no user information is present, this method MUST return an empty * string. * * If a user is present in the URI, this will return that value; * additionally, if the password is also present, it will be appended to the * user value, with a colon (":") separating the values. * * The trailing "@" character is not part of the user information and MUST * NOT be added. * * @return string The URI user information, in "username[:password]" format. */ public function getUserInfo(): string; /** * Retrieve the host component of the URI. * * If no host is present, this method MUST return an empty string. * * The value returned MUST be normalized to lowercase, per RFC 3986 * Section 3.2.2. * * @see http://tools.ietf.org/html/rfc3986#section-3.2.2 * @return string The URI host. */ public function getHost(): string; /** * Retrieve the port component of the URI. * * If a port is present, and it is non-standard for the current scheme, * this method MUST return it as an integer. If the port is the standard port * used with the current scheme, this method SHOULD return null. * * If no port is present, and no scheme is present, this method MUST return * a null value. * * If no port is present, but a scheme is present, this method MAY return * the standard port for that scheme, but SHOULD return null. * * @return null|int The URI port. */ public function getPort(): ?int; /** * Retrieve the path component of the URI. * * The path can either be empty or absolute (starting with a slash) or * rootless (not starting with a slash). Implementations MUST support all * three syntaxes. * * Normally, the empty path "" and absolute path "/" are considered equal as * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically * do this normalization because in contexts with a trimmed base path, e.g. * the front controller, this difference becomes significant. It's the task * of the user to handle both "" and "/". * * The value returned MUST be percent-encoded, but MUST NOT double-encode * any characters. To determine what characters to encode, please refer to * RFC 3986, Sections 2 and 3.3. * * As an example, if the value should include a slash ("/") not intended as * delimiter between path segments, that value MUST be passed in encoded * form (e.g., "%2F") to the instance. * * @see https://tools.ietf.org/html/rfc3986#section-2 * @see https://tools.ietf.org/html/rfc3986#section-3.3 * @return string The URI path. */ public function getPath(): string; /** * Retrieve the query string of the URI. * * If no query string is present, this method MUST return an empty string. * * The leading "?" character is not part of the query and MUST NOT be * added. * * The value returned MUST be percent-encoded, but MUST NOT double-encode * any characters. To determine what characters to encode, please refer to * RFC 3986, Sections 2 and 3.4. * * As an example, if a value in a key/value pair of the query string should * include an ampersand ("&") not intended as a delimiter between values, * that value MUST be passed in encoded form (e.g., "%26") to the instance. * * @see https://tools.ietf.org/html/rfc3986#section-2 * @see https://tools.ietf.org/html/rfc3986#section-3.4 * @return string The URI query string. */ public function getQuery(): string; /** * Retrieve the fragment component of the URI. * * If no fragment is present, this method MUST return an empty string. * * The leading "#" character is not part of the fragment and MUST NOT be * added. * * The value returned MUST be percent-encoded, but MUST NOT double-encode * any characters. To determine what characters to encode, please refer to * RFC 3986, Sections 2 and 3.5. * * @see https://tools.ietf.org/html/rfc3986#section-2 * @see https://tools.ietf.org/html/rfc3986#section-3.5 * @return string The URI fragment. */ public function getFragment(): string; /** * Return an instance with the specified scheme. * * This method MUST retain the state of the current instance, and return * an instance that contains the specified scheme. * * Implementations MUST support the schemes "http" and "https" case * insensitively, and MAY accommodate other schemes if required. * * An empty scheme is equivalent to removing the scheme. * * @param string $scheme The scheme to use with the new instance. * @return static A new instance with the specified scheme. * @throws \InvalidArgumentException for invalid or unsupported schemes. */ public function withScheme(string $scheme): UriInterface; /** * Return an instance with the specified user information. * * This method MUST retain the state of the current instance, and return * an instance that contains the specified user information. * * Password is optional, but the user information MUST include the * user; an empty string for the user is equivalent to removing user * information. * * @param string $user The user name to use for authority. * @param null|string $password The password associated with $user. * @return static A new instance with the specified user information. */ public function withUserInfo(string $user, ?string $password = null): UriInterface; /** * Return an instance with the specified host. * * This method MUST retain the state of the current instance, and return * an instance that contains the specified host. * * An empty host value is equivalent to removing the host. * * @param string $host The hostname to use with the new instance. * @return static A new instance with the specified host. * @throws \InvalidArgumentException for invalid hostnames. */ public function withHost(string $host): UriInterface; /** * Return an instance with the specified port. * * This method MUST retain the state of the current instance, and return * an instance that contains the specified port. * * Implementations MUST raise an exception for ports outside the * established TCP and UDP port ranges. * * A null value provided for the port is equivalent to removing the port * information. * * @param null|int $port The port to use with the new instance; a null value * removes the port information. * @return static A new instance with the specified port. * @throws \InvalidArgumentException for invalid ports. */ public function withPort(?int $port): UriInterface; /** * Return an instance with the specified path. * * This method MUST retain the state of the current instance, and return * an instance that contains the specified path. * * The path can either be empty or absolute (starting with a slash) or * rootless (not starting with a slash). Implementations MUST support all * three syntaxes. * * If the path is intended to be domain-relative rather than path relative then * it must begin with a slash ("/"). Paths not starting with a slash ("/") * are assumed to be relative to some base path known to the application or * consumer. * * Users can provide both encoded and decoded path characters. * Implementations ensure the correct encoding as outlined in getPath(). * * @param string $path The path to use with the new instance. * @return static A new instance with the specified path. * @throws \InvalidArgumentException for invalid paths. */ public function withPath(string $path): UriInterface; /** * Return an instance with the specified query string. * * This method MUST retain the state of the current instance, and return * an instance that contains the specified query string. * * Users can provide both encoded and decoded query characters. * Implementations ensure the correct encoding as outlined in getQuery(). * * An empty query string value is equivalent to removing the query string. * * @param string $query The query string to use with the new instance. * @return static A new instance with the specified query string. * @throws \InvalidArgumentException for invalid query strings. */ public function withQuery(string $query): UriInterface; /** * Return an instance with the specified URI fragment. * * This method MUST retain the state of the current instance, and return * an instance that contains the specified URI fragment. * * Users can provide both encoded and decoded fragment characters. * Implementations ensure the correct encoding as outlined in getFragment(). * * An empty fragment value is equivalent to removing the fragment. * * @param string $fragment The fragment to use with the new instance. * @return static A new instance with the specified fragment. */ public function withFragment(string $fragment): UriInterface; /** * Return the string representation as a URI reference. * * Depending on which components of the URI are present, the resulting * string is either a full URI or relative reference according to RFC 3986, * Section 4.1. The method concatenates the various components of the URI, * using the appropriate delimiters: * * - If a scheme is present, it MUST be suffixed by ":". * - If an authority is present, it MUST be prefixed by "//". * - The path can be concatenated without delimiters. But there are two * cases where the path has to be adjusted to make the URI reference * valid as PHP does not allow to throw an exception in __toString(): * - If the path is rootless and an authority is present, the path MUST * be prefixed by "/". * - If the path is starting with more than one "/" and no authority is * present, the starting slashes MUST be reduced to one. * - If a query is present, it MUST be prefixed by "?". * - If a fragment is present, it MUST be prefixed by "#". * * @see http://tools.ietf.org/html/rfc3986#section-4.1 * @return string */ public function __toString(): string; } PK!y=libraries/vendor/psr/http-factory/RequestFactoryInterface.phpnu[libraries/vendor/psr/http-factory/ResponseFactoryInterface.phpnu[toArray(); for ($x = 0; $x < $dimensions; ++$x) { $grid[$x][$x] = 1; } return new Matrix($grid); } } PK!ӝ7libraries/vendor/markbaker/matrix/classes/Exception.phpnu[execute($matrix); } return $result->result(); } public static function directsum(...$matrixValues): Matrix { if (count($matrixValues) < 2) { throw new Exception('DirectSum operation requires at least 2 arguments'); } $matrix = array_shift($matrixValues); if (is_array($matrix)) { $matrix = new Matrix($matrix); } if (!$matrix instanceof Matrix) { throw new Exception('DirectSum arguments must be Matrix or array'); } $result = new DirectSum($matrix); foreach ($matrixValues as $matrix) { $result->execute($matrix); } return $result->result(); } public static function divideby(...$matrixValues): Matrix { if (count($matrixValues) < 2) { throw new Exception('Division operation requires at least 2 arguments'); } $matrix = array_shift($matrixValues); if (is_array($matrix)) { $matrix = new Matrix($matrix); } if (!$matrix instanceof Matrix) { throw new Exception('Division arguments must be Matrix or array'); } $result = new Division($matrix); foreach ($matrixValues as $matrix) { $result->execute($matrix); } return $result->result(); } public static function divideinto(...$matrixValues): Matrix { if (count($matrixValues) < 2) { throw new Exception('Division operation requires at least 2 arguments'); } $matrix = array_pop($matrixValues); $matrixValues = array_reverse($matrixValues); if (is_array($matrix)) { $matrix = new Matrix($matrix); } if (!$matrix instanceof Matrix) { throw new Exception('Division arguments must be Matrix or array'); } $result = new Division($matrix); foreach ($matrixValues as $matrix) { $result->execute($matrix); } return $result->result(); } public static function multiply(...$matrixValues): Matrix { if (count($matrixValues) < 2) { throw new Exception('Multiplication operation requires at least 2 arguments'); } $matrix = array_shift($matrixValues); if (is_array($matrix)) { $matrix = new Matrix($matrix); } if (!$matrix instanceof Matrix) { throw new Exception('Multiplication arguments must be Matrix or array'); } $result = new Multiplication($matrix); foreach ($matrixValues as $matrix) { $result->execute($matrix); } return $result->result(); } public static function subtract(...$matrixValues): Matrix { if (count($matrixValues) < 2) { throw new Exception('Subtraction operation requires at least 2 arguments'); } $matrix = array_shift($matrixValues); if (is_array($matrix)) { $matrix = new Matrix($matrix); } if (!$matrix instanceof Matrix) { throw new Exception('Subtraction arguments must be Matrix or array'); } $result = new Subtraction($matrix); foreach ($matrixValues as $matrix) { $result->execute($matrix); } return $result->result(); } } PK!DR'R'4libraries/vendor/markbaker/matrix/classes/Matrix.phpnu[buildFromArray(array_values($grid)); } /* * Create a new Matrix object from an array of values * * @param array $grid */ protected function buildFromArray(array $grid): void { $this->rows = count($grid); $columns = array_reduce( $grid, function ($carry, $value) { return max($carry, is_array($value) ? count($value) : 1); } ); $this->columns = $columns; array_walk( $grid, function (&$value) use ($columns) { if (!is_array($value)) { $value = [$value]; } $value = array_pad(array_values($value), $columns, null); } ); $this->grid = $grid; } /** * Validate that a row number is a positive integer * * @param int $row * @return int * @throws Exception */ public static function validateRow(int $row): int { if ((!is_numeric($row)) || (intval($row) < 1)) { throw new Exception('Invalid Row'); } return (int)$row; } /** * Validate that a column number is a positive integer * * @param int $column * @return int * @throws Exception */ public static function validateColumn(int $column): int { if ((!is_numeric($column)) || (intval($column) < 1)) { throw new Exception('Invalid Column'); } return (int)$column; } /** * Validate that a row number falls within the set of rows for this matrix * * @param int $row * @return int * @throws Exception */ protected function validateRowInRange(int $row): int { $row = static::validateRow($row); if ($row > $this->rows) { throw new Exception('Requested Row exceeds matrix size'); } return $row; } /** * Validate that a column number falls within the set of columns for this matrix * * @param int $column * @return int * @throws Exception */ protected function validateColumnInRange(int $column): int { $column = static::validateColumn($column); if ($column > $this->columns) { throw new Exception('Requested Column exceeds matrix size'); } return $column; } /** * Return a new matrix as a subset of rows from this matrix, starting at row number $row, and $rowCount rows * A $rowCount value of 0 will return all rows of the matrix from $row * A negative $rowCount value will return rows until that many rows from the end of the matrix * * Note that row numbers start from 1, not from 0 * * @param int $row * @param int $rowCount * @return static * @throws Exception */ public function getRows(int $row, int $rowCount = 1): Matrix { $row = $this->validateRowInRange($row); if ($rowCount === 0) { $rowCount = $this->rows - $row + 1; } return new static(array_slice($this->grid, $row - 1, (int)$rowCount)); } /** * Return a new matrix as a subset of columns from this matrix, starting at column number $column, and $columnCount columns * A $columnCount value of 0 will return all columns of the matrix from $column * A negative $columnCount value will return columns until that many columns from the end of the matrix * * Note that column numbers start from 1, not from 0 * * @param int $column * @param int $columnCount * @return Matrix * @throws Exception */ public function getColumns(int $column, int $columnCount = 1): Matrix { $column = $this->validateColumnInRange($column); if ($columnCount < 1) { $columnCount = $this->columns + $columnCount - $column + 1; } $grid = []; for ($i = $column - 1; $i < $column + $columnCount - 1; ++$i) { $grid[] = array_column($this->grid, $i); } return (new static($grid))->transpose(); } /** * Return a new matrix as a subset of rows from this matrix, dropping rows starting at row number $row, * and $rowCount rows * A negative $rowCount value will drop rows until that many rows from the end of the matrix * A $rowCount value of 0 will remove all rows of the matrix from $row * * Note that row numbers start from 1, not from 0 * * @param int $row * @param int $rowCount * @return static * @throws Exception */ public function dropRows(int $row, int $rowCount = 1): Matrix { $this->validateRowInRange($row); if ($rowCount === 0) { $rowCount = $this->rows - $row + 1; } $grid = $this->grid; array_splice($grid, $row - 1, (int)$rowCount); return new static($grid); } /** * Return a new matrix as a subset of columns from this matrix, dropping columns starting at column number $column, * and $columnCount columns * A negative $columnCount value will drop columns until that many columns from the end of the matrix * A $columnCount value of 0 will remove all columns of the matrix from $column * * Note that column numbers start from 1, not from 0 * * @param int $column * @param int $columnCount * @return static * @throws Exception */ public function dropColumns(int $column, int $columnCount = 1): Matrix { $this->validateColumnInRange($column); if ($columnCount < 1) { $columnCount = $this->columns + $columnCount - $column + 1; } $grid = $this->grid; array_walk( $grid, function (&$row) use ($column, $columnCount) { array_splice($row, $column - 1, (int)$columnCount); } ); return new static($grid); } /** * Return a value from this matrix, from the "cell" identified by the row and column numbers * Note that row and column numbers start from 1, not from 0 * * @param int $row * @param int $column * @return mixed * @throws Exception */ public function getValue(int $row, int $column) { $row = $this->validateRowInRange($row); $column = $this->validateColumnInRange($column); return $this->grid[$row - 1][$column - 1]; } /** * Returns a Generator that will yield each row of the matrix in turn as a vector matrix * or the value of each cell if the matrix is a column vector * * @return Generator|Matrix[]|mixed[] */ public function rows(): Generator { foreach ($this->grid as $i => $row) { yield $i + 1 => ($this->columns == 1) ? $row[0] : new static([$row]); } } /** * Returns a Generator that will yield each column of the matrix in turn as a vector matrix * or the value of each cell if the matrix is a row vector * * @return Generator|Matrix[]|mixed[] */ public function columns(): Generator { for ($i = 0; $i < $this->columns; ++$i) { yield $i + 1 => ($this->rows == 1) ? $this->grid[0][$i] : new static(array_column($this->grid, $i)); } } /** * Identify if the row and column dimensions of this matrix are equal, * i.e. if it is a "square" matrix * * @return bool */ public function isSquare(): bool { return $this->rows === $this->columns; } /** * Identify if this matrix is a vector * i.e. if it comprises only a single row or a single column * * @return bool */ public function isVector(): bool { return $this->rows === 1 || $this->columns === 1; } /** * Return the matrix as a 2-dimensional array * * @return array */ public function toArray(): array { return $this->grid; } /** * Solve A*X = B. * * @param Matrix $B Right hand side * * @throws Exception * * @return Matrix ... Solution if A is square, least squares solution otherwise */ public function solve(Matrix $B): Matrix { if ($this->columns === $this->rows) { return (new LU($this))->solve($B); } return (new QR($this))->solve($B); } protected static $getters = [ 'rows', 'columns', ]; /** * Access specific properties as read-only (no setters) * * @param string $propertyName * @return mixed * @throws Exception */ public function __get(string $propertyName) { $propertyName = strtolower($propertyName); // Test for function calls if (in_array($propertyName, self::$getters)) { return $this->$propertyName; } throw new Exception('Property does not exist'); } protected static $functions = [ 'adjoint', 'antidiagonal', 'cofactors', 'determinant', 'diagonal', 'identity', 'inverse', 'minors', 'trace', 'transpose', ]; protected static $operations = [ 'add', 'subtract', 'multiply', 'divideby', 'divideinto', 'directsum', ]; /** * Returns the result of the function call or operation * * @param string $functionName * @param mixed[] $arguments * @return Matrix|float * @throws Exception */ public function __call(string $functionName, $arguments) { $functionName = strtolower(str_replace('_', '', $functionName)); // Test for function calls if (in_array($functionName, self::$functions, true)) { return Functions::$functionName($this, ...$arguments); } // Test for operation calls if (in_array($functionName, self::$operations, true)) { return Operations::$functionName($this, ...$arguments); } throw new Exception('Function or Operation does not exist'); } } PK!I$I$7libraries/vendor/markbaker/matrix/classes/Functions.phpnu[isSquare()) { throw new Exception('Adjoint can only be calculated for a square matrix'); } return self::getAdjoint($matrix); } /** * Calculate the cofactors of the matrix * * @param Matrix $matrix The matrix whose cofactors we wish to calculate * @return Matrix * * @throws Exception */ private static function getCofactors(Matrix $matrix) { $cofactors = self::getMinors($matrix); $dimensions = $matrix->rows; $cof = 1; for ($i = 0; $i < $dimensions; ++$i) { $cofs = $cof; for ($j = 0; $j < $dimensions; ++$j) { $cofactors[$i][$j] *= $cofs; $cofs = -$cofs; } $cof = -$cof; } return new Matrix($cofactors); } /** * Return the cofactors of this matrix * * @param Matrix|array $matrix The matrix whose cofactors we wish to calculate * @return Matrix * * @throws Exception */ public static function cofactors($matrix) { $matrix = self::validateMatrix($matrix); if (!$matrix->isSquare()) { throw new Exception('Cofactors can only be calculated for a square matrix'); } return self::getCofactors($matrix); } /** * @param Matrix $matrix * @param int $row * @param int $column * @return float * @throws Exception */ private static function getDeterminantSegment(Matrix $matrix, $row, $column) { $tmpMatrix = $matrix->toArray(); unset($tmpMatrix[$row]); array_walk( $tmpMatrix, function (&$row) use ($column) { unset($row[$column]); } ); return self::getDeterminant(new Matrix($tmpMatrix)); } /** * Calculate the determinant of the matrix * * @param Matrix $matrix The matrix whose determinant we wish to calculate * @return float * * @throws Exception */ private static function getDeterminant(Matrix $matrix) { $dimensions = $matrix->rows; $determinant = 0; switch ($dimensions) { case 1: $determinant = $matrix->getValue(1, 1); break; case 2: $determinant = $matrix->getValue(1, 1) * $matrix->getValue(2, 2) - $matrix->getValue(1, 2) * $matrix->getValue(2, 1); break; default: for ($i = 1; $i <= $dimensions; ++$i) { $det = $matrix->getValue(1, $i) * self::getDeterminantSegment($matrix, 0, $i - 1); if (($i % 2) == 0) { $determinant -= $det; } else { $determinant += $det; } } break; } return $determinant; } /** * Return the determinant of this matrix * * @param Matrix|array $matrix The matrix whose determinant we wish to calculate * @return float * @throws Exception **/ public static function determinant($matrix) { $matrix = self::validateMatrix($matrix); if (!$matrix->isSquare()) { throw new Exception('Determinant can only be calculated for a square matrix'); } return self::getDeterminant($matrix); } /** * Return the diagonal of this matrix * * @param Matrix|array $matrix The matrix whose diagonal we wish to calculate * @return Matrix * @throws Exception **/ public static function diagonal($matrix) { $matrix = self::validateMatrix($matrix); if (!$matrix->isSquare()) { throw new Exception('Diagonal can only be extracted from a square matrix'); } $dimensions = $matrix->rows; $grid = Builder::createFilledMatrix(0, $dimensions, $dimensions) ->toArray(); for ($i = 0; $i < $dimensions; ++$i) { $grid[$i][$i] = $matrix->getValue($i + 1, $i + 1); } return new Matrix($grid); } /** * Return the antidiagonal of this matrix * * @param Matrix|array $matrix The matrix whose antidiagonal we wish to calculate * @return Matrix * @throws Exception **/ public static function antidiagonal($matrix) { $matrix = self::validateMatrix($matrix); if (!$matrix->isSquare()) { throw new Exception('Anti-Diagonal can only be extracted from a square matrix'); } $dimensions = $matrix->rows; $grid = Builder::createFilledMatrix(0, $dimensions, $dimensions) ->toArray(); for ($i = 0; $i < $dimensions; ++$i) { $grid[$i][$dimensions - $i - 1] = $matrix->getValue($i + 1, $dimensions - $i); } return new Matrix($grid); } /** * Return the identity matrix * The identity matrix, or sometimes ambiguously called a unit matrix, of size n is the n × n square matrix * with ones on the main diagonal and zeros elsewhere * * @param Matrix|array $matrix The matrix whose identity we wish to calculate * @return Matrix * @throws Exception **/ public static function identity($matrix) { $matrix = self::validateMatrix($matrix); if (!$matrix->isSquare()) { throw new Exception('Identity can only be created for a square matrix'); } $dimensions = $matrix->rows; return Builder::createIdentityMatrix($dimensions); } /** * Return the inverse of this matrix * * @param Matrix|array $matrix The matrix whose inverse we wish to calculate * @return Matrix * @throws Exception **/ public static function inverse($matrix, string $type = 'inverse') { $matrix = self::validateMatrix($matrix); if (!$matrix->isSquare()) { throw new Exception(ucfirst($type) . ' can only be calculated for a square matrix'); } $determinant = self::getDeterminant($matrix); if ($determinant == 0.0) { throw new Div0Exception(ucfirst($type) . ' can only be calculated for a matrix with a non-zero determinant'); } if ($matrix->rows == 1) { return new Matrix([[1 / $matrix->getValue(1, 1)]]); } return self::getAdjoint($matrix) ->multiply(1 / $determinant); } /** * Calculate the minors of the matrix * * @param Matrix $matrix The matrix whose minors we wish to calculate * @return array[] * * @throws Exception */ protected static function getMinors(Matrix $matrix) { $minors = $matrix->toArray(); $dimensions = $matrix->rows; if ($dimensions == 1) { return $minors; } for ($i = 0; $i < $dimensions; ++$i) { for ($j = 0; $j < $dimensions; ++$j) { $minors[$i][$j] = self::getDeterminantSegment($matrix, $i, $j); } } return $minors; } /** * Return the minors of the matrix * The minor of a matrix A is the determinant of some smaller square matrix, cut down from A by removing one or * more of its rows or columns. * Minors obtained by removing just one row and one column from square matrices (first minors) are required for * calculating matrix cofactors, which in turn are useful for computing both the determinant and inverse of * square matrices. * * @param Matrix|array $matrix The matrix whose minors we wish to calculate * @return Matrix * @throws Exception **/ public static function minors($matrix) { $matrix = self::validateMatrix($matrix); if (!$matrix->isSquare()) { throw new Exception('Minors can only be calculated for a square matrix'); } return new Matrix(self::getMinors($matrix)); } /** * Return the trace of this matrix * The trace is defined as the sum of the elements on the main diagonal (the diagonal from the upper left to the lower right) * of the matrix * * @param Matrix|array $matrix The matrix whose trace we wish to calculate * @return float * @throws Exception **/ public static function trace($matrix) { $matrix = self::validateMatrix($matrix); if (!$matrix->isSquare()) { throw new Exception('Trace can only be extracted from a square matrix'); } $dimensions = $matrix->rows; $result = 0; for ($i = 1; $i <= $dimensions; ++$i) { $result += $matrix->getValue($i, $i); } return $result; } /** * Return the transpose of this matrix * * @param Matrix|\a $matrix The matrix whose transpose we wish to calculate * @return Matrix **/ public static function transpose($matrix) { $matrix = self::validateMatrix($matrix); $array = array_values(array_merge([null], $matrix->toArray())); $grid = call_user_func_array( 'array_map', $array ); return new Matrix($grid); } } PK!3libraries/vendor/markbaker/matrix/classes/index.phpnu[libraries/vendor/markbaker/matrix/classes/Decomposition/QR.phpnu[qrMatrix = $matrix->toArray(); $this->rows = $matrix->rows; $this->columns = $matrix->columns; $this->decompose(); } public function getHouseholdVectors(): Matrix { $householdVectors = []; for ($row = 0; $row < $this->rows; ++$row) { for ($column = 0; $column < $this->columns; ++$column) { if ($row >= $column) { $householdVectors[$row][$column] = $this->qrMatrix[$row][$column]; } else { $householdVectors[$row][$column] = 0.0; } } } return new Matrix($householdVectors); } public function getQ(): Matrix { $qGrid = []; $rowCount = $this->rows; for ($k = $this->columns - 1; $k >= 0; --$k) { for ($i = 0; $i < $this->rows; ++$i) { $qGrid[$i][$k] = 0.0; } $qGrid[$k][$k] = 1.0; if ($this->columns > $this->rows) { $qGrid = array_slice($qGrid, 0, $this->rows); } for ($j = $k; $j < $this->columns; ++$j) { if (isset($this->qrMatrix[$k], $this->qrMatrix[$k][$k]) && $this->qrMatrix[$k][$k] != 0.0) { $s = 0.0; for ($i = $k; $i < $this->rows; ++$i) { $s += $this->qrMatrix[$i][$k] * $qGrid[$i][$j]; } $s = -$s / $this->qrMatrix[$k][$k]; for ($i = $k; $i < $this->rows; ++$i) { $qGrid[$i][$j] += $s * $this->qrMatrix[$i][$k]; } } } } array_walk( $qGrid, function (&$row) use ($rowCount) { $row = array_reverse($row); $row = array_slice($row, 0, $rowCount); } ); return new Matrix($qGrid); } public function getR(): Matrix { $rGrid = []; for ($row = 0; $row < $this->columns; ++$row) { for ($column = 0; $column < $this->columns; ++$column) { if ($row < $column) { $rGrid[$row][$column] = $this->qrMatrix[$row][$column] ?? 0.0; } elseif ($row === $column) { $rGrid[$row][$column] = $this->rDiagonal[$row] ?? 0.0; } else { $rGrid[$row][$column] = 0.0; } } } if ($this->columns > $this->rows) { $rGrid = array_slice($rGrid, 0, $this->rows); } return new Matrix($rGrid); } private function hypo($a, $b): float { if (abs($a) > abs($b)) { $r = $b / $a; $r = abs($a) * sqrt(1 + $r * $r); } elseif ($b != 0.0) { $r = $a / $b; $r = abs($b) * sqrt(1 + $r * $r); } else { $r = 0.0; } return $r; } /** * QR Decomposition computed by Householder reflections. */ private function decompose(): void { for ($k = 0; $k < $this->columns; ++$k) { // Compute 2-norm of k-th column without under/overflow. $norm = 0.0; for ($i = $k; $i < $this->rows; ++$i) { $norm = $this->hypo($norm, $this->qrMatrix[$i][$k]); } if ($norm != 0.0) { // Form k-th Householder vector. if ($this->qrMatrix[$k][$k] < 0.0) { $norm = -$norm; } for ($i = $k; $i < $this->rows; ++$i) { $this->qrMatrix[$i][$k] /= $norm; } $this->qrMatrix[$k][$k] += 1.0; // Apply transformation to remaining columns. for ($j = $k + 1; $j < $this->columns; ++$j) { $s = 0.0; for ($i = $k; $i < $this->rows; ++$i) { $s += $this->qrMatrix[$i][$k] * $this->qrMatrix[$i][$j]; } $s = -$s / $this->qrMatrix[$k][$k]; for ($i = $k; $i < $this->rows; ++$i) { $this->qrMatrix[$i][$j] += $s * $this->qrMatrix[$i][$k]; } } } $this->rDiagonal[$k] = -$norm; } } public function isFullRank(): bool { for ($j = 0; $j < $this->columns; ++$j) { if ($this->rDiagonal[$j] == 0.0) { return false; } } return true; } /** * Least squares solution of A*X = B. * * @param Matrix $B a Matrix with as many rows as A and any number of columns * * @throws Exception * * @return Matrix matrix that minimizes the two norm of Q*R*X-B */ public function solve(Matrix $B): Matrix { if ($B->rows !== $this->rows) { throw new Exception('Matrix row dimensions are not equal'); } if (!$this->isFullRank()) { throw new Exception('Can only perform this operation on a full-rank matrix'); } // Compute Y = transpose(Q)*B $Y = $this->getQ()->transpose() ->multiply($B); // Solve R*X = Y; return $this->getR()->inverse() ->multiply($Y); } } PK!8855>libraries/vendor/markbaker/matrix/classes/Decomposition/LU.phpnu[luMatrix = $matrix->toArray(); $this->rows = $matrix->rows; $this->columns = $matrix->columns; $this->buildPivot(); } /** * Get lower triangular factor. * * @return Matrix Lower triangular factor */ public function getL(): Matrix { $lower = []; $columns = min($this->rows, $this->columns); for ($row = 0; $row < $this->rows; ++$row) { for ($column = 0; $column < $columns; ++$column) { if ($row > $column) { $lower[$row][$column] = $this->luMatrix[$row][$column]; } elseif ($row === $column) { $lower[$row][$column] = 1.0; } else { $lower[$row][$column] = 0.0; } } } return new Matrix($lower); } /** * Get upper triangular factor. * * @return Matrix Upper triangular factor */ public function getU(): Matrix { $upper = []; $rows = min($this->rows, $this->columns); for ($row = 0; $row < $rows; ++$row) { for ($column = 0; $column < $this->columns; ++$column) { if ($row <= $column) { $upper[$row][$column] = $this->luMatrix[$row][$column]; } else { $upper[$row][$column] = 0.0; } } } return new Matrix($upper); } /** * Return pivot permutation vector. * * @return Matrix Pivot matrix */ public function getP(): Matrix { $pMatrix = []; $pivots = $this->pivot; $pivotCount = count($pivots); foreach ($pivots as $row => $pivot) { $pMatrix[$row] = array_fill(0, $pivotCount, 0); $pMatrix[$row][$pivot] = 1; } return new Matrix($pMatrix); } /** * Return pivot permutation vector. * * @return array Pivot vector */ public function getPivot(): array { return $this->pivot; } /** * Is the matrix nonsingular? * * @return bool true if U, and hence A, is nonsingular */ public function isNonsingular(): bool { for ($diagonal = 0; $diagonal < $this->columns; ++$diagonal) { if ($this->luMatrix[$diagonal][$diagonal] === 0.0) { return false; } } return true; } private function buildPivot(): void { for ($row = 0; $row < $this->rows; ++$row) { $this->pivot[$row] = $row; } for ($column = 0; $column < $this->columns; ++$column) { $luColumn = $this->localisedReferenceColumn($column); $this->applyTransformations($column, $luColumn); $pivot = $this->findPivot($column, $luColumn); if ($pivot !== $column) { $this->pivotExchange($pivot, $column); } $this->computeMultipliers($column); unset($luColumn); } } private function localisedReferenceColumn($column): array { $luColumn = []; for ($row = 0; $row < $this->rows; ++$row) { $luColumn[$row] = &$this->luMatrix[$row][$column]; } return $luColumn; } private function applyTransformations($column, array $luColumn): void { for ($row = 0; $row < $this->rows; ++$row) { $luRow = $this->luMatrix[$row]; // Most of the time is spent in the following dot product. $kmax = min($row, $column); $sValue = 0.0; for ($kValue = 0; $kValue < $kmax; ++$kValue) { $sValue += $luRow[$kValue] * $luColumn[$kValue]; } $luRow[$column] = $luColumn[$row] -= $sValue; } } private function findPivot($column, array $luColumn): int { $pivot = $column; for ($row = $column + 1; $row < $this->rows; ++$row) { if (abs($luColumn[$row]) > abs($luColumn[$pivot])) { $pivot = $row; } } return $pivot; } private function pivotExchange($pivot, $column): void { for ($kValue = 0; $kValue < $this->columns; ++$kValue) { $tValue = $this->luMatrix[$pivot][$kValue]; $this->luMatrix[$pivot][$kValue] = $this->luMatrix[$column][$kValue]; $this->luMatrix[$column][$kValue] = $tValue; } $lValue = $this->pivot[$pivot]; $this->pivot[$pivot] = $this->pivot[$column]; $this->pivot[$column] = $lValue; } private function computeMultipliers($diagonal): void { if (($diagonal < $this->rows) && ($this->luMatrix[$diagonal][$diagonal] != 0.0)) { for ($row = $diagonal + 1; $row < $this->rows; ++$row) { $this->luMatrix[$row][$diagonal] /= $this->luMatrix[$diagonal][$diagonal]; } } } private function pivotB(Matrix $B): array { $X = []; foreach ($this->pivot as $rowId) { $row = $B->getRows($rowId + 1)->toArray(); $X[] = array_pop($row); } return $X; } /** * Solve A*X = B. * * @param Matrix $B a Matrix with as many rows as A and any number of columns * * @throws Exception * * @return Matrix X so that L*U*X = B(piv,:) */ public function solve(Matrix $B): Matrix { if ($B->rows !== $this->rows) { throw new Exception('Matrix row dimensions are not equal'); } if ($this->rows !== $this->columns) { throw new Exception('LU solve() only works on square matrices'); } if (!$this->isNonsingular()) { throw new Exception('Can only perform operation on singular matrix'); } // Copy right hand side with pivoting $nx = $B->columns; $X = $this->pivotB($B); // Solve L*Y = B(piv,:) for ($k = 0; $k < $this->columns; ++$k) { for ($i = $k + 1; $i < $this->columns; ++$i) { for ($j = 0; $j < $nx; ++$j) { $X[$i][$j] -= $X[$k][$j] * $this->luMatrix[$i][$k]; } } } // Solve U*X = Y; for ($k = $this->columns - 1; $k >= 0; --$k) { for ($j = 0; $j < $nx; ++$j) { $X[$k][$j] /= $this->luMatrix[$k][$k]; } for ($i = 0; $i < $k; ++$i) { for ($j = 0; $j < $nx; ++$j) { $X[$i][$j] -= $X[$k][$j] * $this->luMatrix[$i][$k]; } } } return new Matrix($X); } } PK!;libraries/vendor/markbaker/matrix/classes/Div0Exception.phpnu[addMatrix($value); } elseif (is_numeric($value)) { return $this->addScalar($value); } throw new Exception('Invalid argument for addition'); } /** * Execute the addition for a scalar * * @param mixed $value The numeric value to add to the current base value * @return $this The operation object, allowing multiple additions to be chained **/ protected function addScalar($value): Operator { for ($row = 0; $row < $this->rows; ++$row) { for ($column = 0; $column < $this->columns; ++$column) { $this->matrix[$row][$column] += $value; } } return $this; } /** * Execute the addition for a matrix * * @param Matrix $value The numeric value to add to the current base value * @return $this The operation object, allowing multiple additions to be chained * @throws Exception If the provided argument is not appropriate for the operation **/ protected function addMatrix(Matrix $value): Operator { $this->validateMatchingDimensions($value); for ($row = 0; $row < $this->rows; ++$row) { for ($column = 0; $column < $this->columns; ++$column) { $this->matrix[$row][$column] += $value->getValue($row + 1, $column + 1); } } return $this; } } PK!w0ؗ@libraries/vendor/markbaker/matrix/classes/Operators/Operator.phpnu[rows = $matrix->rows; $this->columns = $matrix->columns; $this->matrix = $matrix->toArray(); } /** * Compare the dimensions of the matrices being operated on to see if they are valid for addition/subtraction * * @param Matrix $matrix The second Matrix object on which the operation will be performed * @throws Exception */ protected function validateMatchingDimensions(Matrix $matrix): void { if (($this->rows != $matrix->rows) || ($this->columns != $matrix->columns)) { throw new Exception('Matrices have mismatched dimensions'); } } /** * Compare the dimensions of the matrices being operated on to see if they are valid for multiplication/division * * @param Matrix $matrix The second Matrix object on which the operation will be performed * @throws Exception */ protected function validateReflectingDimensions(Matrix $matrix): void { if ($this->columns != $matrix->rows) { throw new Exception('Matrices have mismatched dimensions'); } } /** * Return the result of the operation * * @return Matrix */ public function result(): Matrix { return new Matrix($this->matrix); } } PK!|eeClibraries/vendor/markbaker/matrix/classes/Operators/Subtraction.phpnu[subtractMatrix($value); } elseif (is_numeric($value)) { return $this->subtractScalar($value); } throw new Exception('Invalid argument for subtraction'); } /** * Execute the subtraction for a scalar * * @param mixed $value The numeric value to subtracted from the current base value * @return $this The operation object, allowing multiple additions to be chained **/ protected function subtractScalar($value): Operator { for ($row = 0; $row < $this->rows; ++$row) { for ($column = 0; $column < $this->columns; ++$column) { $this->matrix[$row][$column] -= $value; } } return $this; } /** * Execute the subtraction for a matrix * * @param Matrix $value The numeric value to subtract from the current base value * @return $this The operation object, allowing multiple subtractions to be chained * @throws Exception If the provided argument is not appropriate for the operation **/ protected function subtractMatrix(Matrix $value): Operator { $this->validateMatchingDimensions($value); for ($row = 0; $row < $this->rows; ++$row) { for ($column = 0; $column < $this->columns; ++$column) { $this->matrix[$row][$column] -= $value->getValue($row + 1, $column + 1); } } return $this; } } PK!=libraries/vendor/markbaker/matrix/classes/Operators/index.phpnu[directSumMatrix($value); } throw new Exception('Invalid argument for addition'); } /** * Execute the direct sum for a matrix * * @param Matrix $value The numeric value to concatenate/direct sum with the current base value * @return $this The operation object, allowing multiple additions to be chained **/ private function directSumMatrix($value): Operator { $originalColumnCount = count($this->matrix[0]); $originalRowCount = count($this->matrix); $valColumnCount = $value->columns; $valRowCount = $value->rows; $value = $value->toArray(); for ($row = 0; $row < $this->rows; ++$row) { $this->matrix[$row] = array_merge($this->matrix[$row], array_fill(0, $valColumnCount, 0)); } $this->matrix = array_merge( $this->matrix, array_fill(0, $valRowCount, array_fill(0, $originalColumnCount, 0)) ); for ($row = $originalRowCount; $row < $originalRowCount + $valRowCount; ++$row) { array_splice( $this->matrix[$row], $originalColumnCount, $valColumnCount, $value[$row - $originalRowCount] ); } return $this; } } PK!ھ@libraries/vendor/markbaker/matrix/classes/Operators/Division.phpnu[multiplyMatrix($value, $type); } elseif (is_numeric($value)) { return $this->multiplyScalar(1 / $value, $type); } throw new Exception('Invalid argument for division'); } } PK!^ Flibraries/vendor/markbaker/matrix/classes/Operators/Multiplication.phpnu[multiplyMatrix($value, $type); } elseif (is_numeric($value)) { return $this->multiplyScalar($value, $type); } throw new Exception("Invalid argument for $type"); } /** * Execute the multiplication for a scalar * * @param mixed $value The numeric value to multiply with the current base value * @return $this The operation object, allowing multiple mutiplications to be chained **/ protected function multiplyScalar($value, string $type = 'multiplication'): Operator { try { for ($row = 0; $row < $this->rows; ++$row) { for ($column = 0; $column < $this->columns; ++$column) { $this->matrix[$row][$column] *= $value; } } } catch (Throwable $e) { throw new Exception("Invalid argument for $type"); } return $this; } /** * Execute the multiplication for a matrix * * @param Matrix $value The numeric value to multiply with the current base value * @return $this The operation object, allowing multiple mutiplications to be chained * @throws Exception If the provided argument is not appropriate for the operation **/ protected function multiplyMatrix(Matrix $value, string $type = 'multiplication'): Operator { $this->validateReflectingDimensions($value); $newRows = $this->rows; $newColumns = $value->columns; $matrix = Builder::createFilledMatrix(0, $newRows, $newColumns) ->toArray(); try { for ($row = 0; $row < $newRows; ++$row) { for ($column = 0; $column < $newColumns; ++$column) { $columnData = $value->getColumns($column + 1)->toArray(); foreach ($this->matrix[$row] as $key => $valueData) { $matrix[$row][$column] += $valueData * $columnData[$key][0]; } } } } catch (Throwable $e) { throw new Exception("Invalid argument for $type"); } $this->matrix = $matrix; return $this; } } PK!+libraries/vendor/markbaker/matrix/index.phpnu[realPart = (float) $realPart; $this->imaginaryPart = (float) $imaginaryPart; $this->suffix = strtolower($suffix ?? ''); } /** * Gets the real part of this complex number * * @return Float */ public function getReal(): float { return $this->realPart; } /** * Gets the imaginary part of this complex number * * @return Float */ public function getImaginary(): float { return $this->imaginaryPart; } /** * Gets the suffix of this complex number * * @return String */ public function getSuffix(): string { return $this->suffix; } /** * Returns true if this is a real value, false if a complex value * * @return Bool */ public function isReal(): bool { return $this->imaginaryPart == 0.0; } /** * Returns true if this is a complex value, false if a real value * * @return Bool */ public function isComplex(): bool { return !$this->isReal(); } public function format(): string { $str = ""; if ($this->imaginaryPart != 0.0) { if (\abs($this->imaginaryPart) != 1.0) { $str .= $this->imaginaryPart . $this->suffix; } else { $str .= (($this->imaginaryPart < 0.0) ? '-' : '') . $this->suffix; } } if ($this->realPart != 0.0) { if (($str) && ($this->imaginaryPart > 0.0)) { $str = "+" . $str; } $str = $this->realPart . $str; } if (!$str) { $str = "0.0"; } return $str; } public function __toString(): string { return $this->format(); } /** * Validates whether the argument is a valid complex number, converting scalar or array values if possible * * @param mixed $complex The value to validate * @return Complex * @throws Exception If the argument isn't a Complex number or cannot be converted to one */ public static function validateComplexArgument($complex): Complex { if (is_scalar($complex) || is_array($complex)) { $complex = new Complex($complex); } elseif (!is_object($complex) || !($complex instanceof Complex)) { throw new Exception('Value is not a valid complex number'); } return $complex; } /** * Returns the reverse of this complex number * * @return Complex */ public function reverse(): Complex { return new Complex( $this->imaginaryPart, $this->realPart, ($this->realPart == 0.0) ? null : $this->suffix ); } public function invertImaginary(): Complex { return new Complex( $this->realPart, $this->imaginaryPart * -1, ($this->imaginaryPart == 0.0) ? null : $this->suffix ); } public function invertReal(): Complex { return new Complex( $this->realPart * -1, $this->imaginaryPart, ($this->imaginaryPart == 0.0) ? null : $this->suffix ); } protected static $functions = [ 'abs', 'acos', 'acosh', 'acot', 'acoth', 'acsc', 'acsch', 'argument', 'asec', 'asech', 'asin', 'asinh', 'atan', 'atanh', 'conjugate', 'cos', 'cosh', 'cot', 'coth', 'csc', 'csch', 'exp', 'inverse', 'ln', 'log2', 'log10', 'negative', 'pow', 'rho', 'sec', 'sech', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'theta', ]; protected static $operations = [ 'add', 'subtract', 'multiply', 'divideby', 'divideinto', ]; /** * Returns the result of the function call or operation * * @return Complex|float * @throws Exception|\InvalidArgumentException */ public function __call($functionName, $arguments) { $functionName = strtolower(str_replace('_', '', $functionName)); // Test for function calls if (in_array($functionName, self::$functions, true)) { return Functions::$functionName($this, ...$arguments); } // Test for operation calls if (in_array($functionName, self::$operations, true)) { return Operations::$functionName($this, ...$arguments); } throw new Exception('Complex Function or Operation does not exist'); } } PK!b8libraries/vendor/markbaker/complex/classes/Exception.phpnu[isComplex() && $complex->isComplex() && $result->getSuffix() !== $complex->getSuffix()) { throw new Exception('Suffix Mismatch'); } $real = $result->getReal() + $complex->getReal(); $imaginary = $result->getImaginary() + $complex->getImaginary(); $result = new Complex( $real, $imaginary, ($imaginary == 0.0) ? null : max($result->getSuffix(), $complex->getSuffix()) ); } return $result; } /** * Divides two or more complex numbers * * @param array of string|integer|float|Complex $complexValues The numbers to divide * @return Complex */ public static function divideby(...$complexValues): Complex { if (count($complexValues) < 2) { throw new \Exception('This function requires at least 2 arguments'); } $base = array_shift($complexValues); $result = clone Complex::validateComplexArgument($base); foreach ($complexValues as $complex) { $complex = Complex::validateComplexArgument($complex); if ($result->isComplex() && $complex->isComplex() && $result->getSuffix() !== $complex->getSuffix()) { throw new Exception('Suffix Mismatch'); } if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { throw new InvalidArgumentException('Division by zero'); } $delta1 = ($result->getReal() * $complex->getReal()) + ($result->getImaginary() * $complex->getImaginary()); $delta2 = ($result->getImaginary() * $complex->getReal()) - ($result->getReal() * $complex->getImaginary()); $delta3 = ($complex->getReal() * $complex->getReal()) + ($complex->getImaginary() * $complex->getImaginary()); $real = $delta1 / $delta3; $imaginary = $delta2 / $delta3; $result = new Complex( $real, $imaginary, ($imaginary == 0.0) ? null : max($result->getSuffix(), $complex->getSuffix()) ); } return $result; } /** * Divides two or more complex numbers * * @param array of string|integer|float|Complex $complexValues The numbers to divide * @return Complex */ public static function divideinto(...$complexValues): Complex { if (count($complexValues) < 2) { throw new \Exception('This function requires at least 2 arguments'); } $base = array_shift($complexValues); $result = clone Complex::validateComplexArgument($base); foreach ($complexValues as $complex) { $complex = Complex::validateComplexArgument($complex); if ($result->isComplex() && $complex->isComplex() && $result->getSuffix() !== $complex->getSuffix()) { throw new Exception('Suffix Mismatch'); } if ($result->getReal() == 0.0 && $result->getImaginary() == 0.0) { throw new InvalidArgumentException('Division by zero'); } $delta1 = ($complex->getReal() * $result->getReal()) + ($complex->getImaginary() * $result->getImaginary()); $delta2 = ($complex->getImaginary() * $result->getReal()) - ($complex->getReal() * $result->getImaginary()); $delta3 = ($result->getReal() * $result->getReal()) + ($result->getImaginary() * $result->getImaginary()); $real = $delta1 / $delta3; $imaginary = $delta2 / $delta3; $result = new Complex( $real, $imaginary, ($imaginary == 0.0) ? null : max($result->getSuffix(), $complex->getSuffix()) ); } return $result; } /** * Multiplies two or more complex numbers * * @param array of string|integer|float|Complex $complexValues The numbers to multiply * @return Complex */ public static function multiply(...$complexValues): Complex { if (count($complexValues) < 2) { throw new \Exception('This function requires at least 2 arguments'); } $base = array_shift($complexValues); $result = clone Complex::validateComplexArgument($base); foreach ($complexValues as $complex) { $complex = Complex::validateComplexArgument($complex); if ($result->isComplex() && $complex->isComplex() && $result->getSuffix() !== $complex->getSuffix()) { throw new Exception('Suffix Mismatch'); } $real = ($result->getReal() * $complex->getReal()) - ($result->getImaginary() * $complex->getImaginary()); $imaginary = ($result->getReal() * $complex->getImaginary()) + ($result->getImaginary() * $complex->getReal()); $result = new Complex( $real, $imaginary, ($imaginary == 0.0) ? null : max($result->getSuffix(), $complex->getSuffix()) ); } return $result; } /** * Subtracts two or more complex numbers * * @param array of string|integer|float|Complex $complexValues The numbers to subtract * @return Complex */ public static function subtract(...$complexValues): Complex { if (count($complexValues) < 2) { throw new \Exception('This function requires at least 2 arguments'); } $base = array_shift($complexValues); $result = clone Complex::validateComplexArgument($base); foreach ($complexValues as $complex) { $complex = Complex::validateComplexArgument($complex); if ($result->isComplex() && $complex->isComplex() && $result->getSuffix() !== $complex->getSuffix()) { throw new Exception('Suffix Mismatch'); } $real = $result->getReal() - $complex->getReal(); $imaginary = $result->getImaginary() - $complex->getImaginary(); $result = new Complex( $real, $imaginary, ($imaginary == 0.0) ? null : max($result->getSuffix(), $complex->getSuffix()) ); } return $result; } } PK! j j8libraries/vendor/markbaker/complex/classes/Functions.phpnu[getReal() - $invsqrt->getImaginary(), $complex->getImaginary() + $invsqrt->getReal() ); $log = self::ln($adjust); return new Complex( $log->getImaginary(), -1 * $log->getReal() ); } /** * Returns the inverse hyperbolic cosine of a complex number. * * Formula from Wolfram Alpha: * cosh^(-1)z = ln(z + sqrt(z + 1) sqrt(z - 1)). * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse hyperbolic cosine of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function acosh($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->isReal() && ($complex->getReal() > 1)) { return new Complex(\acosh($complex->getReal())); } $acosh = self::ln( Operations::add( $complex, Operations::multiply( self::sqrt(Operations::add($complex, 1)), self::sqrt(Operations::subtract($complex, 1)) ) ) ); return $acosh; } /** * Returns the inverse cotangent of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse cotangent of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function acot($complex): Complex { $complex = Complex::validateComplexArgument($complex); return self::atan(self::inverse($complex)); } /** * Returns the inverse hyperbolic cotangent of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse hyperbolic cotangent of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function acoth($complex): Complex { $complex = Complex::validateComplexArgument($complex); return self::atanh(self::inverse($complex)); } /** * Returns the inverse cosecant of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse cosecant of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function acsc($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { return new Complex(INF); } return self::asin(self::inverse($complex)); } /** * Returns the inverse hyperbolic cosecant of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse hyperbolic cosecant of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function acsch($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { return new Complex(INF); } return self::asinh(self::inverse($complex)); } /** * Returns the argument of a complex number. * Also known as the theta of the complex number, i.e. the angle in radians * from the real axis to the representation of the number in polar coordinates. * * This function is a synonym for theta() * * @param Complex|mixed $complex Complex number or a numeric value. * @return float The argument (or theta) value of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * * @see theta */ public static function argument($complex): float { return self::theta($complex); } /** * Returns the inverse secant of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse secant of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function asec($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { return new Complex(INF); } return self::acos(self::inverse($complex)); } /** * Returns the inverse hyperbolic secant of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse hyperbolic secant of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function asech($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { return new Complex(INF); } return self::acosh(self::inverse($complex)); } /** * Returns the inverse sine of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse sine of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function asin($complex): Complex { $complex = Complex::validateComplexArgument($complex); $invsqrt = self::sqrt(Operations::subtract(1, Operations::multiply($complex, $complex))); $adjust = new Complex( $invsqrt->getReal() - $complex->getImaginary(), $invsqrt->getImaginary() + $complex->getReal() ); $log = self::ln($adjust); return new Complex( $log->getImaginary(), -1 * $log->getReal() ); } /** * Returns the inverse hyperbolic sine of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse hyperbolic sine of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function asinh($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->isReal() && ($complex->getReal() > 1)) { return new Complex(\asinh($complex->getReal())); } $asinh = clone $complex; $asinh = $asinh->reverse() ->invertReal(); $asinh = self::asin($asinh); return $asinh->reverse() ->invertImaginary(); } /** * Returns the inverse tangent of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse tangent of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function atan($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->isReal()) { return new Complex(\atan($complex->getReal())); } $t1Value = new Complex(-1 * $complex->getImaginary(), $complex->getReal()); $uValue = new Complex(1, 0); $d1Value = clone $uValue; $d1Value = Operations::subtract($d1Value, $t1Value); $d2Value = Operations::add($t1Value, $uValue); $uResult = $d1Value->divideBy($d2Value); $uResult = self::ln($uResult); $realMultiplier = -0.5; $imaginaryMultiplier = 0.5; if (abs($uResult->getImaginary()) === M_PI) { // If we have an imaginary value at the max or min (PI or -PI), then we need to ensure // that the primary is assigned for the correct quadrant. $realMultiplier = ( ($uResult->getImaginary() === M_PI && $uResult->getReal() > 0.0) || ($uResult->getImaginary() === -M_PI && $uResult->getReal() < 0.0) ) ? 0.5 : -0.5; } return new Complex( $uResult->getImaginary() * $realMultiplier, $uResult->getReal() * $imaginaryMultiplier, $complex->getSuffix() ); } /** * Returns the inverse hyperbolic tangent of a complex number. * * Formula from Wolfram Alpha: * tanh^(-1)z = 1/2 [ln(1 + z) - ln(1 - z)]. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse hyperbolic tangent of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function atanh($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->isReal()) { $real = $complex->getReal(); if ($real >= -1.0 && $real <= 1.0) { return new Complex(\atanh($real)); } else { return new Complex(\atanh(1 / $real), (($real < 0.0) ? M_PI_2 : -1 * M_PI_2)); } } $atanh = Operations::multiply( Operations::subtract( self::ln(Operations::add(1.0, $complex)), self::ln(Operations::subtract(1.0, $complex)) ), 0.5 ); return $atanh; } /** * Returns the complex conjugate of a complex number * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The conjugate of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function conjugate($complex): Complex { $complex = Complex::validateComplexArgument($complex); return new Complex( $complex->getReal(), -1 * $complex->getImaginary(), $complex->getSuffix() ); } /** * Returns the cosine of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The cosine of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function cos($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->isReal()) { return new Complex(\cos($complex->getReal())); } return self::conjugate( new Complex( \cos($complex->getReal()) * \cosh($complex->getImaginary()), \sin($complex->getReal()) * \sinh($complex->getImaginary()), $complex->getSuffix() ) ); } /** * Returns the hyperbolic cosine of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The hyperbolic cosine of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function cosh($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->isReal()) { return new Complex(\cosh($complex->getReal())); } return new Complex( \cosh($complex->getReal()) * \cos($complex->getImaginary()), \sinh($complex->getReal()) * \sin($complex->getImaginary()), $complex->getSuffix() ); } /** * Returns the cotangent of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The cotangent of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function cot($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { return new Complex(INF); } return self::inverse(self::tan($complex)); } /** * Returns the hyperbolic cotangent of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The hyperbolic cotangent of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function coth($complex): Complex { $complex = Complex::validateComplexArgument($complex); return self::inverse(self::tanh($complex)); } /** * Returns the cosecant of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The cosecant of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function csc($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { return new Complex(INF); } return self::inverse(self::sin($complex)); } /** * Returns the hyperbolic cosecant of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The hyperbolic cosecant of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function csch($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { return new Complex(INF); } return self::inverse(self::sinh($complex)); } /** * Returns the exponential of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The exponential of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function exp($complex): Complex { $complex = Complex::validateComplexArgument($complex); if (($complex->getReal() == 0.0) && (\abs($complex->getImaginary()) == M_PI)) { return new Complex(-1.0, 0.0); } $rho = \exp($complex->getReal()); return new Complex( $rho * \cos($complex->getImaginary()), $rho * \sin($complex->getImaginary()), $complex->getSuffix() ); } /** * Returns the inverse of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws InvalidArgumentException If function would result in a division by zero */ public static function inverse($complex): Complex { $complex = clone Complex::validateComplexArgument($complex); if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { throw new InvalidArgumentException('Division by zero'); } return $complex->divideInto(1.0); } /** * Returns the natural logarithm of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The natural logarithm of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws InvalidArgumentException If the real and the imaginary parts are both zero */ public static function ln($complex): Complex { $complex = Complex::validateComplexArgument($complex); if (($complex->getReal() == 0.0) && ($complex->getImaginary() == 0.0)) { throw new InvalidArgumentException(); } return new Complex( \log(self::rho($complex)), self::theta($complex), $complex->getSuffix() ); } /** * Returns the base-2 logarithm of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The base-2 logarithm of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws InvalidArgumentException If the real and the imaginary parts are both zero */ public static function log2($complex): Complex { $complex = Complex::validateComplexArgument($complex); if (($complex->getReal() == 0.0) && ($complex->getImaginary() == 0.0)) { throw new InvalidArgumentException(); } elseif (($complex->getReal() > 0.0) && ($complex->getImaginary() == 0.0)) { return new Complex(\log($complex->getReal(), 2), 0.0, $complex->getSuffix()); } return self::ln($complex) ->multiply(\log(Complex::EULER, 2)); } /** * Returns the common logarithm (base 10) of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The common logarithm (base 10) of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws InvalidArgumentException If the real and the imaginary parts are both zero */ public static function log10($complex): Complex { $complex = Complex::validateComplexArgument($complex); if (($complex->getReal() == 0.0) && ($complex->getImaginary() == 0.0)) { throw new InvalidArgumentException(); } elseif (($complex->getReal() > 0.0) && ($complex->getImaginary() == 0.0)) { return new Complex(\log10($complex->getReal()), 0.0, $complex->getSuffix()); } return self::ln($complex) ->multiply(\log10(Complex::EULER)); } /** * Returns the negative of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The negative value of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * * @see rho * */ public static function negative($complex): Complex { $complex = Complex::validateComplexArgument($complex); return new Complex( -1 * $complex->getReal(), -1 * $complex->getImaginary(), $complex->getSuffix() ); } /** * Returns a complex number raised to a power. * * @param Complex|mixed $complex Complex number or a numeric value. * @param float|integer $power The power to raise this value to * @return Complex The complex argument raised to the real power. * @throws Exception If the power argument isn't a valid real */ public static function pow($complex, $power): Complex { $complex = Complex::validateComplexArgument($complex); if (!is_numeric($power)) { throw new Exception('Power argument must be a real number'); } if ($complex->getImaginary() == 0.0 && $complex->getReal() >= 0.0) { return new Complex(\pow($complex->getReal(), $power)); } $rValue = \sqrt(($complex->getReal() * $complex->getReal()) + ($complex->getImaginary() * $complex->getImaginary())); $rPower = \pow($rValue, $power); $theta = $complex->argument() * $power; if ($theta == 0) { return new Complex(1); } return new Complex($rPower * \cos($theta), $rPower * \sin($theta), $complex->getSuffix()); } /** * Returns the rho of a complex number. * This is the distance/radius from the centrepoint to the representation of the number in polar coordinates. * * @param Complex|mixed $complex Complex number or a numeric value. * @return float The rho value of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function rho($complex): float { $complex = Complex::validateComplexArgument($complex); return \sqrt( ($complex->getReal() * $complex->getReal()) + ($complex->getImaginary() * $complex->getImaginary()) ); } /** * Returns the secant of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The secant of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function sec($complex): Complex { $complex = Complex::validateComplexArgument($complex); return self::inverse(self::cos($complex)); } /** * Returns the hyperbolic secant of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The hyperbolic secant of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function sech($complex): Complex { $complex = Complex::validateComplexArgument($complex); return self::inverse(self::cosh($complex)); } /** * Returns the sine of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The sine of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function sin($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->isReal()) { return new Complex(\sin($complex->getReal())); } return new Complex( \sin($complex->getReal()) * \cosh($complex->getImaginary()), \cos($complex->getReal()) * \sinh($complex->getImaginary()), $complex->getSuffix() ); } /** * Returns the hyperbolic sine of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The hyperbolic sine of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function sinh($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->isReal()) { return new Complex(\sinh($complex->getReal())); } return new Complex( \sinh($complex->getReal()) * \cos($complex->getImaginary()), \cosh($complex->getReal()) * \sin($complex->getImaginary()), $complex->getSuffix() ); } /** * Returns the square root of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The Square root of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function sqrt($complex): Complex { $complex = Complex::validateComplexArgument($complex); $theta = self::theta($complex); $delta1 = \cos($theta / 2); $delta2 = \sin($theta / 2); $rho = \sqrt(self::rho($complex)); return new Complex($delta1 * $rho, $delta2 * $rho, $complex->getSuffix()); } /** * Returns the tangent of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The tangent of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws InvalidArgumentException If function would result in a division by zero */ public static function tan($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->isReal()) { return new Complex(\tan($complex->getReal())); } $real = $complex->getReal(); $imaginary = $complex->getImaginary(); $divisor = 1 + \pow(\tan($real), 2) * \pow(\tanh($imaginary), 2); if ($divisor == 0.0) { throw new InvalidArgumentException('Division by zero'); } return new Complex( \pow(self::sech($imaginary)->getReal(), 2) * \tan($real) / $divisor, \pow(self::sec($real)->getReal(), 2) * \tanh($imaginary) / $divisor, $complex->getSuffix() ); } /** * Returns the hyperbolic tangent of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The hyperbolic tangent of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function tanh($complex): Complex { $complex = Complex::validateComplexArgument($complex); $real = $complex->getReal(); $imaginary = $complex->getImaginary(); $divisor = \cos($imaginary) * \cos($imaginary) + \sinh($real) * \sinh($real); if ($divisor == 0.0) { throw new InvalidArgumentException('Division by zero'); } return new Complex( \sinh($real) * \cosh($real) / $divisor, 0.5 * \sin(2 * $imaginary) / $divisor, $complex->getSuffix() ); } /** * Returns the theta of a complex number. * This is the angle in radians from the real axis to the representation of the number in polar coordinates. * * @param Complex|mixed $complex Complex number or a numeric value. * @return float The theta value of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function theta($complex): float { $complex = Complex::validateComplexArgument($complex); if ($complex->getReal() == 0.0) { if ($complex->isReal()) { return 0.0; } elseif ($complex->getImaginary() < 0.0) { return M_PI / -2; } return M_PI / 2; } elseif ($complex->getReal() > 0.0) { return \atan($complex->getImaginary() / $complex->getReal()); } elseif ($complex->getImaginary() < 0.0) { return -(M_PI - \atan(\abs($complex->getImaginary()) / \abs($complex->getReal()))); } return M_PI - \atan($complex->getImaginary() / \abs($complex->getReal())); } } PK!4libraries/vendor/markbaker/complex/classes/index.phpnu[ * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace TablePress\Composer\Pcre; final class MatchWithOffsetsResult { /** * An array of match group => pair of string matched + offset in bytes (or -1 if no match) * * @readonly * @var array * @phpstan-var array}> */ public $matches; /** * @readonly * @var bool */ public $matched; /** * @param 0|positive-int $count * @param array $matches * @phpstan-param array}> $matches */ public function __construct(int $count, array $matches) { $this->matches = $matches; $this->matched = (bool) $count; } } PK!W?libraries/vendor/composer/pcre/UnexpectedNullMatchException.phpnu[ * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace TablePress\Composer\Pcre; class UnexpectedNullMatchException extends PcreException { public static function fromFunction($function, $pattern) { throw new \LogicException('fromFunction should not be called on '.self::class.', use '.PcreException::class); } } PK!#۸.libraries/vendor/composer/pcre/MatchResult.phpnu[ * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace TablePress\Composer\Pcre; final class MatchResult { /** * An array of match group => string matched * * @readonly * @var array */ public $matches; /** * @readonly * @var bool */ public $matched; /** * @param 0|positive-int $count * @param array $matches */ public function __construct(int $count, array $matches) { $this->matches = $matches; $this->matched = (bool) $count; } } PK!;O(libraries/vendor/composer/pcre/Regex.phpnu[ * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace TablePress\Composer\Pcre; class Regex { /** * @param non-empty-string $pattern */ public static function isMatch(string $pattern, string $subject, int $offset = 0): bool { return (bool) Preg::match($pattern, $subject, $matches, 0, $offset); } /** * @param non-empty-string $pattern * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported */ public static function match(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchResult { self::checkOffsetCapture($flags, 'matchWithOffsets'); $count = Preg::match($pattern, $subject, $matches, $flags, $offset); return new MatchResult($count, $matches); } /** * Variant of `match()` which returns non-null matches (or throws) * * @param non-empty-string $pattern * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported * @throws UnexpectedNullMatchException */ public static function matchStrictGroups(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchStrictGroupsResult { // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups $count = Preg::matchStrictGroups($pattern, $subject, $matches, $flags, $offset); return new MatchStrictGroupsResult($count, $matches); } /** * Runs preg_match with PREG_OFFSET_CAPTURE * * @param non-empty-string $pattern * @param int-mask $flags PREG_UNMATCHED_AS_NULL and PREG_MATCH_OFFSET are always set, no other flags are supported */ public static function matchWithOffsets(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchWithOffsetsResult { $count = Preg::matchWithOffsets($pattern, $subject, $matches, $flags, $offset); return new MatchWithOffsetsResult($count, $matches); } /** * @param non-empty-string $pattern * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported */ public static function matchAll(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchAllResult { self::checkOffsetCapture($flags, 'matchAllWithOffsets'); self::checkSetOrder($flags); $count = Preg::matchAll($pattern, $subject, $matches, $flags, $offset); return new MatchAllResult($count, $matches); } /** * Variant of `matchAll()` which returns non-null matches (or throws) * * @param non-empty-string $pattern * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported * @throws UnexpectedNullMatchException */ public static function matchAllStrictGroups(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchAllStrictGroupsResult { self::checkOffsetCapture($flags, 'matchAllWithOffsets'); self::checkSetOrder($flags); // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups $count = Preg::matchAllStrictGroups($pattern, $subject, $matches, $flags, $offset); return new MatchAllStrictGroupsResult($count, $matches); } /** * Runs preg_match_all with PREG_OFFSET_CAPTURE * * @param non-empty-string $pattern * @param int-mask $flags PREG_UNMATCHED_AS_NULL and PREG_MATCH_OFFSET are always set, no other flags are supported */ public static function matchAllWithOffsets(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchAllWithOffsetsResult { self::checkSetOrder($flags); $count = Preg::matchAllWithOffsets($pattern, $subject, $matches, $flags, $offset); return new MatchAllWithOffsetsResult($count, $matches); } /** * @param string|string[] $pattern * @param string|string[] $replacement * @param string $subject */ public static function replace($pattern, $replacement, $subject, int $limit = -1): ReplaceResult { $result = Preg::replace($pattern, $replacement, $subject, $limit, $count); return new ReplaceResult($count, $result); } /** * @param string|string[] $pattern * @param ($flags is PREG_OFFSET_CAPTURE ? (callable(array}>): string) : callable(array): string) $replacement * @param string $subject * @param int-mask $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set */ public static function replaceCallback($pattern, callable $replacement, $subject, int $limit = -1, int $flags = 0): ReplaceResult { $result = Preg::replaceCallback($pattern, $replacement, $subject, $limit, $count, $flags); return new ReplaceResult($count, $result); } /** * Variant of `replaceCallback()` which outputs non-null matches (or throws) * * @param string $pattern * @param ($flags is PREG_OFFSET_CAPTURE ? (callable(array}>): string) : callable(array): string) $replacement * @param string $subject * @param int-mask $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set */ public static function replaceCallbackStrictGroups($pattern, callable $replacement, $subject, int $limit = -1, int $flags = 0): ReplaceResult { $result = Preg::replaceCallbackStrictGroups($pattern, $replacement, $subject, $limit, $count, $flags); return new ReplaceResult($count, $result); } /** * @param ($flags is PREG_OFFSET_CAPTURE ? (array}>): string>) : array): string>) $pattern * @param string $subject * @param int-mask $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set */ public static function replaceCallbackArray(array $pattern, $subject, int $limit = -1, int $flags = 0): ReplaceResult { $result = Preg::replaceCallbackArray($pattern, $subject, $limit, $count, $flags); return new ReplaceResult($count, $result); } private static function checkOffsetCapture(int $flags, string $useFunctionName): void { if (($flags & PREG_OFFSET_CAPTURE) !== 0) { throw new \InvalidArgumentException('PREG_OFFSET_CAPTURE is not supported as it changes the return type, use '.$useFunctionName.'() instead'); } } private static function checkSetOrder(int $flags): void { if (($flags & PREG_SET_ORDER) !== 0) { throw new \InvalidArgumentException('PREG_SET_ORDER is not supported as it changes the return type'); } } } PK!*DD<libraries/vendor/composer/pcre/MatchAllWithOffsetsResult.phpnu[ * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace TablePress\Composer\Pcre; final class MatchAllWithOffsetsResult { /** * An array of match group => list of matches, every match being a pair of string matched + offset in bytes (or -1 if no match) * * @readonly * @var array> * @phpstan-var array}>> */ public $matches; /** * @readonly * @var 0|positive-int */ public $count; /** * @readonly * @var bool */ public $matched; /** * @param 0|positive-int $count * @param array> $matches * @phpstan-param array}>> $matches */ public function __construct(int $count, array $matches) { $this->matches = $matches; $this->matched = (bool) $count; $this->count = $count; } } PK!fF0libraries/vendor/composer/pcre/PcreException.phpnu[ * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace TablePress\Composer\Pcre; class PcreException extends \RuntimeException { /** * @param string $function * @param string|string[] $pattern * @return self */ public static function fromFunction($function, $pattern) { $code = preg_last_error(); if (is_array($pattern)) { $pattern = implode(', ', $pattern); } return new PcreException($function.'(): failed executing "'.$pattern.'": '.self::pcreLastErrorMessage($code), $code); } /** * @param int $code * @return string */ private static function pcreLastErrorMessage($code) { if (function_exists('preg_last_error_msg')) { return preg_last_error_msg(); } $constants = get_defined_constants(true); if (!isset($constants['pcre']) || !is_array($constants['pcre'])) { return 'UNDEFINED_ERROR'; } foreach ($constants['pcre'] as $const => $val) { if ($val === $code && substr($const, -6) === '_ERROR') { return $const; } } return 'UNDEFINED_ERROR'; } } PK!(libraries/vendor/composer/pcre/index.phpnu[>'libraries/vendor/composer/pcre/Preg.phpnu[ * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace TablePress\Composer\Pcre; class Preg { /** @internal */ public const ARRAY_MSG = '$subject as an array is not supported. You can use \'foreach\' instead.'; /** @internal */ public const INVALID_TYPE_MSG = '$subject must be a string, %s given.'; /** * @param non-empty-string $pattern * @param array $matches Set by method * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported * @return 0|1 * * @param-out array $matches */ public static function match(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int { self::checkOffsetCapture($flags, 'matchWithOffsets'); $result = preg_match($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL, $offset); if ($result === false) { throw PcreException::fromFunction('preg_match', $pattern); } return $result; } /** * Variant of `match()` which outputs non-null matches (or throws) * * @param non-empty-string $pattern * @param array $matches Set by method * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported * @return 0|1 * @throws UnexpectedNullMatchException * * @param-out array $matches */ public static function matchStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int { $result = self::match($pattern, $subject, $matchesInternal, $flags, $offset); $matches = self::enforceNonNullMatches($pattern, $matchesInternal, 'match'); return $result; } /** * Runs preg_match with PREG_OFFSET_CAPTURE * * @param non-empty-string $pattern * @param array $matches Set by method * @param int-mask $flags PREG_UNMATCHED_AS_NULL and PREG_OFFSET_CAPTURE are always set, no other flags are supported * @return 0|1 * * @param-out array}> $matches */ public static function matchWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): int { $result = preg_match($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL | PREG_OFFSET_CAPTURE, $offset); if ($result === false) { throw PcreException::fromFunction('preg_match', $pattern); } return $result; } /** * @param non-empty-string $pattern * @param array $matches Set by method * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported * @return 0|positive-int * * @param-out array> $matches */ public static function matchAll(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int { self::checkOffsetCapture($flags, 'matchAllWithOffsets'); self::checkSetOrder($flags); $result = preg_match_all($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL, $offset); if (!is_int($result)) { // PHP < 8 may return null, 8+ returns int|false throw PcreException::fromFunction('preg_match_all', $pattern); } return $result; } /** * Variant of `match()` which outputs non-null matches (or throws) * * @param non-empty-string $pattern * @param array $matches Set by method * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported * @return 0|positive-int * @throws UnexpectedNullMatchException * * @param-out array> $matches */ public static function matchAllStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int { $result = self::matchAll($pattern, $subject, $matchesInternal, $flags, $offset); $matches = self::enforceNonNullMatchAll($pattern, $matchesInternal, 'matchAll'); return $result; } /** * Runs preg_match_all with PREG_OFFSET_CAPTURE * * @param non-empty-string $pattern * @param array $matches Set by method * @param int-mask $flags PREG_UNMATCHED_AS_NULL and PREG_MATCH_OFFSET are always set, no other flags are supported * @return 0|positive-int * * @param-out array}>> $matches */ public static function matchAllWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): int { self::checkSetOrder($flags); $result = preg_match_all($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL | PREG_OFFSET_CAPTURE, $offset); if (!is_int($result)) { // PHP < 8 may return null, 8+ returns int|false throw PcreException::fromFunction('preg_match_all', $pattern); } return $result; } /** * @param string|string[] $pattern * @param string|string[] $replacement * @param string $subject * @param int $count Set by method * * @param-out int<0, max> $count */ public static function replace($pattern, $replacement, $subject, int $limit = -1, ?int &$count = null): string { if (!is_scalar($subject)) { if (is_array($subject)) { throw new \InvalidArgumentException(static::ARRAY_MSG); } throw new \TypeError(sprintf(static::INVALID_TYPE_MSG, gettype($subject))); } $result = preg_replace($pattern, $replacement, $subject, $limit, $count); if ($result === null) { throw PcreException::fromFunction('preg_replace', $pattern); } return $result; } /** * @param string|string[] $pattern * @param ($flags is PREG_OFFSET_CAPTURE ? (callable(array}>): string) : callable(array): string) $replacement * @param string $subject * @param int $count Set by method * @param int-mask $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set * * @param-out int<0, max> $count */ public static function replaceCallback($pattern, callable $replacement, $subject, int $limit = -1, ?int &$count = null, int $flags = 0): string { if (!is_scalar($subject)) { if (is_array($subject)) { throw new \InvalidArgumentException(static::ARRAY_MSG); } throw new \TypeError(sprintf(static::INVALID_TYPE_MSG, gettype($subject))); } $result = preg_replace_callback($pattern, $replacement, $subject, $limit, $count, $flags | PREG_UNMATCHED_AS_NULL); if ($result === null) { throw PcreException::fromFunction('preg_replace_callback', $pattern); } return $result; } /** * Variant of `replaceCallback()` which outputs non-null matches (or throws) * * @param string $pattern * @param ($flags is PREG_OFFSET_CAPTURE ? (callable(array}>): string) : callable(array): string) $replacement * @param string $subject * @param int $count Set by method * @param int-mask $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set * * @param-out int<0, max> $count */ public static function replaceCallbackStrictGroups(string $pattern, callable $replacement, $subject, int $limit = -1, ?int &$count = null, int $flags = 0): string { return self::replaceCallback($pattern, function (array $matches) use ($pattern, $replacement) { return $replacement(self::enforceNonNullMatches($pattern, $matches, 'replaceCallback')); }, $subject, $limit, $count, $flags); } /** * @param ($flags is PREG_OFFSET_CAPTURE ? (array}>): string>) : array): string>) $pattern * @param string $subject * @param int $count Set by method * @param int-mask $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set * * @param-out int<0, max> $count */ public static function replaceCallbackArray(array $pattern, $subject, int $limit = -1, ?int &$count = null, int $flags = 0): string { if (!is_scalar($subject)) { if (is_array($subject)) { throw new \InvalidArgumentException(static::ARRAY_MSG); } throw new \TypeError(sprintf(static::INVALID_TYPE_MSG, gettype($subject))); } $result = preg_replace_callback_array($pattern, $subject, $limit, $count, $flags | PREG_UNMATCHED_AS_NULL); if ($result === null) { $pattern = array_keys($pattern); throw PcreException::fromFunction('preg_replace_callback_array', $pattern); } return $result; } /** * @param int-mask $flags PREG_SPLIT_NO_EMPTY or PREG_SPLIT_DELIM_CAPTURE * @return list */ public static function split(string $pattern, string $subject, int $limit = -1, int $flags = 0): array { if (($flags & PREG_SPLIT_OFFSET_CAPTURE) !== 0) { throw new \InvalidArgumentException('PREG_SPLIT_OFFSET_CAPTURE is not supported as it changes the type of $matches, use splitWithOffsets() instead'); } $result = preg_split($pattern, $subject, $limit, $flags); if ($result === false) { throw PcreException::fromFunction('preg_split', $pattern); } return $result; } /** * @param int-mask $flags PREG_SPLIT_NO_EMPTY or PREG_SPLIT_DELIM_CAPTURE, PREG_SPLIT_OFFSET_CAPTURE is always set * @return list * @phpstan-return list}> */ public static function splitWithOffsets(string $pattern, string $subject, int $limit = -1, int $flags = 0): array { $result = preg_split($pattern, $subject, $limit, $flags | PREG_SPLIT_OFFSET_CAPTURE); if ($result === false) { throw PcreException::fromFunction('preg_split', $pattern); } return $result; } /** * @template T of string|\Stringable * @param string $pattern * @param array $array * @param int-mask $flags PREG_GREP_INVERT * @return array */ public static function grep(string $pattern, array $array, int $flags = 0): array { $result = preg_grep($pattern, $array, $flags); if ($result === false) { throw PcreException::fromFunction('preg_grep', $pattern); } return $result; } /** * Variant of match() which returns a bool instead of int * * @param non-empty-string $pattern * @param array $matches Set by method * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported * * @param-out array $matches */ public static function isMatch(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool { return (bool) static::match($pattern, $subject, $matches, $flags, $offset); } /** * Variant of `isMatch()` which outputs non-null matches (or throws) * * @param non-empty-string $pattern * @param array $matches Set by method * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported * @throws UnexpectedNullMatchException * * @param-out array $matches */ public static function isMatchStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool { return (bool) self::matchStrictGroups($pattern, $subject, $matches, $flags, $offset); } /** * Variant of matchAll() which returns a bool instead of int * * @param non-empty-string $pattern * @param array $matches Set by method * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported * * @param-out array> $matches */ public static function isMatchAll(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool { return (bool) static::matchAll($pattern, $subject, $matches, $flags, $offset); } /** * Variant of `isMatchAll()` which outputs non-null matches (or throws) * * @param non-empty-string $pattern * @param array $matches Set by method * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported * * @param-out array> $matches */ public static function isMatchAllStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool { return (bool) self::matchAllStrictGroups($pattern, $subject, $matches, $flags, $offset); } /** * Variant of matchWithOffsets() which returns a bool instead of int * * Runs preg_match with PREG_OFFSET_CAPTURE * * @param non-empty-string $pattern * @param array $matches Set by method * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported * * @param-out array}> $matches */ public static function isMatchWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): bool { return (bool) static::matchWithOffsets($pattern, $subject, $matches, $flags, $offset); } /** * Variant of matchAllWithOffsets() which returns a bool instead of int * * Runs preg_match_all with PREG_OFFSET_CAPTURE * * @param non-empty-string $pattern * @param array $matches Set by method * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported * * @param-out array}>> $matches */ public static function isMatchAllWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): bool { return (bool) static::matchAllWithOffsets($pattern, $subject, $matches, $flags, $offset); } private static function checkOffsetCapture(int $flags, string $useFunctionName): void { if (($flags & PREG_OFFSET_CAPTURE) !== 0) { throw new \InvalidArgumentException('PREG_OFFSET_CAPTURE is not supported as it changes the type of $matches, use ' . $useFunctionName . '() instead'); } } private static function checkSetOrder(int $flags): void { if (($flags & PREG_SET_ORDER) !== 0) { throw new \InvalidArgumentException('PREG_SET_ORDER is not supported as it changes the type of $matches'); } } /** * @param array $matches * @return array * @throws UnexpectedNullMatchException */ private static function enforceNonNullMatches(string $pattern, array $matches, string $variantMethod) { foreach ($matches as $group => $match) { if (is_string($match) || (is_array($match) && is_string($match[0]))) { continue; } throw new UnexpectedNullMatchException('Pattern "'.$pattern.'" had an unexpected unmatched group "'.$group.'", make sure the pattern always matches or use '.$variantMethod.'() instead.'); } /** @var array */ return $matches; } /** * @param array> $matches * @return array> * @throws UnexpectedNullMatchException */ private static function enforceNonNullMatchAll(string $pattern, array $matches, string $variantMethod) { foreach ($matches as $group => $groupMatches) { foreach ($groupMatches as $match) { if (null === $match) { throw new UnexpectedNullMatchException('Pattern "'.$pattern.'" had an unexpected unmatched group "'.$group.'", make sure the pattern always matches or use '.$variantMethod.'() instead.'); } } } /** @var array> */ return $matches; } } PK!B:libraries/vendor/composer/pcre/MatchStrictGroupsResult.phpnu[ * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace TablePress\Composer\Pcre; final class MatchStrictGroupsResult { /** * An array of match group => string matched * * @readonly * @var array */ public $matches; /** * @readonly * @var bool */ public $matched; /** * @param 0|positive-int $count * @param array $matches */ public function __construct(int $count, array $matches) { $this->matches = $matches; $this->matched = (bool) $count; } } PK!o{ { Blibraries/vendor/composer/pcre/PHPStan/InvalidRegexPatternRule.phpnu[ */ class InvalidRegexPatternRule implements Rule { public function getNodeType(): string { return StaticCall::class; } public function processNode(Node $node, Scope $scope): array { $patterns = $this->extractPatterns($node, $scope); $errors = []; foreach ($patterns as $pattern) { $errorMessage = $this->validatePattern($pattern); if ($errorMessage === null) { continue; } $errors[] = RuleErrorBuilder::message(sprintf('Regex pattern is invalid: %s', $errorMessage))->identifier('regexp.pattern')->build(); } return $errors; } /** * @return string[] */ private function extractPatterns(StaticCall $node, Scope $scope): array { if (!$node->class instanceof FullyQualified) { return []; } $isRegex = $node->class->toString() === Regex::class; $isPreg = $node->class->toString() === Preg::class; if (!$isRegex && !$isPreg) { return []; } if (!$node->name instanceof Node\Identifier || !Preg::isMatch('{^(match|isMatch|grep|replace|split)}', $node->name->name)) { return []; } $functionName = $node->name->name; if (!isset($node->getArgs()[0])) { return []; } $patternNode = $node->getArgs()[0]->value; $patternType = $scope->getType($patternNode); $patternStrings = []; foreach ($patternType->getConstantStrings() as $constantStringType) { if ($functionName === 'replaceCallbackArray') { continue; } $patternStrings[] = $constantStringType->getValue(); } foreach ($patternType->getConstantArrays() as $constantArrayType) { if ( in_array($functionName, [ 'replace', 'replaceCallback', ], true) ) { foreach ($constantArrayType->getValueTypes() as $arrayKeyType) { foreach ($arrayKeyType->getConstantStrings() as $constantString) { $patternStrings[] = $constantString->getValue(); } } } if ($functionName !== 'replaceCallbackArray') { continue; } foreach ($constantArrayType->getKeyTypes() as $arrayKeyType) { foreach ($arrayKeyType->getConstantStrings() as $constantString) { $patternStrings[] = $constantString->getValue(); } } } return $patternStrings; } private function validatePattern(string $pattern): ?string { try { $msg = null; $prev = set_error_handler(function (int $severity, string $message, string $file) use (&$msg): bool { $msg = preg_replace("#^preg_match(_all)?\\(.*?\\): #", '', $message); return true; }); if ($pattern === '') { return 'Empty string is not a valid regular expression'; } Preg::match($pattern, ''); if ($msg !== null) { return $msg; } } catch (PcreException $e) { if ($e->getCode() === PREG_INTERNAL_ERROR && $msg !== null) { return $msg; } return preg_replace('{.*? failed executing ".*": }', '', $e->getMessage()); } finally { restore_error_handler(); } return null; } } PK!Ce Elibraries/vendor/composer/pcre/PHPStan/UnsafeStrictGroupsCallRule.phpnu[ */ final class UnsafeStrictGroupsCallRule implements Rule { /** * @var RegexArrayShapeMatcher */ private $regexShapeMatcher; public function __construct(RegexArrayShapeMatcher $regexShapeMatcher) { $this->regexShapeMatcher = $regexShapeMatcher; } public function getNodeType(): string { return StaticCall::class; } public function processNode(Node $node, Scope $scope): array { if (!$node->class instanceof FullyQualified) { return []; } $isRegex = $node->class->toString() === Regex::class; $isPreg = $node->class->toString() === Preg::class; if (!$isRegex && !$isPreg) { return []; } if (!$node->name instanceof Node\Identifier || !in_array($node->name->name, ['matchStrictGroups', 'isMatchStrictGroups', 'matchAllStrictGroups', 'isMatchAllStrictGroups'], true)) { return []; } $args = $node->getArgs(); if (!isset($args[0])) { return []; } $patternArg = $args[0] ?? null; if ($isPreg) { if (!isset($args[2])) { // no matches set, skip as the matches won't be used anyway return []; } $flagsArg = $args[3] ?? null; } else { $flagsArg = $args[2] ?? null; } if ($patternArg === null) { return []; } $flagsType = PregMatchFlags::getType($flagsArg, $scope); if ($flagsType === null) { return []; } $matchedType = $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createYes(), $scope); if ($matchedType === null) { return [ RuleErrorBuilder::message(sprintf('The %s call is potentially unsafe as $matches\' type could not be inferred.', $node->name->name)) ->identifier('composerPcre.maybeUnsafeStrictGroups') ->build(), ]; } if (count($matchedType->getConstantArrays()) === 1) { $matchedType = $matchedType->getConstantArrays()[0]; $nullableGroups = []; foreach ($matchedType->getValueTypes() as $index => $type) { if (TypeCombinator::containsNull($type)) { $nullableGroups[] = $matchedType->getKeyTypes()[$index]->getValue(); } } if (\count($nullableGroups) > 0) { return [ RuleErrorBuilder::message(sprintf( 'The %s call is unsafe as match group%s "%s" %s optional and may be null.', $node->name->name, \count($nullableGroups) > 1 ? 's' : '', implode('", "', $nullableGroups), \count($nullableGroups) > 1 ? 'are' : 'is' ))->identifier('composerPcre.unsafeStrictGroups')->build(), ]; } } return []; } } PK!CߜL L Klibraries/vendor/composer/pcre/PHPStan/PregMatchTypeSpecifyingExtension.phpnu[regexShapeMatcher = $regexShapeMatcher; } public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void { $this->typeSpecifier = $typeSpecifier; } public function getClass(): string { return Preg::class; } public function isStaticMethodSupported(MethodReflection $methodReflection, StaticCall $node, TypeSpecifierContext $context): bool { return in_array($methodReflection->getName(), [ 'match', 'isMatch', 'matchStrictGroups', 'isMatchStrictGroups', 'matchAll', 'isMatchAll', 'matchAllStrictGroups', 'isMatchAllStrictGroups' ], true) && !$context->null(); } public function specifyTypes(MethodReflection $methodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes { $args = $node->getArgs(); $patternArg = $args[0] ?? null; $matchesArg = $args[2] ?? null; $flagsArg = $args[3] ?? null; if ( $patternArg === null || $matchesArg === null ) { return new SpecifiedTypes(); } $flagsType = PregMatchFlags::getType($flagsArg, $scope); if ($flagsType === null) { return new SpecifiedTypes(); } if (stripos($methodReflection->getName(), 'matchAll') !== false) { $matchedType = $this->regexShapeMatcher->matchAllExpr($patternArg->value, $flagsType, TrinaryLogic::createFromBoolean($context->true()), $scope); } else { $matchedType = $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createFromBoolean($context->true()), $scope); } if ($matchedType === null) { return new SpecifiedTypes(); } if ( in_array($methodReflection->getName(), ['matchStrictGroups', 'isMatchStrictGroups', 'matchAllStrictGroups', 'isMatchAllStrictGroups'], true) ) { $matchedType = PregMatchFlags::removeNullFromMatches($matchedType); } $overwrite = false; if ($context->false()) { $overwrite = true; $context = $context->negate(); } // @phpstan-ignore function.alreadyNarrowedType if (method_exists('PHPStan\Analyser\SpecifiedTypes', 'setRootExpr')) { $typeSpecifier = $this->typeSpecifier->create( $matchesArg->value, $matchedType, $context, $scope )->setRootExpr($node); return $overwrite ? $typeSpecifier->setAlwaysOverwriteTypes() : $typeSpecifier; } // @phpstan-ignore arguments.count return $this->typeSpecifier->create( $matchesArg->value, $matchedType, $context, // @phpstan-ignore argument.type $overwrite, $scope, $node ); } } PK!0libraries/vendor/composer/pcre/PHPStan/index.phpnu[regexShapeMatcher = $regexShapeMatcher; } public function isStaticMethodSupported(MethodReflection $methodReflection, ParameterReflection $parameter): bool { return $methodReflection->getDeclaringClass()->getName() === Preg::class && in_array($methodReflection->getName(), [ 'match', 'isMatch', 'matchStrictGroups', 'isMatchStrictGroups', 'matchAll', 'isMatchAll', 'matchAllStrictGroups', 'isMatchAllStrictGroups' ], true) && $parameter->getName() === 'matches'; } public function getParameterOutTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, ParameterReflection $parameter, Scope $scope): ?Type { $args = $methodCall->getArgs(); $patternArg = $args[0] ?? null; $matchesArg = $args[2] ?? null; $flagsArg = $args[3] ?? null; if ( $patternArg === null || $matchesArg === null ) { return null; } $flagsType = PregMatchFlags::getType($flagsArg, $scope); if ($flagsType === null) { return null; } if (stripos($methodReflection->getName(), 'matchAll') !== false) { return $this->regexShapeMatcher->matchAllExpr($patternArg->value, $flagsType, TrinaryLogic::createMaybe(), $scope); } return $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createMaybe(), $scope); } } PK!ik9libraries/vendor/composer/pcre/PHPStan/PregMatchFlags.phpnu[getType($flagsArg->value); $constantScalars = $flagsType->getConstantScalarValues(); if ($constantScalars === []) { return null; } $internalFlagsTypes = []; foreach ($flagsType->getConstantScalarValues() as $constantScalarValue) { if (!is_int($constantScalarValue)) { return null; } $internalFlagsTypes[] = new ConstantIntegerType($constantScalarValue | PREG_UNMATCHED_AS_NULL); } return TypeCombinator::union(...$internalFlagsTypes); } static public function removeNullFromMatches(Type $matchesType): Type { return TypeTraverser::map($matchesType, static function (Type $type, callable $traverse): Type { if ($type instanceof UnionType || $type instanceof IntersectionType) { return $traverse($type); } if ($type instanceof ConstantArrayType) { return new ConstantArrayType( $type->getKeyTypes(), array_map(static function (Type $valueType) use ($traverse): Type { return $traverse($valueType); }, $type->getValueTypes()), $type->getNextAutoIndexes(), [], $type->isList() ); } if ($type instanceof ArrayType) { return new ArrayType($type->getKeyType(), $traverse($type->getItemType())); } return TypeCombinator::removeNull($type); }); } } PK!; z  Rlibraries/vendor/composer/pcre/PHPStan/PregReplaceCallbackClosureTypeExtension.phpnu[regexShapeMatcher = $regexShapeMatcher; } public function isStaticMethodSupported(MethodReflection $methodReflection, ParameterReflection $parameter): bool { return in_array($methodReflection->getDeclaringClass()->getName(), [Preg::class, Regex::class], true) && in_array($methodReflection->getName(), ['replaceCallback', 'replaceCallbackStrictGroups'], true) && $parameter->getName() === 'replacement'; } public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, ParameterReflection $parameter, Scope $scope): ?Type { $args = $methodCall->getArgs(); $patternArg = $args[0] ?? null; $flagsArg = $args[5] ?? null; if ( $patternArg === null ) { return null; } $flagsType = PregMatchFlags::getType($flagsArg, $scope); $matchesType = $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createYes(), $scope); if ($matchesType === null) { return null; } if ($methodReflection->getName() === 'replaceCallbackStrictGroups' && count($matchesType->getConstantArrays()) === 1) { $matchesType = $matchesType->getConstantArrays()[0]; $matchesType = new ConstantArrayType( $matchesType->getKeyTypes(), array_map(static function (Type $valueType): Type { if (count($valueType->getConstantArrays()) === 1) { $valueTypeArray = $valueType->getConstantArrays()[0]; return new ConstantArrayType( $valueTypeArray->getKeyTypes(), array_map(static function (Type $valueType): Type { return TypeCombinator::removeNull($valueType); }, $valueTypeArray->getValueTypes()), $valueTypeArray->getNextAutoIndexes(), [], $valueTypeArray->isList() ); } return TypeCombinator::removeNull($valueType); }, $matchesType->getValueTypes()), $matchesType->getNextAutoIndexes(), [], $matchesType->isList() ); } return new ClosureType( [ new NativeParameterReflection($parameter->getName(), $parameter->isOptional(), $matchesType, $parameter->passedByReference(), $parameter->isVariadic(), $parameter->getDefaultValue()), ], new StringType() ); } } PK!0libraries/vendor/composer/pcre/ReplaceResult.phpnu[ * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace TablePress\Composer\Pcre; final class ReplaceResult { /** * @readonly * @var string */ public $result; /** * @readonly * @var 0|positive-int */ public $count; /** * @readonly * @var bool */ public $matched; /** * @param 0|positive-int $count */ public function __construct(int $count, string $result) { $this->count = $count; $this->matched = (bool) $count; $this->result = $result; } } PK!@3661libraries/vendor/composer/pcre/MatchAllResult.phpnu[ * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace TablePress\Composer\Pcre; final class MatchAllResult { /** * An array of match group => list of matched strings * * @readonly * @var array> */ public $matches; /** * @readonly * @var 0|positive-int */ public $count; /** * @readonly * @var bool */ public $matched; /** * @param 0|positive-int $count * @param array> $matches */ public function __construct(int $count, array $matches) { $this->matches = $matches; $this->matched = (bool) $count; $this->count = $count; } } PK!*(,,=libraries/vendor/composer/pcre/MatchAllStrictGroupsResult.phpnu[ * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace TablePress\Composer\Pcre; final class MatchAllStrictGroupsResult { /** * An array of match group => list of matched strings * * @readonly * @var array> */ public $matches; /** * @readonly * @var 0|positive-int */ public $count; /** * @readonly * @var bool */ public $matched; /** * @param 0|positive-int $count * @param array> $matches */ public function __construct(int $count, array $matches) { $this->matches = $matches; $this->matched = (bool) $count; $this->count = $count; } } PK!#libraries/vendor/composer/index.phpnu[ */ class Iterator implements \Iterator { /** * Spreadsheet to iterate. */ private Spreadsheet $subject; /** * Current iterator position. */ private int $position = 0; /** * Create a new worksheet iterator. */ public function __construct(Spreadsheet $subject) { // Set subject $this->subject = $subject; } /** * Rewind iterator. */ public function rewind(): void { $this->position = 0; } /** * Current Worksheet. */ public function current(): Worksheet { return $this->subject->getSheet($this->position); } /** * Current key. */ public function key(): int { return $this->position; } /** * Next value. */ public function next(): void { ++$this->position; } /** * Are there more Worksheet instances available? */ public function valid(): bool { return $this->position < $this->subject->getSheetCount() && $this->position >= 0; } } PK!~B^  9libraries/vendor/PhpSpreadsheet/Worksheet/RowIterator.phpnu[ */ class RowIterator implements NativeIterator { /** * Worksheet to iterate. */ private Worksheet $subject; /** * Current iterator position. */ private int $position = 1; /** * Start position. */ private int $startRow = 1; /** * End position. */ private int $endRow = 1; /** * Create a new row iterator. * * @param Worksheet $subject The worksheet to iterate over * @param int $startRow The row number at which to start iterating * @param ?int $endRow Optionally, the row number at which to stop iterating */ public function __construct(Worksheet $subject, int $startRow = 1, ?int $endRow = null) { // Set subject $this->subject = $subject; $this->resetEnd($endRow); $this->resetStart($startRow); } public function __destruct() { unset($this->subject); } /** * (Re)Set the start row and the current row pointer. * * @param int $startRow The row number at which to start iterating * * @return $this */ public function resetStart(int $startRow = 1) { if ($startRow > $this->subject->getHighestRow()) { throw new PhpSpreadsheetException( "Start row ({$startRow}) is beyond highest row ({$this->subject->getHighestRow()})" ); } $this->startRow = $startRow; if ($this->endRow < $this->startRow) { $this->endRow = $this->startRow; } $this->seek($startRow); return $this; } /** * (Re)Set the end row. * * @param ?int $endRow The row number at which to stop iterating * * @return $this */ public function resetEnd(?int $endRow = null) { $this->endRow = $endRow ?: $this->subject->getHighestRow(); return $this; } /** * Set the row pointer to the selected row. * * @param int $row The row number to set the current pointer at * * @return $this */ public function seek(int $row = 1) { if (($row < $this->startRow) || ($row > $this->endRow)) { throw new PhpSpreadsheetException("Row $row is out of range ({$this->startRow} - {$this->endRow})"); } $this->position = $row; return $this; } /** * Rewind the iterator to the starting row. */ public function rewind(): void { $this->position = $this->startRow; } /** * Return the current row in this worksheet. */ public function current(): Row { return new Row($this->subject, $this->position); } /** * Return the current iterator key. */ public function key(): int { return $this->position; } /** * Set the iterator to its next value. */ public function next(): void { ++$this->position; } /** * Set the iterator to its previous value. */ public function prev(): void { --$this->position; } /** * Indicate if more rows exist in the worksheet range of rows that we're iterating. */ public function valid(): bool { return $this->position <= $this->endRow && $this->position >= $this->startRow; } } PK!>libraries/vendor/PhpSpreadsheet/Worksheet/AutoFilter/index.phpnu[columnIndex = $column; $this->parent = $parent; } public function setEvaluatedFalse(): void { if ($this->parent !== null) { $this->parent->setEvaluated(false); } } /** * Get AutoFilter column index as string eg: 'A'. */ public function getColumnIndex(): string { return $this->columnIndex; } /** * Set AutoFilter column index as string eg: 'A'. * * @param string $column Column (e.g. A) * * @return $this */ public function setColumnIndex(string $column) { $this->setEvaluatedFalse(); // Uppercase coordinate $column = strtoupper($column); if ($this->parent !== null) { $this->parent->testColumnInRange($column); } $this->columnIndex = $column; return $this; } /** * Get this Column's AutoFilter Parent. */ public function getParent(): ?AutoFilter { return $this->parent; } /** * Set this Column's AutoFilter Parent. * * @return $this */ public function setParent(?AutoFilter $parent = null) { $this->setEvaluatedFalse(); $this->parent = $parent; return $this; } /** * Get AutoFilter Type. */ public function getFilterType(): string { return $this->filterType; } /** * Set AutoFilter Type. * * @return $this */ public function setFilterType(string $filterType) { $this->setEvaluatedFalse(); if (!in_array($filterType, self::$filterTypes)) { throw new PhpSpreadsheetException('Invalid filter type for column AutoFilter.'); } if ($filterType === self::AUTOFILTER_FILTERTYPE_CUSTOMFILTER && count($this->ruleset) > 2) { throw new PhpSpreadsheetException('No more than 2 rules are allowed in a Custom Filter'); } $this->filterType = $filterType; return $this; } /** * Get AutoFilter Multiple Rules And/Or Join. */ public function getJoin(): string { return $this->join; } /** * Set AutoFilter Multiple Rules And/Or. * * @param string $join And/Or * * @return $this */ public function setJoin(string $join) { $this->setEvaluatedFalse(); // Lowercase And/Or $join = strtolower($join); if (!in_array($join, self::$ruleJoins)) { throw new PhpSpreadsheetException('Invalid rule connection for column AutoFilter.'); } $this->join = $join; return $this; } /** * Set AutoFilter Attributes. * * @param (float|int|string)[] $attributes * * @return $this */ public function setAttributes(array $attributes) { $this->setEvaluatedFalse(); $this->attributes = $attributes; return $this; } /** * Set An AutoFilter Attribute. * * @param string $name Attribute Name * @param float|int|string $value Attribute Value * * @return $this */ public function setAttribute(string $name, $value) { $this->setEvaluatedFalse(); $this->attributes[$name] = $value; return $this; } /** * Get AutoFilter Column Attributes. * * @return (float|int|string)[] */ public function getAttributes(): array { return $this->attributes; } /** * Get specific AutoFilter Column Attribute. * * @param string $name Attribute Name * @return float|int|string|null */ public function getAttribute(string $name) { if (isset($this->attributes[$name])) { return $this->attributes[$name]; } return null; } public function ruleCount(): int { return count($this->ruleset); } /** * Get all AutoFilter Column Rules. * * @return Column\Rule[] */ public function getRules(): array { return $this->ruleset; } /** * Get a specified AutoFilter Column Rule. * * @param int $index Rule index in the ruleset array */ public function getRule(int $index): Column\Rule { if (!isset($this->ruleset[$index])) { $this->ruleset[$index] = new Column\Rule($this); } return $this->ruleset[$index]; } /** * Create a new AutoFilter Column Rule in the ruleset. */ public function createRule(): Column\Rule { $this->setEvaluatedFalse(); if ($this->filterType === self::AUTOFILTER_FILTERTYPE_CUSTOMFILTER && count($this->ruleset) >= 2) { throw new PhpSpreadsheetException('No more than 2 rules are allowed in a Custom Filter'); } $this->ruleset[] = new Column\Rule($this); return end($this->ruleset); } /** * Add a new AutoFilter Column Rule to the ruleset. * * @return $this */ public function addRule(Column\Rule $rule) { $this->setEvaluatedFalse(); $rule->setParent($this); $this->ruleset[] = $rule; return $this; } /** * Delete a specified AutoFilter Column Rule * If the number of rules is reduced to 1, then we reset And/Or logic to Or. * * @param int $index Rule index in the ruleset array * * @return $this */ public function deleteRule(int $index) { $this->setEvaluatedFalse(); if (isset($this->ruleset[$index])) { unset($this->ruleset[$index]); // If we've just deleted down to a single rule, then reset And/Or joining to Or if (count($this->ruleset) <= 1) { $this->setJoin(self::AUTOFILTER_COLUMN_JOIN_OR); } } return $this; } /** * Delete all AutoFilter Column Rules. * * @return $this */ public function clearRules() { $this->setEvaluatedFalse(); $this->ruleset = []; $this->setJoin(self::AUTOFILTER_COLUMN_JOIN_OR); return $this; } /** * Implement PHP __clone to create a deep clone, not just a shallow copy. */ public function __clone() { $vars = get_object_vars($this); /** @var Column\Rule[] $value */ foreach ($vars as $key => $value) { if ($key === 'parent') { // Detach from autofilter parent $this->parent = null; } elseif ($key === 'ruleset') { // The columns array of \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\AutoFilter objects $this->ruleset = []; foreach ($value as $k => $v) { $cloned = clone $v; $cloned->setParent($this); // attach the new cloned Rule to this new cloned Autofilter Cloned object $this->ruleset[$k] = $cloned; } } else { $this->$key = $value; } } } } PK!*E33Dlibraries/vendor/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.phpnu[parent = $parent; } private function setEvaluatedFalse(): void { if ($this->parent !== null) { $this->parent->setEvaluatedFalse(); } } /** * Get AutoFilter Rule Type. */ public function getRuleType(): string { return $this->ruleType; } /** * Set AutoFilter Rule Type. * * @param string $ruleType see self::AUTOFILTER_RULETYPE_* * * @return $this */ public function setRuleType(string $ruleType) { $this->setEvaluatedFalse(); if (!in_array($ruleType, self::RULE_TYPES)) { throw new PhpSpreadsheetException('Invalid rule type for column AutoFilter Rule.'); } $this->ruleType = $ruleType; return $this; } /** * Get AutoFilter Rule Value. * * @return int|int[]|string|string[] */ public function getValue() { return $this->value; } /** * Set AutoFilter Rule Value. * * @param int|int[]|string|string[] $value * * @return $this */ public function setValue($value) { $this->setEvaluatedFalse(); if (is_array($value)) { $grouping = -1; foreach ($value as $key => $v) { // Validate array entries if (!in_array($key, self::DATE_TIME_GROUPS)) { // Remove any invalid entries from the value array unset($value[$key]); } else { // Work out what the dateTime grouping will be $grouping = max($grouping, array_search($key, self::DATE_TIME_GROUPS)); } } if (count($value) == 0) { throw new PhpSpreadsheetException('Invalid rule value for column AutoFilter Rule.'); } // Set the dateTime grouping that we've anticipated $this->setGrouping(self::DATE_TIME_GROUPS[$grouping]); } $this->value = $value; return $this; } /** * Get AutoFilter Rule Operator. */ public function getOperator(): string { return $this->operator; } /** * Set AutoFilter Rule Operator. * * @param string $operator see self::AUTOFILTER_COLUMN_RULE_* * * @return $this */ public function setOperator(string $operator) { $this->setEvaluatedFalse(); if (empty($operator)) { $operator = self::AUTOFILTER_COLUMN_RULE_EQUAL; } if ( (!in_array($operator, self::OPERATORS)) && (!in_array($operator, self::TOP_TEN_VALUE)) ) { throw new PhpSpreadsheetException('Invalid operator for column AutoFilter Rule.'); } $this->operator = $operator; return $this; } /** * Get AutoFilter Rule Grouping. */ public function getGrouping(): string { return $this->grouping; } /** * Set AutoFilter Rule Grouping. * * @return $this */ public function setGrouping(string $grouping) { $this->setEvaluatedFalse(); if ( (!in_array($grouping, self::DATE_TIME_GROUPS)) && (!in_array($grouping, self::DYNAMIC_TYPES)) && (!in_array($grouping, self::TOP_TEN_TYPE)) ) { throw new PhpSpreadsheetException('Invalid grouping for column AutoFilter Rule.'); } $this->grouping = $grouping; return $this; } /** * Set AutoFilter Rule. * * @param string $operator see self::AUTOFILTER_COLUMN_RULE_* * @param int|int[]|string|string[] $value * * @return $this */ public function setRule(string $operator, $value, ?string $grouping = null) { $this->setEvaluatedFalse(); $this->setOperator($operator); $this->setValue($value); // Only set grouping if it's been passed in as a user-supplied argument, // otherwise we're calculating it when we setValue() and don't want to overwrite that // If the user supplies an argumnet for grouping, then on their own head be it if ($grouping !== null) { $this->setGrouping($grouping); } return $this; } /** * Get this Rule's AutoFilter Column Parent. */ public function getParent(): ?Column { return $this->parent; } /** * Set this Rule's AutoFilter Column Parent. * * @return $this */ public function setParent(?Column $parent = null) { $this->setEvaluatedFalse(); $this->parent = $parent; return $this; } /** * Implement PHP __clone to create a deep clone, not just a shallow copy. */ public function __clone() { $vars = get_object_vars($this); foreach ($vars as $key => $value) { if (is_object($value)) { if ($key == 'parent') { // this is only object // Detach from autofilter column parent $this->$key = null; } } else { $this->$key = $value; } } } } PK!Elibraries/vendor/PhpSpreadsheet/Worksheet/AutoFilter/Column/index.phpnu[ */ class ColumnCellIterator extends CellIterator { /** * Current iterator position. */ private int $currentRow; /** * Column index. */ private int $columnIndex; /** * Start position. */ private int $startRow = 1; /** * End position. */ private int $endRow = 1; /** * Create a new row iterator. * * @param Worksheet $worksheet The worksheet to iterate over * @param string $columnIndex The column that we want to iterate * @param int $startRow The row number at which to start iterating * @param ?int $endRow Optionally, the row number at which to stop iterating */ public function __construct(Worksheet $worksheet, string $columnIndex = 'A', int $startRow = 1, ?int $endRow = null, bool $iterateOnlyExistingCells = false) { // Set subject $this->worksheet = $worksheet; $this->cellCollection = $worksheet->getCellCollection(); $this->columnIndex = Coordinate::columnIndexFromString($columnIndex); $this->resetEnd($endRow); $this->resetStart($startRow); $this->setIterateOnlyExistingCells($iterateOnlyExistingCells); } /** * (Re)Set the start row and the current row pointer. * * @param int $startRow The row number at which to start iterating * * @return $this */ public function resetStart(int $startRow = 1) { $this->startRow = $startRow; $this->adjustForExistingOnlyRange(); $this->seek($startRow); return $this; } /** * (Re)Set the end row. * * @param ?int $endRow The row number at which to stop iterating * * @return $this */ public function resetEnd(?int $endRow = null) { $this->endRow = $endRow ?: $this->worksheet->getHighestRow(); $this->adjustForExistingOnlyRange(); return $this; } /** * Set the row pointer to the selected row. * * @param int $row The row number to set the current pointer at * * @return $this */ public function seek(int $row = 1) { if ( $this->onlyExistingCells && (!$this->cellCollection->has(Coordinate::stringFromColumnIndex($this->columnIndex) . $row)) ) { throw new PhpSpreadsheetException('In "IterateOnlyExistingCells" mode and Cell does not exist'); } if (($row < $this->startRow) || ($row > $this->endRow)) { throw new PhpSpreadsheetException("Row $row is out of range ({$this->startRow} - {$this->endRow})"); } $this->currentRow = $row; return $this; } /** * Rewind the iterator to the starting row. */ public function rewind(): void { $this->currentRow = $this->startRow; } /** * Return the current cell in this worksheet column. */ public function current(): ?Cell { $cellAddress = Coordinate::stringFromColumnIndex($this->columnIndex) . $this->currentRow; return $this->cellCollection->has($cellAddress) ? $this->cellCollection->get($cellAddress) : ( $this->ifNotExists === self::IF_NOT_EXISTS_CREATE_NEW ? $this->worksheet->createNewCell($cellAddress) : null ); } /** * Return the current iterator key. */ public function key(): int { return $this->currentRow; } /** * Set the iterator to its next value. */ public function next(): void { $columnAddress = Coordinate::stringFromColumnIndex($this->columnIndex); do { ++$this->currentRow; } while ( ($this->onlyExistingCells) && ($this->currentRow <= $this->endRow) && (!$this->cellCollection->has($columnAddress . $this->currentRow)) ); } /** * Set the iterator to its previous value. */ public function prev(): void { $columnAddress = Coordinate::stringFromColumnIndex($this->columnIndex); do { --$this->currentRow; } while ( ($this->onlyExistingCells) && ($this->currentRow >= $this->startRow) && (!$this->cellCollection->has($columnAddress . $this->currentRow)) ); } /** * Indicate if more rows exist in the worksheet range of rows that we're iterating. */ public function valid(): bool { return $this->currentRow <= $this->endRow && $this->currentRow >= $this->startRow; } /** * Validate start/end values for "IterateOnlyExistingCells" mode, and adjust if necessary. */ protected function adjustForExistingOnlyRange(): void { if ($this->onlyExistingCells) { $columnAddress = Coordinate::stringFromColumnIndex($this->columnIndex); while ( (!$this->cellCollection->has($columnAddress . $this->startRow)) && ($this->startRow <= $this->endRow) ) { ++$this->startRow; } while ( (!$this->cellCollection->has($columnAddress . $this->endRow)) && ($this->endRow >= $this->startRow) ) { --$this->endRow; } } } } PK!и^nnAlibraries/vendor/PhpSpreadsheet/Worksheet/HeaderFooterDrawing.phpnu[getPath() . $this->name . $this->offsetX . $this->offsetY . $this->width . $this->height . __CLASS__ ); } } PK!6__1libraries/vendor/PhpSpreadsheet/Worksheet/Row.phpnu[worksheet = $worksheet; $this->rowIndex = $rowIndex; } /** * Destructor. */ public function __destruct() { unset($this->worksheet); } /** * Get row index. */ public function getRowIndex(): int { return $this->rowIndex; } /** * Get cell iterator. * * @param string $startColumn The column address at which to start iterating * @param ?string $endColumn Optionally, the column address at which to stop iterating */ public function getCellIterator(string $startColumn = 'A', ?string $endColumn = null, bool $iterateOnlyExistingCells = false): RowCellIterator { return new RowCellIterator($this->worksheet, $this->rowIndex, $startColumn, $endColumn, $iterateOnlyExistingCells); } /** * Get column iterator. Synonym for getCellIterator(). * * @param string $startColumn The column address at which to start iterating * @param ?string $endColumn Optionally, the column address at which to stop iterating */ public function getColumnIterator(string $startColumn = 'A', ?string $endColumn = null, bool $iterateOnlyExistingCells = false): RowCellIterator { return $this->getCellIterator($startColumn, $endColumn, $iterateOnlyExistingCells); } /** * Returns a boolean true if the row contains no cells. By default, this means that no cell records exist in the * collection for this row. false will be returned otherwise. * This rule can be modified by passing a $definitionOfEmptyFlags value: * 1 - CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL If the only cells in the collection are null value * cells, then the row will be considered empty. * 2 - CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL If the only cells in the collection are empty * string value cells, then the row will be considered empty. * 3 - CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL | CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL * If the only cells in the collection are null value or empty string value cells, then the row * will be considered empty. * * @param int $definitionOfEmptyFlags * Possible Flag Values are: * CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL * CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL * @param string $startColumn The column address at which to start checking if cells are empty * @param ?string $endColumn Optionally, the column address at which to stop checking if cells are empty */ public function isEmpty(int $definitionOfEmptyFlags = 0, string $startColumn = 'A', ?string $endColumn = null): bool { $nullValueCellIsEmpty = (bool) ($definitionOfEmptyFlags & CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL); $emptyStringCellIsEmpty = (bool) ($definitionOfEmptyFlags & CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL); $cellIterator = $this->getCellIterator($startColumn, $endColumn); $cellIterator->setIterateOnlyExistingCells(true); foreach ($cellIterator as $cell) { $value = $cell->getValue(); if ($value === null && $nullValueCellIsEmpty === true) { continue; } if ($value === '' && $emptyStringCellIsEmpty === true) { continue; } return false; } return true; } /** * Returns bound worksheet. */ public function getWorksheet(): Worksheet { return $this->worksheet; } } PK!g7libraries/vendor/PhpSpreadsheet/Worksheet/PageBreak.phpnu[breakType = $breakType; $this->coordinate = $coordinate; $this->maxColOrRow = $maxColOrRow; } public function getBreakType(): int { return $this->breakType; } public function getCoordinate(): string { return $this->coordinate; } public function getMaxColOrRow(): int { return $this->maxColOrRow; } public function getColumnInt(): int { return Coordinate::indexesFromString($this->coordinate)[0]; } public function getRow(): int { return Coordinate::indexesFromString($this->coordinate)[1]; } public function getColumnString(): string { return Coordinate::indexesFromString($this->coordinate)[2]; } } PK!h88:libraries/vendor/PhpSpreadsheet/Worksheet/CellIterator.phpnu[ */ abstract class CellIterator implements NativeIterator { public const TREAT_NULL_VALUE_AS_EMPTY_CELL = 1; public const TREAT_EMPTY_STRING_AS_EMPTY_CELL = 2; public const IF_NOT_EXISTS_RETURN_NULL = false; public const IF_NOT_EXISTS_CREATE_NEW = true; /** * Worksheet to iterate. */ protected Worksheet $worksheet; /** * Cell Collection to iterate. */ protected Cells $cellCollection; /** * Iterate only existing cells. */ protected bool $onlyExistingCells = false; /** * If iterating all cells, and a cell doesn't exist, identifies whether a new cell should be created, * or if the iterator should return a null value. */ protected bool $ifNotExists = self::IF_NOT_EXISTS_CREATE_NEW; /** * Destructor. */ public function __destruct() { unset($this->worksheet, $this->cellCollection); } public function getIfNotExists(): bool { return $this->ifNotExists; } public function setIfNotExists(bool $ifNotExists = self::IF_NOT_EXISTS_CREATE_NEW): void { $this->ifNotExists = $ifNotExists; } /** * Get loop only existing cells. */ public function getIterateOnlyExistingCells(): bool { return $this->onlyExistingCells; } /** * Validate start/end values for 'IterateOnlyExistingCells' mode, and adjust if necessary. */ abstract protected function adjustForExistingOnlyRange(): void; /** * Set the iterator to loop only existing cells. */ public function setIterateOnlyExistingCells(bool $value): void { $this->onlyExistingCells = (bool) $value; $this->adjustForExistingOnlyRange(); } } PK!^]<libraries/vendor/PhpSpreadsheet/Worksheet/Drawing/Shadow.phpnu[visible = false; $this->blurRadius = 6; $this->distance = 2; $this->direction = 0; $this->alignment = self::SHADOW_BOTTOM_RIGHT; $this->color = new Color(Color::COLOR_BLACK); $this->alpha = 50; } /** * Get Visible. */ public function getVisible(): bool { return $this->visible; } /** * Set Visible. * * @return $this */ public function setVisible(bool $visible) { $this->visible = $visible; return $this; } /** * Get Blur radius. */ public function getBlurRadius(): int { return $this->blurRadius; } /** * Set Blur radius. * * @return $this */ public function setBlurRadius(int $blurRadius) { $this->blurRadius = $blurRadius; return $this; } /** * Get Shadow distance. */ public function getDistance(): int { return $this->distance; } /** * Set Shadow distance. * * @return $this */ public function setDistance(int $distance) { $this->distance = $distance; return $this; } /** * Get Shadow direction (in degrees). */ public function getDirection(): int { return $this->direction; } /** * Set Shadow direction (in degrees). * * @return $this */ public function setDirection(int $direction) { $this->direction = $direction; return $this; } /** * Get Shadow alignment. */ public function getAlignment(): string { return $this->alignment; } /** * Set Shadow alignment. * * @return $this */ public function setAlignment(string $alignment) { $this->alignment = $alignment; return $this; } /** * Get Color. */ public function getColor(): Color { return $this->color; } /** * Set Color. * * @return $this */ public function setColor(Color $color) { $this->color = $color; return $this; } /** * Get Alpha. */ public function getAlpha(): int { return $this->alpha; } /** * Set Alpha. * * @return $this */ public function setAlpha(int $alpha) { $this->alpha = $alpha; return $this; } /** * Get hash code. * * @return string Hash code */ public function getHashCode(): string { return md5( ($this->visible ? 't' : 'f') . $this->blurRadius . $this->distance . $this->direction . $this->alignment . $this->color->getHashCode() . $this->alpha . __CLASS__ ); } /** * Implement PHP __clone to create a deep clone, not just a shallow copy. */ public function __clone() { $vars = get_object_vars($this); foreach ($vars as $key => $value) { if (is_object($value)) { $this->$key = clone $value; } else { $this->$key = $value; } } } } PK!;libraries/vendor/PhpSpreadsheet/Worksheet/Drawing/index.phpnu[ */ class RowCellIterator extends CellIterator { /** * Current iterator position. */ private int $currentColumnIndex; /** * Row index. */ private int $rowIndex; /** * Start position. */ private int $startColumnIndex = 1; /** * End position. */ private int $endColumnIndex = 1; /** * Create a new column iterator. * * @param Worksheet $worksheet The worksheet to iterate over * @param int $rowIndex The row that we want to iterate * @param string $startColumn The column address at which to start iterating * @param ?string $endColumn Optionally, the column address at which to stop iterating */ public function __construct(Worksheet $worksheet, int $rowIndex = 1, string $startColumn = 'A', ?string $endColumn = null, bool $iterateOnlyExistingCells = false) { // Set subject and row index $this->worksheet = $worksheet; $this->cellCollection = $worksheet->getCellCollection(); $this->rowIndex = $rowIndex; $this->resetEnd($endColumn); $this->resetStart($startColumn); $this->setIterateOnlyExistingCells($iterateOnlyExistingCells); } /** * (Re)Set the start column and the current column pointer. * * @param string $startColumn The column address at which to start iterating * * @return $this */ public function resetStart(string $startColumn = 'A') { $this->startColumnIndex = Coordinate::columnIndexFromString($startColumn); $this->adjustForExistingOnlyRange(); $this->seek(Coordinate::stringFromColumnIndex($this->startColumnIndex)); return $this; } /** * (Re)Set the end column. * * @param ?string $endColumn The column address at which to stop iterating * * @return $this */ public function resetEnd(?string $endColumn = null) { $endColumn = $endColumn ?: $this->worksheet->getHighestColumn(); $this->endColumnIndex = Coordinate::columnIndexFromString($endColumn); $this->adjustForExistingOnlyRange(); return $this; } /** * Set the column pointer to the selected column. * * @param string $column The column address to set the current pointer at * * @return $this */ public function seek(string $column = 'A') { $columnId = Coordinate::columnIndexFromString($column); if ($this->onlyExistingCells && !($this->cellCollection->has($column . $this->rowIndex))) { throw new PhpSpreadsheetException('In "IterateOnlyExistingCells" mode and Cell does not exist'); } if (($columnId < $this->startColumnIndex) || ($columnId > $this->endColumnIndex)) { throw new PhpSpreadsheetException("Column $column is out of range ({$this->startColumnIndex} - {$this->endColumnIndex})"); } $this->currentColumnIndex = $columnId; return $this; } /** * Rewind the iterator to the starting column. */ public function rewind(): void { $this->currentColumnIndex = $this->startColumnIndex; } /** * Return the current cell in this worksheet row. */ public function current(): ?Cell { $cellAddress = Coordinate::stringFromColumnIndex($this->currentColumnIndex) . $this->rowIndex; return $this->cellCollection->has($cellAddress) ? $this->cellCollection->get($cellAddress) : ( $this->ifNotExists === self::IF_NOT_EXISTS_CREATE_NEW ? $this->worksheet->createNewCell($cellAddress) : null ); } /** * Return the current iterator key. */ public function key(): string { return Coordinate::stringFromColumnIndex($this->currentColumnIndex); } /** * Set the iterator to its next value. */ public function next(): void { do { ++$this->currentColumnIndex; } while (($this->onlyExistingCells) && (!$this->cellCollection->has(Coordinate::stringFromColumnIndex($this->currentColumnIndex) . $this->rowIndex)) && ($this->currentColumnIndex <= $this->endColumnIndex)); } /** * Set the iterator to its previous value. */ public function prev(): void { do { --$this->currentColumnIndex; } while (($this->onlyExistingCells) && (!$this->cellCollection->has(Coordinate::stringFromColumnIndex($this->currentColumnIndex) . $this->rowIndex)) && ($this->currentColumnIndex >= $this->startColumnIndex)); } /** * Indicate if more columns exist in the worksheet range of columns that we're iterating. */ public function valid(): bool { return $this->currentColumnIndex <= $this->endColumnIndex && $this->currentColumnIndex >= $this->startColumnIndex; } /** * Return the current iterator position. */ public function getCurrentColumnIndex(): int { return $this->currentColumnIndex; } /** * Validate start/end values for "IterateOnlyExistingCells" mode, and adjust if necessary. */ protected function adjustForExistingOnlyRange(): void { if ($this->onlyExistingCells) { while ((!$this->cellCollection->has(Coordinate::stringFromColumnIndex($this->startColumnIndex) . $this->rowIndex)) && ($this->startColumnIndex <= $this->endColumnIndex)) { ++$this->startColumnIndex; } while ((!$this->cellCollection->has(Coordinate::stringFromColumnIndex($this->endColumnIndex) . $this->rowIndex)) && ($this->endColumnIndex >= $this->startColumnIndex)) { --$this->endColumnIndex; } } } } PK!@""8libraries/vendor/PhpSpreadsheet/Worksheet/Protection.phpnu[password !== '' || isset($this->sheet) || isset($this->objects) || isset($this->scenarios) || isset($this->formatCells) || isset($this->formatColumns) || isset($this->formatRows) || isset($this->insertColumns) || isset($this->insertRows) || isset($this->insertHyperlinks) || isset($this->deleteColumns) || isset($this->deleteRows) || isset($this->selectLockedCells) || isset($this->sort) || isset($this->autoFilter) || isset($this->pivotTables) || isset($this->selectUnlockedCells); } public function getSheet(): ?bool { return $this->sheet; } public function setSheet(?bool $sheet): self { $this->sheet = $sheet; return $this; } public function getObjects(): ?bool { return $this->objects; } public function setObjects(?bool $objects): self { $this->objects = $objects; return $this; } public function getScenarios(): ?bool { return $this->scenarios; } public function setScenarios(?bool $scenarios): self { $this->scenarios = $scenarios; return $this; } public function getFormatCells(): ?bool { return $this->formatCells; } public function setFormatCells(?bool $formatCells): self { $this->formatCells = $formatCells; return $this; } public function getFormatColumns(): ?bool { return $this->formatColumns; } public function setFormatColumns(?bool $formatColumns): self { $this->formatColumns = $formatColumns; return $this; } public function getFormatRows(): ?bool { return $this->formatRows; } public function setFormatRows(?bool $formatRows): self { $this->formatRows = $formatRows; return $this; } public function getInsertColumns(): ?bool { return $this->insertColumns; } public function setInsertColumns(?bool $insertColumns): self { $this->insertColumns = $insertColumns; return $this; } public function getInsertRows(): ?bool { return $this->insertRows; } public function setInsertRows(?bool $insertRows): self { $this->insertRows = $insertRows; return $this; } public function getInsertHyperlinks(): ?bool { return $this->insertHyperlinks; } public function setInsertHyperlinks(?bool $insertHyperLinks): self { $this->insertHyperlinks = $insertHyperLinks; return $this; } public function getDeleteColumns(): ?bool { return $this->deleteColumns; } public function setDeleteColumns(?bool $deleteColumns): self { $this->deleteColumns = $deleteColumns; return $this; } public function getDeleteRows(): ?bool { return $this->deleteRows; } public function setDeleteRows(?bool $deleteRows): self { $this->deleteRows = $deleteRows; return $this; } public function getSelectLockedCells(): ?bool { return $this->selectLockedCells; } public function setSelectLockedCells(?bool $selectLockedCells): self { $this->selectLockedCells = $selectLockedCells; return $this; } public function getSort(): ?bool { return $this->sort; } public function setSort(?bool $sort): self { $this->sort = $sort; return $this; } public function getAutoFilter(): ?bool { return $this->autoFilter; } public function setAutoFilter(?bool $autoFilter): self { $this->autoFilter = $autoFilter; return $this; } public function getPivotTables(): ?bool { return $this->pivotTables; } public function setPivotTables(?bool $pivotTables): self { $this->pivotTables = $pivotTables; return $this; } public function getSelectUnlockedCells(): ?bool { return $this->selectUnlockedCells; } public function setSelectUnlockedCells(?bool $selectUnlockedCells): self { $this->selectUnlockedCells = $selectUnlockedCells; return $this; } /** * Get hashed password. */ public function getPassword(): string { return $this->password; } /** * Set Password. * * @param bool $alreadyHashed If the password has already been hashed, set this to true * * @return $this */ public function setPassword(string $password, bool $alreadyHashed = false) { if (!$alreadyHashed) { $salt = $this->generateSalt(); $this->setSalt($salt); $password = PasswordHasher::hashPassword($password, $this->getAlgorithm(), $this->getSalt(), $this->getSpinCount()); } $this->password = $password; return $this; } public function setHashValue(string $password): self { return $this->setPassword($password, true); } /** * Create a pseudorandom string. */ private function generateSalt(): string { return base64_encode(random_bytes(16)); } /** * Get algorithm name. */ public function getAlgorithm(): string { return $this->algorithm; } /** * Set algorithm name. */ public function setAlgorithm(string $algorithm): self { return $this->setAlgorithmName($algorithm); } /** * Set algorithm name. */ public function setAlgorithmName(string $algorithm): self { $this->algorithm = $algorithm; return $this; } public function getSalt(): string { return $this->salt; } public function setSalt(string $salt): self { return $this->setSaltValue($salt); } public function setSaltValue(string $salt): self { $this->salt = $salt; return $this; } /** * Get spin count. */ public function getSpinCount(): int { return $this->spinCount; } /** * Set spin count. */ public function setSpinCount(int $spinCount): self { $this->spinCount = $spinCount; return $this; } /** * Verify that the given non-hashed password can "unlock" the protection. */ public function verify(string $password): bool { if ($this->password === '') { return true; } $hash = PasswordHasher::hashPassword($password, $this->getAlgorithm(), $this->getSalt(), $this->getSpinCount()); return $this->getPassword() === $hash; } /** * Implement PHP __clone to create a deep clone, not just a shallow copy. */ public function __clone() { $vars = get_object_vars($this); foreach ($vars as $key => $value) { if (is_object($value)) { $this->$key = clone $value; } else { $this->$key = $value; } } } } PK!{y y Blibraries/vendor/PhpSpreadsheet/Worksheet/Table/TableDxfsStyle.phpnu[name = $name; } /** * Get name. */ public function getName(): string { return $this->name; } /** * Set header row dxfs index. */ public function setHeaderRow(int $row): self { $this->headerRow = $row; return $this; } /** * Get header row dxfs index. */ public function getHeaderRow(): ?int { return $this->headerRow; } /** * Set first row stripe dxfs index. */ public function setFirstRowStripe(int $row): self { $this->firstRowStripe = $row; return $this; } /** * Get first row stripe dxfs index. */ public function getFirstRowStripe(): ?int { return $this->firstRowStripe; } /** * Set second row stripe dxfs index. */ public function setSecondRowStripe(int $row): self { $this->secondRowStripe = $row; return $this; } /** * Get second row stripe dxfs index. */ public function getSecondRowStripe(): ?int { return $this->secondRowStripe; } /** * Set Header row Style. */ public function setHeaderRowStyle(Style $style): self { $this->headerRowStyle = $style; return $this; } /** * Get Header row Style. */ public function getHeaderRowStyle(): ?Style { return $this->headerRowStyle; } /** * Set first row stripe Style. */ public function setFirstRowStripeStyle(Style $style): self { $this->firstRowStripeStyle = $style; return $this; } /** * Get first row stripe Style. */ public function getFirstRowStripeStyle(): ?Style { return $this->firstRowStripeStyle; } /** * Set second row stripe Style. */ public function setSecondRowStripeStyle(Style $style): self { $this->secondRowStripeStyle = $style; return $this; } /** * Get second row stripe Style. */ public function getSecondRowStripeStyle(): ?Style { return $this->secondRowStripeStyle; } } PK!}8  >libraries/vendor/PhpSpreadsheet/Worksheet/Table/TableStyle.phpnu[theme = $theme; } /** * Get theme. */ public function getTheme(): string { return $this->theme; } /** * Set theme. */ public function setTheme(string $theme): self { $this->theme = $theme; return $this; } /** * Get show First Column. */ public function getShowFirstColumn(): bool { return $this->showFirstColumn; } /** * Set show First Column. */ public function setShowFirstColumn(bool $showFirstColumn): self { $this->showFirstColumn = $showFirstColumn; return $this; } /** * Get show Last Column. */ public function getShowLastColumn(): bool { return $this->showLastColumn; } /** * Set show Last Column. */ public function setShowLastColumn(bool $showLastColumn): self { $this->showLastColumn = $showLastColumn; return $this; } /** * Get show Row Stripes. */ public function getShowRowStripes(): bool { return $this->showRowStripes; } /** * Set show Row Stripes. */ public function setShowRowStripes(bool $showRowStripes): self { $this->showRowStripes = $showRowStripes; return $this; } /** * Get show Column Stripes. */ public function getShowColumnStripes(): bool { return $this->showColumnStripes; } /** * Set show Column Stripes. */ public function setShowColumnStripes(bool $showColumnStripes): self { $this->showColumnStripes = $showColumnStripes; return $this; } /** * Get this Style's Dxfs TableStyle. */ public function getTableDxfsStyle(): ?TableDxfsStyle { return $this->tableStyle; } /** * Set this Style's Dxfs TableStyle. */ public function setTableDxfsStyle(TableDxfsStyle $tableStyle, array $dxfs): self { $this->tableStyle = $tableStyle; if ($this->tableStyle->getHeaderRow() !== null && isset($dxfs[$this->tableStyle->getHeaderRow()])) { $this->tableStyle->setHeaderRowStyle($dxfs[$this->tableStyle->getHeaderRow()]); } if ($this->tableStyle->getFirstRowStripe() !== null && isset($dxfs[$this->tableStyle->getFirstRowStripe()])) { $this->tableStyle->setFirstRowStripeStyle($dxfs[$this->tableStyle->getFirstRowStripe()]); } if ($this->tableStyle->getSecondRowStripe() !== null && isset($dxfs[$this->tableStyle->getSecondRowStripe()])) { $this->tableStyle->setSecondRowStripeStyle($dxfs[$this->tableStyle->getSecondRowStripe()]); } return $this; } /** * Get this Style's Table. */ public function getTable(): ?Table { return $this->table; } /** * Set this Style's Table. */ public function setTable(?Table $table = null): self { $this->table = $table; return $this; } } PK!9libraries/vendor/PhpSpreadsheet/Worksheet/Table/index.phpnu[columnIndex = $column; $this->table = $table; } /** * Get Table column index as string eg: 'A'. */ public function getColumnIndex(): string { return $this->columnIndex; } /** * Set Table column index as string eg: 'A'. * * @param string $column Column (e.g. A) */ public function setColumnIndex(string $column): self { // Uppercase coordinate $column = strtoupper($column); if ($this->table !== null) { $this->table->isColumnInRange($column); } $this->columnIndex = $column; return $this; } /** * Get show Filter Button. */ public function getShowFilterButton(): bool { return $this->showFilterButton; } /** * Set show Filter Button. */ public function setShowFilterButton(bool $showFilterButton): self { $this->showFilterButton = $showFilterButton; return $this; } /** * Get total Row Label. */ public function getTotalsRowLabel(): ?string { return $this->totalsRowLabel; } /** * Set total Row Label. */ public function setTotalsRowLabel(string $totalsRowLabel): self { $this->totalsRowLabel = $totalsRowLabel; return $this; } /** * Get total Row Function. */ public function getTotalsRowFunction(): ?string { return $this->totalsRowFunction; } /** * Set total Row Function. */ public function setTotalsRowFunction(string $totalsRowFunction): self { $this->totalsRowFunction = $totalsRowFunction; return $this; } /** * Get total Row Formula. */ public function getTotalsRowFormula(): ?string { return $this->totalsRowFormula; } /** * Set total Row Formula. */ public function setTotalsRowFormula(string $totalsRowFormula): self { $this->totalsRowFormula = $totalsRowFormula; return $this; } /** * Get column Formula. */ public function getColumnFormula(): ?string { return $this->columnFormula; } /** * Set column Formula. */ public function setColumnFormula(string $columnFormula): self { $this->columnFormula = $columnFormula; return $this; } /** * Get this Column's Table. */ public function getTable(): ?Table { return $this->table; } /** * Set this Column's Table. */ public function setTable(?Table $table = null): self { $this->table = $table; return $this; } public static function updateStructuredReferences(?Worksheet $workSheet, ?string $oldTitle, ?string $newTitle): void { if ($workSheet === null || $oldTitle === null || $oldTitle === '' || $newTitle === null) { return; } // Remember that table headings are case-insensitive if (StringHelper::strToLower($oldTitle) !== StringHelper::strToLower($newTitle)) { // We need to check all formula cells that might contain Structured References that refer // to this column, and update those formulae to reference the new column text $spreadsheet = $workSheet->getParentOrThrow(); foreach ($spreadsheet->getWorksheetIterator() as $sheet) { self::updateStructuredReferencesInCells($sheet, $oldTitle, $newTitle); } self::updateStructuredReferencesInNamedFormulae($spreadsheet, $oldTitle, $newTitle); } } private static function updateStructuredReferencesInCells(Worksheet $worksheet, string $oldTitle, string $newTitle): void { $pattern = '/\[(@?)' . preg_quote($oldTitle, '/') . '\]/mui'; foreach ($worksheet->getCoordinates(false) as $coordinate) { $cell = $worksheet->getCell($coordinate); if ($cell->getDataType() === DataType::TYPE_FORMULA) { $formula = $cell->getValueString(); if (preg_match($pattern, $formula) === 1) { $formula = preg_replace($pattern, "[$1{$newTitle}]", $formula); $cell->setValueExplicit($formula, DataType::TYPE_FORMULA); } } } } private static function updateStructuredReferencesInNamedFormulae(Spreadsheet $spreadsheet, string $oldTitle, string $newTitle): void { $pattern = '/\[(@?)' . preg_quote($oldTitle, '/') . '\]/mui'; foreach ($spreadsheet->getNamedFormulae() as $namedFormula) { $formula = $namedFormula->getValue(); if (preg_match($pattern, $formula) === 1) { $formula = preg_replace($pattern, "[$1{$newTitle}]", $formula) ?? ''; $namedFormula->setValue($formula); } } } } PK!Ke5libraries/vendor/PhpSpreadsheet/Worksheet/Drawing.phpnu[ IMAGETYPE_PNG, IMAGETYPE_JPEG => IMAGETYPE_JPEG, IMAGETYPE_PNG => IMAGETYPE_PNG, IMAGETYPE_BMP => IMAGETYPE_PNG, ]; /** * Path. */ private string $path; /** * Whether or not we are dealing with a URL. */ private bool $isUrl; /** * Create a new Drawing. */ public function __construct() { // Initialise values $this->path = ''; $this->isUrl = false; // Initialize parent parent::__construct(); } /** * Get Filename. */ public function getFilename(): string { return basename($this->path); } /** * Get indexed filename (using image index). */ public function getIndexedFilename(): string { return md5($this->path) . '.' . $this->getExtension(); } /** * Get Extension. */ public function getExtension(): string { $exploded = explode('.', basename($this->path)); return $exploded[count($exploded) - 1]; } /** * Get full filepath to store drawing in zip archive. */ public function getMediaFilename(): string { if (!array_key_exists($this->type, self::IMAGE_TYPES_CONVERTION_MAP)) { throw new PhpSpreadsheetException('Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.'); } return sprintf('image%d%s', $this->getImageIndex(), $this->getImageFileExtensionForSave()); } /** * Get Path. */ public function getPath(): string { return $this->path; } /** * Set Path. * * @param string $path File path * @param bool $verifyFile Verify file * @param ?ZipArchive $zip Zip archive instance * * @return $this */ public function setPath(string $path, bool $verifyFile = true, ?ZipArchive $zip = null) { $this->isUrl = false; if (preg_match('~^data:image/[a-z]+;base64,~', $path) === 1) { $this->path = $path; return $this; } $this->path = ''; // Check if a URL has been passed. https://stackoverflow.com/a/2058596/1252979 if (filter_var($path, FILTER_VALIDATE_URL) || (preg_match('/^([\w\s\x00-\x1f]+):/u', $path) && !preg_match('/^([\w]+):/u', $path))) { if (!preg_match('/^(http|https|file|ftp|s3):/', $path)) { throw new PhpSpreadsheetException('Invalid protocol for linked drawing'); } // Implicit that it is a URL, rather store info than running check above on value in other places. $this->isUrl = true; $ctx = null; // https://github.com/php/php-src/issues/16023 // https://github.com/php/php-src/issues/17121 if (str_starts_with($path, 'https:') || str_starts_with($path, 'http:')) { $ctxArray = [ 'http' => [ 'user_agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', 'header' => [ //'Connection: keep-alive', // unacceptable performance 'Accept: image/*;q=0.9,*/*;q=0.8', ], ], ]; if (str_starts_with($path, 'https:')) { $ctxArray['ssl'] = ['crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT]; } $ctx = stream_context_create($ctxArray); } $imageContents = @file_get_contents($path, false, $ctx); if ($imageContents !== false) { $filePath = tempnam(sys_get_temp_dir(), 'Drawing'); if ($filePath) { $put = @file_put_contents($filePath, $imageContents); if ($put !== false) { if ($this->isImage($filePath)) { $this->path = $path; $this->setSizesAndType($filePath); } unlink($filePath); } } } } elseif ($zip instanceof ZipArchive) { $zipPath = explode('#', $path)[1]; $locate = @$zip->locateName($zipPath); if ($locate !== false) { if ($this->isImage($path)) { $this->path = $path; $this->setSizesAndType($path); } } } else { $exists = @file_exists($path); if ($exists !== false && $this->isImage($path)) { $this->path = $path; $this->setSizesAndType($path); } } if ($this->path === '' && $verifyFile) { throw new PhpSpreadsheetException("File $path not found!"); } if ($this->worksheet !== null) { if ($this->path !== '') { $this->worksheet->getCell($this->coordinates); } } return $this; } private function isImage(string $path): bool { $mime = (string) @mime_content_type($path); $retVal = false; if (str_starts_with($mime, 'image/')) { $retVal = true; } elseif ($mime === 'application/octet-stream') { $extension = pathinfo($path, PATHINFO_EXTENSION); $retVal = in_array($extension, ['bin', 'emf'], true); } return $retVal; } /** * Get isURL. */ public function getIsURL(): bool { return $this->isUrl; } /** * Get hash code. * * @return string Hash code */ public function getHashCode(): string { return md5( $this->path . parent::getHashCode() . __CLASS__ ); } /** * Get Image Type for Save. */ public function getImageTypeForSave(): int { if (!array_key_exists($this->type, self::IMAGE_TYPES_CONVERTION_MAP)) { throw new PhpSpreadsheetException('Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.'); } return self::IMAGE_TYPES_CONVERTION_MAP[$this->type]; } /** * Get Image file extention for Save. */ public function getImageFileExtensionForSave(bool $includeDot = true): string { if (!array_key_exists($this->type, self::IMAGE_TYPES_CONVERTION_MAP)) { throw new PhpSpreadsheetException('Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.'); } $result = image_type_to_extension(self::IMAGE_TYPES_CONVERTION_MAP[$this->type], $includeDot); return "$result"; } /** * Get Image mime type. */ public function getImageMimeType(): string { if (!array_key_exists($this->type, self::IMAGE_TYPES_CONVERTION_MAP)) { throw new PhpSpreadsheetException('Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.'); } return image_type_to_mime_type(self::IMAGE_TYPES_CONVERTION_MAP[$this->type]); } } PK!T6 6 :libraries/vendor/PhpSpreadsheet/Worksheet/RowDimension.phpnu[rowIndex = $index; // set dimension as unformatted by default parent::__construct(null); } /** * Get Row Index. */ public function getRowIndex(): ?int { return $this->rowIndex; } /** * Set Row Index. * * @return $this */ public function setRowIndex(int $index) { $this->rowIndex = $index; return $this; } /** * Get Row Height. * By default, this will be in points; but this method also accepts an optional unit of measure * argument, and will convert the value from points to the specified UoM. * A value of -1 tells Excel to display this column in its default height. */ public function getRowHeight(?string $unitOfMeasure = null): float { return ($unitOfMeasure === null || $this->height < 0) ? $this->height : (new CssDimension($this->height . CssDimension::UOM_POINTS))->toUnit($unitOfMeasure); } /** * Set Row Height. * * @param float $height in points. A value of -1 tells Excel to display this column in its default height. * By default, this will be the passed argument value; but this method also accepts an optional unit of measure * argument, and will convert the passed argument value to points from the specified UoM * * @return $this */ public function setRowHeight(float $height, ?string $unitOfMeasure = null) { $this->height = ($unitOfMeasure === null || $height < 0) ? $height : (new CssDimension("{$height}{$unitOfMeasure}"))->height(); return $this; } /** * Get ZeroHeight. */ public function getZeroHeight(): bool { return $this->zeroHeight; } /** * Set ZeroHeight. * * @return $this */ public function setZeroHeight(bool $zeroHeight) { $this->zeroHeight = $zeroHeight; return $this; } } PK!q`;;3libraries/vendor/PhpSpreadsheet/Worksheet/Table.phpnu[|AddressRange|AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $range * A simple string containing a Cell range like 'A1:E10' is permitted * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), * or an AddressRange object. * @param string $name (e.g. Table1) */ public function __construct($range = '', string $name = '') { $this->style = new TableStyle(); $this->autoFilter = new AutoFilter($range); $this->setRange($range); $this->setName($name); } /** * Code to execute when this table is unset(). */ public function __destruct() { $this->workSheet = null; } /** * Get Table name. */ public function getName(): string { return $this->name; } /** * Set Table name. * * @throws PhpSpreadsheetException */ public function setName(string $name): self { $name = trim($name); if (!empty($name)) { if (strlen($name) === 1 && in_array($name, ['C', 'c', 'R', 'r'])) { throw new PhpSpreadsheetException('The table name is invalid'); } if (StringHelper::countCharacters($name) > 255) { throw new PhpSpreadsheetException('The table name cannot be longer than 255 characters'); } // Check for A1 or R1C1 cell reference notation if ( preg_match(Coordinate::A1_COORDINATE_REGEX, $name) || preg_match('/^R\[?\-?[0-9]*\]?C\[?\-?[0-9]*\]?$/i', $name) ) { throw new PhpSpreadsheetException('The table name can\'t be the same as a cell reference'); } if (!preg_match('/^[\p{L}_\\\]/iu', $name)) { throw new PhpSpreadsheetException('The table name must begin a name with a letter, an underscore character (_), or a backslash (\)'); } if (!preg_match('/^[\p{L}_\\\][\p{L}\p{M}0-9\._]+$/iu', $name)) { throw new PhpSpreadsheetException('The table name contains invalid characters'); } $this->checkForDuplicateTableNames($name, $this->workSheet); $this->updateStructuredReferences($name); } $this->name = $name; return $this; } /** * @throws PhpSpreadsheetException */ private function checkForDuplicateTableNames(string $name, ?Worksheet $worksheet): void { // Remember that table names are case-insensitive $tableName = StringHelper::strToLower($name); if ($worksheet !== null && StringHelper::strToLower($this->name) !== $name) { $spreadsheet = $worksheet->getParentOrThrow(); foreach ($spreadsheet->getWorksheetIterator() as $sheet) { foreach ($sheet->getTableCollection() as $table) { if (StringHelper::strToLower($table->getName()) === $tableName && $table != $this) { throw new PhpSpreadsheetException("Spreadsheet already contains a table named '{$this->name}'"); } } } } } private function updateStructuredReferences(string $name): void { if (!$this->workSheet || !$this->name) { return; } // Remember that table names are case-insensitive if (StringHelper::strToLower($this->name) !== StringHelper::strToLower($name)) { // We need to check all formula cells that might contain fully-qualified Structured References // that refer to this table, and update those formulae to reference the new table name $spreadsheet = $this->workSheet->getParentOrThrow(); foreach ($spreadsheet->getWorksheetIterator() as $sheet) { $this->updateStructuredReferencesInCells($sheet, $name); } $this->updateStructuredReferencesInNamedFormulae($spreadsheet, $name); } } private function updateStructuredReferencesInCells(Worksheet $worksheet, string $newName): void { $pattern = '/' . preg_quote($this->name, '/') . '\[/mui'; foreach ($worksheet->getCoordinates(false) as $coordinate) { $cell = $worksheet->getCell($coordinate); if ($cell->getDataType() === DataType::TYPE_FORMULA) { $formula = $cell->getValueString(); if (preg_match($pattern, $formula) === 1) { $formula = preg_replace($pattern, "{$newName}[", $formula); $cell->setValueExplicit($formula, DataType::TYPE_FORMULA); } } } } private function updateStructuredReferencesInNamedFormulae(Spreadsheet $spreadsheet, string $newName): void { $pattern = '/' . preg_quote($this->name, '/') . '\[/mui'; foreach ($spreadsheet->getNamedFormulae() as $namedFormula) { $formula = $namedFormula->getValue(); if (preg_match($pattern, $formula) === 1) { $formula = preg_replace($pattern, "{$newName}[", $formula) ?? ''; $namedFormula->setValue($formula); } } } /** * Get show Header Row. */ public function getShowHeaderRow(): bool { return $this->showHeaderRow; } /** * Set show Header Row. */ public function setShowHeaderRow(bool $showHeaderRow): self { $this->showHeaderRow = $showHeaderRow; return $this; } /** * Get show Totals Row. */ public function getShowTotalsRow(): bool { return $this->showTotalsRow; } /** * Set show Totals Row. */ public function setShowTotalsRow(bool $showTotalsRow): self { $this->showTotalsRow = $showTotalsRow; return $this; } /** * Get allow filter. * If false, autofiltering is disabled for the table, if true it is enabled. */ public function getAllowFilter(): bool { return $this->allowFilter; } /** * Set show Autofiltering. * Disabling autofiltering has the same effect as hiding the filter button on all the columns in the table. */ public function setAllowFilter(bool $allowFilter): self { $this->allowFilter = $allowFilter; return $this; } /** * Get Table Range. */ public function getRange(): string { return $this->range; } /** * Set Table Cell Range. * * @param AddressRange|AddressRange|AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $range * A simple string containing a Cell range like 'A1:E10' is permitted * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), * or an AddressRange object. */ public function setRange($range = ''): self { // extract coordinate if ($range !== '') { [, $range] = Worksheet::extractSheetTitle(Validations::validateCellRange($range), true); } if (empty($range)) { // Discard all column rules $this->columns = []; $this->range = ''; return $this; } if (!str_contains($range, ':')) { throw new PhpSpreadsheetException('Table must be set on a range of cells.'); } [$width, $height] = Coordinate::rangeDimension($range); if ($width < 1 || $height < 1) { throw new PhpSpreadsheetException('The table range must be at least 1 column and row'); } $this->range = $range; $this->autoFilter->setRange($range); // Discard any column rules that are no longer valid within this range [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range); foreach ($this->columns as $key => $value) { $colIndex = Coordinate::columnIndexFromString($key); if (($rangeStart[0] > $colIndex) || ($rangeEnd[0] < $colIndex)) { unset($this->columns[$key]); } } return $this; } /** * Set Table Cell Range to max row. */ public function setRangeToMaxRow(): self { if ($this->workSheet !== null) { $thisrange = $this->range; $range = (string) preg_replace('/\d+$/', (string) $this->workSheet->getHighestRow(), $thisrange); if ($range !== $thisrange) { $this->setRange($range); } } return $this; } /** * Get Table's Worksheet. */ public function getWorksheet(): ?Worksheet { return $this->workSheet; } /** * Set Table's Worksheet. */ public function setWorksheet(?Worksheet $worksheet = null): self { if ($this->name !== '' && $worksheet !== null) { $spreadsheet = $worksheet->getParentOrThrow(); $tableName = StringHelper::strToUpper($this->name); foreach ($spreadsheet->getWorksheetIterator() as $sheet) { foreach ($sheet->getTableCollection() as $table) { if (StringHelper::strToUpper($table->getName()) === $tableName) { throw new PhpSpreadsheetException("Workbook already contains a table named '{$this->name}'"); } } } } $this->workSheet = $worksheet; $this->autoFilter->setParent($worksheet); return $this; } /** * Get all Table Columns. * * @return Table\Column[] */ public function getColumns(): array { return $this->columns; } /** * Validate that the specified column is in the Table range. * * @param string $column Column name (e.g. A) * * @return int The column offset within the table range */ public function isColumnInRange(string $column): int { if (empty($this->range)) { throw new PhpSpreadsheetException('No table range is defined.'); } $columnIndex = Coordinate::columnIndexFromString($column); [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range); if (($rangeStart[0] > $columnIndex) || ($rangeEnd[0] < $columnIndex)) { throw new PhpSpreadsheetException('Column is outside of current table range.'); } return $columnIndex - $rangeStart[0]; } /** * Get a specified Table Column Offset within the defined Table range. * * @param string $column Column name (e.g. A) * * @return int The offset of the specified column within the table range */ public function getColumnOffset(string $column): int { return $this->isColumnInRange($column); } /** * Get a specified Table Column. * * @param string $column Column name (e.g. A) */ public function getColumn(string $column): Table\Column { $this->isColumnInRange($column); if (!isset($this->columns[$column])) { $this->columns[$column] = new Table\Column($column, $this); } return $this->columns[$column]; } /** * Get a specified Table Column by it's offset. * * @param int $columnOffset Column offset within range (starting from 0) */ public function getColumnByOffset(int $columnOffset): Table\Column { [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range); $pColumn = Coordinate::stringFromColumnIndex($rangeStart[0] + $columnOffset); return $this->getColumn($pColumn); } /** * Set Table. * * @param string|Table\Column $columnObjectOrString * A simple string containing a Column ID like 'A' is permitted */ public function setColumn($columnObjectOrString): self { if ((is_string($columnObjectOrString)) && (!empty($columnObjectOrString))) { $column = $columnObjectOrString; } elseif ($columnObjectOrString instanceof Table\Column) { $column = $columnObjectOrString->getColumnIndex(); } else { throw new PhpSpreadsheetException('Column is not within the table range.'); } $this->isColumnInRange($column); if (is_string($columnObjectOrString)) { $this->columns[$columnObjectOrString] = new Table\Column($columnObjectOrString, $this); } else { $columnObjectOrString->setTable($this); $this->columns[$column] = $columnObjectOrString; } ksort($this->columns); return $this; } /** * Clear a specified Table Column. * * @param string $column Column name (e.g. A) */ public function clearColumn(string $column): self { $this->isColumnInRange($column); if (isset($this->columns[$column])) { unset($this->columns[$column]); } return $this; } /** * Shift an Table Column Rule to a different column. * * Note: This method bypasses validation of the destination column to ensure it is within this Table range. * Nor does it verify whether any column rule already exists at $toColumn, but will simply override any existing value. * Use with caution. * * @param string $fromColumn Column name (e.g. A) * @param string $toColumn Column name (e.g. B) */ public function shiftColumn(string $fromColumn, string $toColumn): self { $fromColumn = strtoupper($fromColumn); $toColumn = strtoupper($toColumn); if (isset($this->columns[$fromColumn])) { $this->columns[$fromColumn]->setTable(); $this->columns[$fromColumn]->setColumnIndex($toColumn); $this->columns[$toColumn] = $this->columns[$fromColumn]; $this->columns[$toColumn]->setTable($this); unset($this->columns[$fromColumn]); ksort($this->columns); } return $this; } /** * Get table Style. */ public function getStyle(): TableStyle { return $this->style; } /** * Set table Style. */ public function setStyle(TableStyle $style): self { $this->style = $style; return $this; } /** * Get AutoFilter. */ public function getAutoFilter(): AutoFilter { return $this->autoFilter; } /** * Set AutoFilter. */ public function setAutoFilter(AutoFilter $autoFilter): self { $this->autoFilter = $autoFilter; return $this; } /** * Get the row number on this table for given coordinates. */ public function getRowNumber(string $coordinate): int { $range = $this->getRange(); $coords = Coordinate::splitRange($range); $firstCell = Coordinate::coordinateFromString($coords[0][0]); $thisCell = Coordinate::coordinateFromString($coordinate); return (int) $thisCell[1] - (int) $firstCell[1]; } /** * Implement PHP __clone to create a deep clone, not just a shallow copy. */ public function __clone() { $vars = get_object_vars($this); foreach ($vars as $key => $value) { if (is_object($value)) { if ($key === 'workSheet') { // Detach from worksheet $this->{$key} = null; } else { $this->{$key} = clone $value; } } elseif ((is_array($value)) && ($key === 'columns')) { // The columns array of \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\Table objects $this->{$key} = []; foreach ($value as $k => $v) { /** @var Table\Column $v */ $this->{$key}[$k] = clone $v; // attach the new cloned Column to this new cloned Table object $this->{$key}[$k]->setTable($this); } } else { $this->{$key} = $value; } } } /** * toString method replicates previous behavior by returning the range if object is * referenced as a property of its worksheet. */ public function __toString(): string { return (string) $this->range; } } PK!3libraries/vendor/PhpSpreadsheet/Worksheet/index.phpnu[worksheet = $worksheet; $this->columnIndex = $columnIndex; } /** * Destructor. */ public function __destruct() { unset($this->worksheet); } /** * Get column index as string eg: 'A'. */ public function getColumnIndex(): string { return $this->columnIndex; } /** * Get cell iterator. * * @param int $startRow The row number at which to start iterating * @param ?int $endRow Optionally, the row number at which to stop iterating */ public function getCellIterator(int $startRow = 1, ?int $endRow = null, bool $iterateOnlyExistingCells = false): ColumnCellIterator { return new ColumnCellIterator($this->worksheet, $this->columnIndex, $startRow, $endRow, $iterateOnlyExistingCells); } /** * Get row iterator. Synonym for getCellIterator(). * * @param int $startRow The row number at which to start iterating * @param ?int $endRow Optionally, the row number at which to stop iterating */ public function getRowIterator(int $startRow = 1, ?int $endRow = null, bool $iterateOnlyExistingCells = false): ColumnCellIterator { return $this->getCellIterator($startRow, $endRow, $iterateOnlyExistingCells); } /** * Returns a boolean true if the column contains no cells. By default, this means that no cell records exist in the * collection for this column. false will be returned otherwise. * This rule can be modified by passing a $definitionOfEmptyFlags value: * 1 - CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL If the only cells in the collection are null value * cells, then the column will be considered empty. * 2 - CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL If the only cells in the collection are empty * string value cells, then the column will be considered empty. * 3 - CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL | CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL * If the only cells in the collection are null value or empty string value cells, then the column * will be considered empty. * * @param int $definitionOfEmptyFlags * Possible Flag Values are: * CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL * CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL * @param int $startRow The row number at which to start checking if cells are empty * @param ?int $endRow Optionally, the row number at which to stop checking if cells are empty */ public function isEmpty(int $definitionOfEmptyFlags = 0, int $startRow = 1, ?int $endRow = null): bool { $nullValueCellIsEmpty = (bool) ($definitionOfEmptyFlags & CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL); $emptyStringCellIsEmpty = (bool) ($definitionOfEmptyFlags & CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL); $cellIterator = $this->getCellIterator($startRow, $endRow); $cellIterator->setIterateOnlyExistingCells(true); foreach ($cellIterator as $cell) { $value = $cell->getValue(); if ($value === null && $nullValueCellIsEmpty === true) { continue; } if ($value === '' && $emptyStringCellIsEmpty === true) { continue; } return false; } return true; } /** * Returns bound worksheet. */ public function getWorksheet(): Worksheet { return $this->worksheet; } } PK!?''7libraries/vendor/PhpSpreadsheet/Worksheet/Worksheet.phpnu[ */ private ArrayObject $drawingCollection; /** * Collection of Chart objects. * * @var ArrayObject */ private ArrayObject $chartCollection; /** * Collection of Table objects. * * @var ArrayObject */ private ArrayObject $tableCollection; /** * Worksheet title. */ private string $title = ''; /** * Sheet state. */ private string $sheetState; /** * Page setup. */ private PageSetup $pageSetup; /** * Page margins. */ private PageMargins $pageMargins; /** * Page header/footer. */ private HeaderFooter $headerFooter; /** * Sheet view. */ private SheetView $sheetView; /** * Protection. */ private Protection $protection; /** * Conditional styles. Indexed by cell coordinate, e.g. 'A1'. */ private array $conditionalStylesCollection = []; /** * Collection of row breaks. * * @var PageBreak[] */ private array $rowBreaks = []; /** * Collection of column breaks. * * @var PageBreak[] */ private array $columnBreaks = []; /** * Collection of merged cell ranges. * * @var string[] */ private array $mergeCells = []; /** * Collection of protected cell ranges. * * @var ProtectedRange[] */ private array $protectedCells = []; /** * Autofilter Range and selection. */ private AutoFilter $autoFilter; /** * Freeze pane. */ private ?string $freezePane = null; /** * Default position of the right bottom pane. */ private ?string $topLeftCell = null; private string $paneTopLeftCell = ''; private string $activePane = ''; private int $xSplit = 0; private int $ySplit = 0; private string $paneState = ''; /** * Properties of the 4 panes. * * @var (null|Pane)[] */ private array $panes = [ 'bottomRight' => null, 'bottomLeft' => null, 'topRight' => null, 'topLeft' => null, ]; /** * Show gridlines? */ private bool $showGridlines = true; /** * Print gridlines? */ private bool $printGridlines = false; /** * Show row and column headers? */ private bool $showRowColHeaders = true; /** * Show summary below? (Row/Column outline). */ private bool $showSummaryBelow = true; /** * Show summary right? (Row/Column outline). */ private bool $showSummaryRight = true; /** * Collection of comments. * * @var Comment[] */ private array $comments = []; /** * Active cell. (Only one!). */ private string $activeCell = 'A1'; /** * Selected cells. */ private string $selectedCells = 'A1'; /** * Cached highest column. */ private int $cachedHighestColumn = 1; /** * Cached highest row. */ private int $cachedHighestRow = 1; /** * Right-to-left? */ private bool $rightToLeft = false; /** * Hyperlinks. Indexed by cell coordinate, e.g. 'A1'. */ private array $hyperlinkCollection = []; /** * Data validation objects. Indexed by cell coordinate, e.g. 'A1'. * Index can include ranges, and multiple cells/ranges. */ private array $dataValidationCollection = []; /** * Tab color. */ private ?Color $tabColor = null; /** * Hash. */ private int $hash; /** * CodeName. */ private ?string $codeName = null; /** * Create a new worksheet. */ public function __construct(?Spreadsheet $parent = null, string $title = 'Worksheet') { // Set parent and title $this->parent = $parent; $this->hash = spl_object_id($this); $this->setTitle($title, false); // setTitle can change $pTitle $this->setCodeName($this->getTitle()); $this->setSheetState(self::SHEETSTATE_VISIBLE); $this->cellCollection = CellsFactory::getInstance($this); // Set page setup $this->pageSetup = new PageSetup(); // Set page margins $this->pageMargins = new PageMargins(); // Set page header/footer $this->headerFooter = new HeaderFooter(); // Set sheet view $this->sheetView = new SheetView(); // Drawing collection $this->drawingCollection = new ArrayObject(); // Chart collection $this->chartCollection = new ArrayObject(); // Protection $this->protection = new Protection(); // Default row dimension $this->defaultRowDimension = new RowDimension(null); // Default column dimension $this->defaultColumnDimension = new ColumnDimension(null); // AutoFilter $this->autoFilter = new AutoFilter('', $this); // Table collection $this->tableCollection = new ArrayObject(); } /** * Disconnect all cells from this Worksheet object, * typically so that the worksheet object can be unset. */ public function disconnectCells(): void { if (isset($this->cellCollection)) { $this->cellCollection->unsetWorksheetCells(); unset($this->cellCollection); } // detach ourself from the workbook, so that it can then delete this worksheet successfully $this->parent = null; } /** * Code to execute when this worksheet is unset(). */ public function __destruct() { Calculation::getInstance($this->parent)->clearCalculationCacheForWorksheet($this->title); $this->disconnectCells(); unset($this->rowDimensions, $this->columnDimensions, $this->tableCollection, $this->drawingCollection, $this->chartCollection, $this->autoFilter); } public function __wakeup(): void { $this->hash = spl_object_id($this); } /** * Return the cell collection. */ public function getCellCollection(): Cells { return $this->cellCollection; } /** * Get array of invalid characters for sheet title. */ public static function getInvalidCharacters(): array { return self::INVALID_CHARACTERS; } /** * Check sheet code name for valid Excel syntax. * * @param string $sheetCodeName The string to check * * @return string The valid string */ private static function checkSheetCodeName(string $sheetCodeName): string { $charCount = StringHelper::countCharacters($sheetCodeName); if ($charCount == 0) { throw new Exception('Sheet code name cannot be empty.'); } // Some of the printable ASCII characters are invalid: * : / \ ? [ ] and first and last characters cannot be a "'" if ( (str_replace(self::INVALID_CHARACTERS, '', $sheetCodeName) !== $sheetCodeName) || (StringHelper::substring($sheetCodeName, -1, 1) == '\'') || (StringHelper::substring($sheetCodeName, 0, 1) == '\'') ) { throw new Exception('Invalid character found in sheet code name'); } // Enforce maximum characters allowed for sheet title if ($charCount > self::SHEET_TITLE_MAXIMUM_LENGTH) { throw new Exception('Maximum ' . self::SHEET_TITLE_MAXIMUM_LENGTH . ' characters allowed in sheet code name.'); } return $sheetCodeName; } /** * Check sheet title for valid Excel syntax. * * @param string $sheetTitle The string to check * * @return string The valid string */ private static function checkSheetTitle(string $sheetTitle): string { // Some of the printable ASCII characters are invalid: * : / \ ? [ ] if (str_replace(self::INVALID_CHARACTERS, '', $sheetTitle) !== $sheetTitle) { throw new Exception('Invalid character found in sheet title'); } // Enforce maximum characters allowed for sheet title if (StringHelper::countCharacters($sheetTitle) > self::SHEET_TITLE_MAXIMUM_LENGTH) { throw new Exception('Maximum ' . self::SHEET_TITLE_MAXIMUM_LENGTH . ' characters allowed in sheet title.'); } return $sheetTitle; } /** * Get a sorted list of all cell coordinates currently held in the collection by row and column. * * @param bool $sorted Also sort the cell collection? * * @return string[] */ public function getCoordinates(bool $sorted = true): array { if (!isset($this->cellCollection)) { return []; } if ($sorted) { return $this->cellCollection->getSortedCoordinates(); } return $this->cellCollection->getCoordinates(); } /** * Get collection of row dimensions. * * @return RowDimension[] */ public function getRowDimensions(): array { return $this->rowDimensions; } /** * Get default row dimension. */ public function getDefaultRowDimension(): RowDimension { return $this->defaultRowDimension; } /** * Get collection of column dimensions. * * @return ColumnDimension[] */ public function getColumnDimensions(): array { /** @var callable $callable */ $callable = [self::class, 'columnDimensionCompare']; uasort($this->columnDimensions, $callable); return $this->columnDimensions; } private static function columnDimensionCompare(ColumnDimension $a, ColumnDimension $b): int { return $a->getColumnNumeric() - $b->getColumnNumeric(); } /** * Get default column dimension. */ public function getDefaultColumnDimension(): ColumnDimension { return $this->defaultColumnDimension; } /** * Get collection of drawings. * * @return ArrayObject */ public function getDrawingCollection(): ArrayObject { return $this->drawingCollection; } /** * Get collection of charts. * * @return ArrayObject */ public function getChartCollection(): ArrayObject { return $this->chartCollection; } public function addChart(Chart $chart): Chart { $chart->setWorksheet($this); $this->chartCollection[] = $chart; return $chart; } /** * Return the count of charts on this worksheet. * * @return int The number of charts */ public function getChartCount(): int { return count($this->chartCollection); } /** * Get a chart by its index position. * * @param ?string $index Chart index position * * @return Chart|false */ public function getChartByIndex(?string $index) { $chartCount = count($this->chartCollection); if ($chartCount == 0) { return false; } if ($index === null) { $index = --$chartCount; } if (!isset($this->chartCollection[$index])) { return false; } return $this->chartCollection[$index]; } /** * Return an array of the names of charts on this worksheet. * * @return string[] The names of charts */ public function getChartNames(): array { $chartNames = []; foreach ($this->chartCollection as $chart) { $chartNames[] = $chart->getName(); } return $chartNames; } /** * Get a chart by name. * * @param string $chartName Chart name * * @return Chart|false */ public function getChartByName(string $chartName) { foreach ($this->chartCollection as $index => $chart) { if ($chart->getName() == $chartName) { return $chart; } } return false; } public function getChartByNameOrThrow(string $chartName): Chart { $chart = $this->getChartByName($chartName); if ($chart !== false) { return $chart; } throw new Exception("Sheet does not have a chart named $chartName."); } /** * Refresh column dimensions. * * @return $this */ public function refreshColumnDimensions() { $newColumnDimensions = []; foreach ($this->getColumnDimensions() as $objColumnDimension) { $newColumnDimensions[$objColumnDimension->getColumnIndex()] = $objColumnDimension; } $this->columnDimensions = $newColumnDimensions; return $this; } /** * Refresh row dimensions. * * @return $this */ public function refreshRowDimensions() { $newRowDimensions = []; foreach ($this->getRowDimensions() as $objRowDimension) { $newRowDimensions[$objRowDimension->getRowIndex()] = $objRowDimension; } $this->rowDimensions = $newRowDimensions; return $this; } /** * Calculate worksheet dimension. * * @return string String containing the dimension of this worksheet */ public function calculateWorksheetDimension(): string { // Return return 'A1:' . $this->getHighestColumn() . $this->getHighestRow(); } /** * Calculate worksheet data dimension. * * @return string String containing the dimension of this worksheet that actually contain data */ public function calculateWorksheetDataDimension(): string { // Return return 'A1:' . $this->getHighestDataColumn() . $this->getHighestDataRow(); } /** * Calculate widths for auto-size columns. * * @return $this */ public function calculateColumnWidths() { $activeSheet = ($nullsafeVariable1 = $this->getParent()) ? $nullsafeVariable1->getActiveSheetIndex() : null; $selectedCells = $this->selectedCells; // initialize $autoSizes array $autoSizes = []; foreach ($this->getColumnDimensions() as $colDimension) { if ($colDimension->getAutoSize()) { $autoSizes[$colDimension->getColumnIndex()] = -1; } } // There is only something to do if there are some auto-size columns if (!empty($autoSizes)) { $holdActivePane = $this->activePane; // build list of cells references that participate in a merge $isMergeCell = []; foreach ($this->getMergeCells() as $cells) { foreach (Coordinate::extractAllCellReferencesInRange($cells) as $cellReference) { $isMergeCell[$cellReference] = true; } } $autoFilterIndentRanges = (new AutoFit($this))->getAutoFilterIndentRanges(); // loop through all cells in the worksheet foreach ($this->getCoordinates(false) as $coordinate) { $cell = $this->getCellOrNull($coordinate); if ($cell !== null && isset($autoSizes[$this->cellCollection->getCurrentColumn()])) { //Determine if cell is in merge range $isMerged = isset($isMergeCell[$this->cellCollection->getCurrentCoordinate()]); //By default merged cells should be ignored $isMergedButProceed = false; //The only exception is if it's a merge range value cell of a 'vertical' range (1 column wide) if ($isMerged && $cell->isMergeRangeValueCell()) { $range = (string) $cell->getMergeRange(); $rangeBoundaries = Coordinate::rangeDimension($range); if ($rangeBoundaries[0] === 1) { $isMergedButProceed = true; } } // Determine width if cell is not part of a merge or does and is a value cell of 1-column wide range if (!$isMerged || $isMergedButProceed) { // Determine if we need to make an adjustment for the first row in an AutoFilter range that // has a column filter dropdown $filterAdjustment = false; if (!empty($autoFilterIndentRanges)) { foreach ($autoFilterIndentRanges as $autoFilterFirstRowRange) { if ($cell->isInRange($autoFilterFirstRowRange)) { $filterAdjustment = true; break; } } } $indentAdjustment = $cell->getStyle()->getAlignment()->getIndent(); $indentAdjustment += (int) ($cell->getStyle()->getAlignment()->getHorizontal() === Alignment::HORIZONTAL_CENTER); // Calculated value // To formatted string $cellValue = NumberFormat::toFormattedString( $cell->getCalculatedValueString(), (string) $this->getParentOrThrow()->getCellXfByIndex($cell->getXfIndex()) ->getNumberFormat()->getFormatCode(true) ); if ($cellValue !== '') { $autoSizes[$this->cellCollection->getCurrentColumn()] = max( $autoSizes[$this->cellCollection->getCurrentColumn()], round( Shared\Font::calculateColumnWidth( $this->getParentOrThrow()->getCellXfByIndex($cell->getXfIndex())->getFont(), $cellValue, (int) $this->getParentOrThrow()->getCellXfByIndex($cell->getXfIndex()) ->getAlignment()->getTextRotation(), $this->getParentOrThrow()->getDefaultStyle()->getFont(), $filterAdjustment, $indentAdjustment ), 3 ) ); } } } } // adjust column widths foreach ($autoSizes as $columnIndex => $width) { if ($width == -1) { $width = $this->getDefaultColumnDimension()->getWidth(); } $this->getColumnDimension($columnIndex)->setWidth($width); } $this->activePane = $holdActivePane; } if ($activeSheet !== null && $activeSheet >= 0) { ($nullsafeVariable2 = $this->getParent()) ? $nullsafeVariable2->setActiveSheetIndex($activeSheet) : null; } $this->setSelectedCells($selectedCells); return $this; } /** * Get parent or null. */ public function getParent(): ?Spreadsheet { return $this->parent; } /** * Get parent, throw exception if null. */ public function getParentOrThrow(): Spreadsheet { if ($this->parent !== null) { return $this->parent; } throw new Exception('Sheet does not have a parent.'); } /** * Re-bind parent. * * @return $this */ public function rebindParent(Spreadsheet $parent) { if ($this->parent !== null) { $definedNames = $this->parent->getDefinedNames(); foreach ($definedNames as $definedName) { $parent->addDefinedName($definedName); } $this->parent->removeSheetByIndex( $this->parent->getIndex($this) ); } $this->parent = $parent; return $this; } public function setParent(Spreadsheet $parent): self { $this->parent = $parent; return $this; } /** * Get title. */ public function getTitle(): string { return $this->title; } /** * Set title. * * @param string $title String containing the dimension of this worksheet * @param bool $updateFormulaCellReferences Flag indicating whether cell references in formulae should * be updated to reflect the new sheet name. * This should be left as the default true, unless you are * certain that no formula cells on any worksheet contain * references to this worksheet * @param bool $validate False to skip validation of new title. WARNING: This should only be set * at parse time (by Readers), where titles can be assumed to be valid. * * @return $this */ public function setTitle(string $title, bool $updateFormulaCellReferences = true, bool $validate = true) { // Is this a 'rename' or not? if ($this->getTitle() == $title) { return $this; } // Old title $oldTitle = $this->getTitle(); if ($validate) { // Syntax check self::checkSheetTitle($title); if ($this->parent && $this->parent->getIndex($this, true) >= 0) { // Is there already such sheet name? if ($this->parent->sheetNameExists($title)) { // Use name, but append with lowest possible integer if (StringHelper::countCharacters($title) > 29) { $title = StringHelper::substring($title, 0, 29); } $i = 1; while ($this->parent->sheetNameExists($title . ' ' . $i)) { ++$i; if ($i == 10) { if (StringHelper::countCharacters($title) > 28) { $title = StringHelper::substring($title, 0, 28); } } elseif ($i == 100) { if (StringHelper::countCharacters($title) > 27) { $title = StringHelper::substring($title, 0, 27); } } } $title .= " $i"; } } } // Set title $this->title = $title; if ($this->parent && $this->parent->getIndex($this, true) >= 0 && $this->parent->getCalculationEngine()) { // New title $newTitle = $this->getTitle(); $this->parent->getCalculationEngine() ->renameCalculationCacheForWorksheet($oldTitle, $newTitle); if ($updateFormulaCellReferences) { ReferenceHelper::getInstance()->updateNamedFormulae($this->parent, $oldTitle, $newTitle); } } return $this; } /** * Get sheet state. * * @return string Sheet state (visible, hidden, veryHidden) */ public function getSheetState(): string { return $this->sheetState; } /** * Set sheet state. * * @param string $value Sheet state (visible, hidden, veryHidden) * * @return $this */ public function setSheetState(string $value) { $this->sheetState = $value; return $this; } /** * Get page setup. */ public function getPageSetup(): PageSetup { return $this->pageSetup; } /** * Set page setup. * * @return $this */ public function setPageSetup(PageSetup $pageSetup) { $this->pageSetup = $pageSetup; return $this; } /** * Get page margins. */ public function getPageMargins(): PageMargins { return $this->pageMargins; } /** * Set page margins. * * @return $this */ public function setPageMargins(PageMargins $pageMargins) { $this->pageMargins = $pageMargins; return $this; } /** * Get page header/footer. */ public function getHeaderFooter(): HeaderFooter { return $this->headerFooter; } /** * Set page header/footer. * * @return $this */ public function setHeaderFooter(HeaderFooter $headerFooter) { $this->headerFooter = $headerFooter; return $this; } /** * Get sheet view. */ public function getSheetView(): SheetView { return $this->sheetView; } /** * Set sheet view. * * @return $this */ public function setSheetView(SheetView $sheetView) { $this->sheetView = $sheetView; return $this; } /** * Get Protection. */ public function getProtection(): Protection { return $this->protection; } /** * Set Protection. * * @return $this */ public function setProtection(Protection $protection) { $this->protection = $protection; return $this; } /** * Get highest worksheet column. * * @param null|int|string $row Return the data highest column for the specified row, * or the highest column of any row if no row number is passed * * @return string Highest column name */ public function getHighestColumn($row = null): string { if ($row === null) { return Coordinate::stringFromColumnIndex($this->cachedHighestColumn); } return $this->getHighestDataColumn($row); } /** * Get highest worksheet column that contains data. * * @param null|int|string $row Return the highest data column for the specified row, * or the highest data column of any row if no row number is passed * * @return string Highest column name that contains data */ public function getHighestDataColumn($row = null): string { return $this->cellCollection->getHighestColumn($row); } /** * Get highest worksheet row. * * @param null|string $column Return the highest data row for the specified column, * or the highest row of any column if no column letter is passed * * @return int Highest row number */ public function getHighestRow(?string $column = null): int { if ($column === null) { return $this->cachedHighestRow; } return $this->getHighestDataRow($column); } /** * Get highest worksheet row that contains data. * * @param null|string $column Return the highest data row for the specified column, * or the highest data row of any column if no column letter is passed * * @return int Highest row number that contains data */ public function getHighestDataRow(?string $column = null): int { return $this->cellCollection->getHighestRow($column); } /** * Get highest worksheet column and highest row that have cell records. * * @return array Highest column name and highest row number */ public function getHighestRowAndColumn(): array { return $this->cellCollection->getHighestRowAndColumn(); } /** * Set a cell value. * * @param array{0: int, 1: int}|CellAddress|string $coordinate Coordinate of the cell as a string, eg: 'C5'; * or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. * @param mixed $value Value for the cell * @param null|IValueBinder $binder Value Binder to override the currently set Value Binder * * @return $this */ public function setCellValue($coordinate, $value, ?IValueBinder $binder = null) { $cellAddress = Functions::trimSheetFromCellReference(Validations::validateCellAddress($coordinate)); $this->getCell($cellAddress)->setValue($value, $binder); return $this; } /** * Set a cell value. * * @param array{0: int, 1: int}|CellAddress|string $coordinate Coordinate of the cell as a string, eg: 'C5'; * or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. * @param mixed $value Value of the cell * @param string $dataType Explicit data type, see DataType::TYPE_* * Note that PhpSpreadsheet does not validate that the value and datatype are consistent, in using this * method, then it is your responsibility as an end-user developer to validate that the value and * the datatype match. * If you do mismatch value and datatpe, then the value you enter may be changed to match the datatype * that you specify. * * @see DataType * * @return $this */ public function setCellValueExplicit($coordinate, $value, string $dataType) { $cellAddress = Functions::trimSheetFromCellReference(Validations::validateCellAddress($coordinate)); $this->getCell($cellAddress)->setValueExplicit($value, $dataType); return $this; } /** * Get cell at a specific coordinate. * * @param array{0: int, 1: int}|CellAddress|string $coordinate Coordinate of the cell as a string, eg: 'C5'; * or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. * * @return Cell Cell that was found or created * WARNING: Because the cell collection can be cached to reduce memory, it only allows one * "active" cell at a time in memory. If you assign that cell to a variable, then select * another cell using getCell() or any of its variants, the newly selected cell becomes * the "active" cell, and any previous assignment becomes a disconnected reference because * the active cell has changed. */ public function getCell($coordinate): Cell { $cellAddress = Functions::trimSheetFromCellReference(Validations::validateCellAddress($coordinate)); // Shortcut for increased performance for the vast majority of simple cases if ($this->cellCollection->has($cellAddress)) { /** @var Cell $cell */ $cell = $this->cellCollection->get($cellAddress); return $cell; } /** @var Worksheet $sheet */ [$sheet, $finalCoordinate] = $this->getWorksheetAndCoordinate($cellAddress); $cell = $sheet->getCellCollection()->get($finalCoordinate); return $cell ?? $sheet->createNewCell($finalCoordinate); } /** * Get the correct Worksheet and coordinate from a coordinate that may * contains reference to another sheet or a named range. * * @return array{0: Worksheet, 1: string} */ private function getWorksheetAndCoordinate(string $coordinate): array { $sheet = null; $finalCoordinate = null; // Worksheet reference? if (str_contains($coordinate, '!')) { $worksheetReference = self::extractSheetTitle($coordinate, true, true); $sheet = $this->getParentOrThrow()->getSheetByName($worksheetReference[0]); $finalCoordinate = strtoupper($worksheetReference[1]); if ($sheet === null) { throw new Exception('Sheet not found for name: ' . $worksheetReference[0]); } } elseif ( !preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $coordinate) && preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/iu', $coordinate) ) { // Named range? $namedRange = $this->validateNamedRange($coordinate, true); if ($namedRange !== null) { $sheet = $namedRange->getWorksheet(); if ($sheet === null) { throw new Exception('Sheet not found for named range: ' . $namedRange->getName()); } /** @phpstan-ignore-next-line */ $cellCoordinate = ltrim(substr($namedRange->getValue(), strrpos($namedRange->getValue(), '!')), '!'); $finalCoordinate = str_replace('$', '', $cellCoordinate); } } if ($sheet === null || $finalCoordinate === null) { $sheet = $this; $finalCoordinate = strtoupper($coordinate); } if (Coordinate::coordinateIsRange($finalCoordinate)) { throw new Exception('Cell coordinate string can not be a range of cells.'); } $finalCoordinate = str_replace('$', '', $finalCoordinate); return [$sheet, $finalCoordinate]; } /** * Get an existing cell at a specific coordinate, or null. * * @param string $coordinate Coordinate of the cell, eg: 'A1' * * @return null|Cell Cell that was found or null */ private function getCellOrNull(string $coordinate): ?Cell { // Check cell collection if ($this->cellCollection->has($coordinate)) { return $this->cellCollection->get($coordinate); } return null; } /** * Create a new cell at the specified coordinate. * * @param string $coordinate Coordinate of the cell * * @return Cell Cell that was created * WARNING: Because the cell collection can be cached to reduce memory, it only allows one * "active" cell at a time in memory. If you assign that cell to a variable, then select * another cell using getCell() or any of its variants, the newly selected cell becomes * the "active" cell, and any previous assignment becomes a disconnected reference because * the active cell has changed. */ public function createNewCell(string $coordinate): Cell { [$column, $row, $columnString] = Coordinate::indexesFromString($coordinate); $cell = new Cell(null, DataType::TYPE_NULL, $this); $this->cellCollection->add($coordinate, $cell); // Coordinates if ($column > $this->cachedHighestColumn) { $this->cachedHighestColumn = $column; } if ($row > $this->cachedHighestRow) { $this->cachedHighestRow = $row; } // Cell needs appropriate xfIndex from dimensions records // but don't create dimension records if they don't already exist $rowDimension = $this->rowDimensions[$row] ?? null; $columnDimension = $this->columnDimensions[$columnString] ?? null; $xfSet = false; if ($rowDimension !== null) { $rowXf = (int) $rowDimension->getXfIndex(); if ($rowXf > 0) { // then there is a row dimension with explicit style, assign it to the cell $cell->setXfIndex($rowXf); $xfSet = true; } } if (!$xfSet && $columnDimension !== null) { $colXf = (int) $columnDimension->getXfIndex(); if ($colXf > 0) { // then there is a column dimension, assign it to the cell $cell->setXfIndex($colXf); } } return $cell; } /** * Does the cell at a specific coordinate exist? * * @param array{0: int, 1: int}|CellAddress|string $coordinate Coordinate of the cell as a string, eg: 'C5'; * or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. */ public function cellExists($coordinate): bool { $cellAddress = Validations::validateCellAddress($coordinate); [$sheet, $finalCoordinate] = $this->getWorksheetAndCoordinate($cellAddress); return $sheet->getCellCollection()->has($finalCoordinate); } /** * Get row dimension at a specific row. * * @param int $row Numeric index of the row */ public function getRowDimension(int $row): RowDimension { // Get row dimension if (!isset($this->rowDimensions[$row])) { $this->rowDimensions[$row] = new RowDimension($row); $this->cachedHighestRow = max($this->cachedHighestRow, $row); } return $this->rowDimensions[$row]; } public function getRowStyle(int $row): ?Style { return ($nullsafeVariable3 = $this->parent) ? $nullsafeVariable3->getCellXfByIndexOrNull(($nullsafeVariable4 = $this->rowDimensions[$row] ?? null) ? $nullsafeVariable4->getXfIndex() : null) : null; } public function rowDimensionExists(int $row): bool { return isset($this->rowDimensions[$row]); } public function columnDimensionExists(string $column): bool { return isset($this->columnDimensions[$column]); } /** * Get column dimension at a specific column. * * @param string $column String index of the column eg: 'A' */ public function getColumnDimension(string $column): ColumnDimension { // Uppercase coordinate $column = strtoupper($column); // Fetch dimensions if (!isset($this->columnDimensions[$column])) { $this->columnDimensions[$column] = new ColumnDimension($column); $columnIndex = Coordinate::columnIndexFromString($column); if ($this->cachedHighestColumn < $columnIndex) { $this->cachedHighestColumn = $columnIndex; } } return $this->columnDimensions[$column]; } /** * Get column dimension at a specific column by using numeric cell coordinates. * * @param int $columnIndex Numeric column coordinate of the cell */ public function getColumnDimensionByColumn(int $columnIndex): ColumnDimension { return $this->getColumnDimension(Coordinate::stringFromColumnIndex($columnIndex)); } public function getColumnStyle(string $column): ?Style { return ($nullsafeVariable5 = $this->parent) ? $nullsafeVariable5->getCellXfByIndexOrNull(($nullsafeVariable6 = $this->columnDimensions[$column] ?? null) ? $nullsafeVariable6->getXfIndex() : null) : null; } /** * Get style for cell. * * @param AddressRange|AddressRange|AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|CellAddress|int|string $cellCoordinate * A simple string containing a cell address like 'A1' or a cell range like 'A1:E10' * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), * or a CellAddress or AddressRange object. */ public function getStyle($cellCoordinate): Style { if (is_string($cellCoordinate)) { $cellCoordinate = Validations::definedNameToCoordinate($cellCoordinate, $this); } $cellCoordinate = Validations::validateCellOrCellRange($cellCoordinate); $cellCoordinate = str_replace('$', '', $cellCoordinate); // set this sheet as active $this->getParentOrThrow()->setActiveSheetIndex($this->getParentOrThrow()->getIndex($this)); // set cell coordinate as active $this->setSelectedCells($cellCoordinate); return $this->getParentOrThrow()->getCellXfSupervisor(); } /** * Get table styles set for the for given cell. * * @param Cell $cell * The Cell for which the tables are retrieved */ public function getTablesWithStylesForCell(Cell $cell): array { $retVal = []; foreach ($this->tableCollection as $table) { /** @var Table $table */ $dxfsTableStyle = $table->getStyle()->getTableDxfsStyle(); if ($dxfsTableStyle !== null) { if ($dxfsTableStyle->getHeaderRowStyle() !== null || $dxfsTableStyle->getFirstRowStripeStyle() !== null || $dxfsTableStyle->getSecondRowStripeStyle() !== null) { $range = $table->getRange(); if ($cell->isInRange($range)) { $retVal[] = $table; } } } } return $retVal; } /** * Get conditional styles for a cell. * * @param string $coordinate eg: 'A1' or 'A1:A3'. * If a single cell is referenced, then the array of conditional styles will be returned if the cell is * included in a conditional style range. * If a range of cells is specified, then the styles will only be returned if the range matches the entire * range of the conditional. * @param bool $firstOnly default true, return all matching * conditionals ordered by priority if false, first only if true * * @return Conditional[] */ public function getConditionalStyles(string $coordinate, bool $firstOnly = true): array { $coordinate = strtoupper($coordinate); if (preg_match('/[: ,]/', $coordinate) === 1) { return $this->conditionalStylesCollection[$coordinate] ?? []; } $conditionalStyles = []; foreach ($this->conditionalStylesCollection as $keyStylesOrig => $conditionalRange) { $keyStyles = Coordinate::resolveUnionAndIntersection($keyStylesOrig); $keyParts = explode(',', $keyStyles); foreach ($keyParts as $keyPart) { if ($keyPart === $coordinate) { if ($firstOnly) { return $conditionalRange; } $conditionalStyles[$keyStylesOrig] = $conditionalRange; break; } elseif (str_contains($keyPart, ':')) { if (Coordinate::coordinateIsInsideRange($keyPart, $coordinate)) { if ($firstOnly) { return $conditionalRange; } $conditionalStyles[$keyStylesOrig] = $conditionalRange; break; } } } } $outArray = []; foreach ($conditionalStyles as $conditionalArray) { foreach ($conditionalArray as $conditional) { $outArray[] = $conditional; } } usort($outArray, [self::class, 'comparePriority']); return $outArray; } private static function comparePriority(Conditional $condA, Conditional $condB): int { $a = $condA->getPriority(); $b = $condB->getPriority(); if ($a === $b) { return 0; } if ($a === 0) { return 1; } if ($b === 0) { return -1; } return ($a < $b) ? -1 : 1; } public function getConditionalRange(string $coordinate): ?string { $coordinate = strtoupper($coordinate); $cell = $this->getCell($coordinate); foreach (array_keys($this->conditionalStylesCollection) as $conditionalRange) { $cellBlocks = explode(',', Coordinate::resolveUnionAndIntersection($conditionalRange)); foreach ($cellBlocks as $cellBlock) { if ($cell->isInRange($cellBlock)) { return $conditionalRange; } } } return null; } /** * Do conditional styles exist for this cell? * * @param string $coordinate eg: 'A1' or 'A1:A3'. * If a single cell is specified, then this method will return true if that cell is included in a * conditional style range. * If a range of cells is specified, then true will only be returned if the range matches the entire * range of the conditional. */ public function conditionalStylesExists(string $coordinate): bool { return !empty($this->getConditionalStyles($coordinate)); } /** * Removes conditional styles for a cell. * * @param string $coordinate eg: 'A1' * * @return $this */ public function removeConditionalStyles(string $coordinate) { unset($this->conditionalStylesCollection[strtoupper($coordinate)]); return $this; } /** * Get collection of conditional styles. */ public function getConditionalStylesCollection(): array { return $this->conditionalStylesCollection; } /** * Set conditional styles. * * @param string $coordinate eg: 'A1' * @param Conditional[] $styles * * @return $this */ public function setConditionalStyles(string $coordinate, array $styles) { $this->conditionalStylesCollection[strtoupper($coordinate)] = $styles; return $this; } /** * Duplicate cell style to a range of cells. * * Please note that this will overwrite existing cell styles for cells in range! * * @param Style $style Cell style to duplicate * @param string $range Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1") * * @return $this */ public function duplicateStyle(Style $style, string $range) { // Add the style to the workbook if necessary $workbook = $this->getParentOrThrow(); if ($existingStyle = $workbook->getCellXfByHashCode($style->getHashCode())) { // there is already such cell Xf in our collection $xfIndex = $existingStyle->getIndex(); } else { // we don't have such a cell Xf, need to add $workbook->addCellXf($style); $xfIndex = $style->getIndex(); } // Calculate range outer borders [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($range . ':' . $range); // Make sure we can loop upwards on rows and columns if ($rangeStart[0] > $rangeEnd[0] && $rangeStart[1] > $rangeEnd[1]) { $tmp = $rangeStart; $rangeStart = $rangeEnd; $rangeEnd = $tmp; } // Loop through cells and apply styles for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) { for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) { $this->getCell(Coordinate::stringFromColumnIndex($col) . $row)->setXfIndex($xfIndex); } } return $this; } /** * Duplicate conditional style to a range of cells. * * Please note that this will overwrite existing cell styles for cells in range! * * @param Conditional[] $styles Cell style to duplicate * @param string $range Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1") * * @return $this */ public function duplicateConditionalStyle(array $styles, string $range = '') { foreach ($styles as $cellStyle) { if (!($cellStyle instanceof Conditional)) { // @phpstan-ignore-line throw new Exception('Style is not a conditional style'); } } // Calculate range outer borders [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($range . ':' . $range); // Make sure we can loop upwards on rows and columns if ($rangeStart[0] > $rangeEnd[0] && $rangeStart[1] > $rangeEnd[1]) { $tmp = $rangeStart; $rangeStart = $rangeEnd; $rangeEnd = $tmp; } // Loop through cells and apply styles for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) { for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) { $this->setConditionalStyles(Coordinate::stringFromColumnIndex($col) . $row, $styles); } } return $this; } /** * Set break on a cell. * * @param array{0: int, 1: int}|CellAddress|string $coordinate Coordinate of the cell as a string, eg: 'C5'; * or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. * @param int $break Break type (type of Worksheet::BREAK_*) * * @return $this */ public function setBreak($coordinate, int $break, int $max = -1) { $cellAddress = Functions::trimSheetFromCellReference(Validations::validateCellAddress($coordinate)); if ($break === self::BREAK_NONE) { unset($this->rowBreaks[$cellAddress], $this->columnBreaks[$cellAddress]); } elseif ($break === self::BREAK_ROW) { $this->rowBreaks[$cellAddress] = new PageBreak($break, $cellAddress, $max); } elseif ($break === self::BREAK_COLUMN) { $this->columnBreaks[$cellAddress] = new PageBreak($break, $cellAddress, $max); } return $this; } /** * Get breaks. * * @return int[] */ public function getBreaks(): array { $breaks = []; /** @var callable $compareFunction */ $compareFunction = [self::class, 'compareRowBreaks']; uksort($this->rowBreaks, $compareFunction); foreach ($this->rowBreaks as $break) { $breaks[$break->getCoordinate()] = self::BREAK_ROW; } /** @var callable $compareFunction */ $compareFunction = [self::class, 'compareColumnBreaks']; uksort($this->columnBreaks, $compareFunction); foreach ($this->columnBreaks as $break) { $breaks[$break->getCoordinate()] = self::BREAK_COLUMN; } return $breaks; } /** * Get row breaks. * * @return PageBreak[] */ public function getRowBreaks(): array { /** @var callable $compareFunction */ $compareFunction = [self::class, 'compareRowBreaks']; uksort($this->rowBreaks, $compareFunction); return $this->rowBreaks; } protected static function compareRowBreaks(string $coordinate1, string $coordinate2): int { $row1 = Coordinate::indexesFromString($coordinate1)[1]; $row2 = Coordinate::indexesFromString($coordinate2)[1]; return $row1 - $row2; } protected static function compareColumnBreaks(string $coordinate1, string $coordinate2): int { $column1 = Coordinate::indexesFromString($coordinate1)[0]; $column2 = Coordinate::indexesFromString($coordinate2)[0]; return $column1 - $column2; } /** * Get column breaks. * * @return PageBreak[] */ public function getColumnBreaks(): array { /** @var callable $compareFunction */ $compareFunction = [self::class, 'compareColumnBreaks']; uksort($this->columnBreaks, $compareFunction); return $this->columnBreaks; } /** * Set merge on a cell range. * * @param AddressRange|AddressRange|AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $range A simple string containing a Cell range like 'A1:E10' * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), * or an AddressRange. * @param string $behaviour How the merged cells should behave. * Possible values are: * MERGE_CELL_CONTENT_EMPTY - Empty the content of the hidden cells * MERGE_CELL_CONTENT_HIDE - Keep the content of the hidden cells * MERGE_CELL_CONTENT_MERGE - Move the content of the hidden cells into the first cell * * @return $this */ public function mergeCells($range, string $behaviour = self::MERGE_CELL_CONTENT_EMPTY) { $range = Functions::trimSheetFromCellReference(Validations::validateCellRange($range)); if (!str_contains($range, ':')) { $range .= ":{$range}"; } if (preg_match('/^([A-Z]+)(\d+):([A-Z]+)(\d+)$/', $range, $matches) !== 1) { throw new Exception('Merge must be on a valid range of cells.'); } $this->mergeCells[$range] = $range; $firstRow = (int) $matches[2]; $lastRow = (int) $matches[4]; $firstColumn = $matches[1]; $lastColumn = $matches[3]; $firstColumnIndex = Coordinate::columnIndexFromString($firstColumn); $lastColumnIndex = Coordinate::columnIndexFromString($lastColumn); $numberRows = $lastRow - $firstRow; $numberColumns = $lastColumnIndex - $firstColumnIndex; if ($numberRows === 1 && $numberColumns === 1) { return $this; } // create upper left cell if it does not already exist $upperLeft = "{$firstColumn}{$firstRow}"; if (!$this->cellExists($upperLeft)) { $this->getCell($upperLeft)->setValueExplicit(null, DataType::TYPE_NULL); } if ($behaviour !== self::MERGE_CELL_CONTENT_HIDE) { // Blank out the rest of the cells in the range (if they exist) if ($numberRows > $numberColumns) { $this->clearMergeCellsByColumn($firstColumn, $lastColumn, $firstRow, $lastRow, $upperLeft, $behaviour); } else { $this->clearMergeCellsByRow($firstColumn, $lastColumnIndex, $firstRow, $lastRow, $upperLeft, $behaviour); } } return $this; } private function clearMergeCellsByColumn(string $firstColumn, string $lastColumn, int $firstRow, int $lastRow, string $upperLeft, string $behaviour): void { $leftCellValue = ($behaviour === self::MERGE_CELL_CONTENT_MERGE) ? [$this->getCell($upperLeft)->getFormattedValue()] : []; foreach ($this->getColumnIterator($firstColumn, $lastColumn) as $column) { $iterator = $column->getCellIterator($firstRow); $iterator->setIterateOnlyExistingCells(true); foreach ($iterator as $cell) { $row = $cell->getRow(); if ($row > $lastRow) { break; } $leftCellValue = $this->mergeCellBehaviour($cell, $upperLeft, $behaviour, $leftCellValue); } } if ($behaviour === self::MERGE_CELL_CONTENT_MERGE) { $this->getCell($upperLeft)->setValueExplicit(implode(' ', $leftCellValue), DataType::TYPE_STRING); } } private function clearMergeCellsByRow(string $firstColumn, int $lastColumnIndex, int $firstRow, int $lastRow, string $upperLeft, string $behaviour): void { $leftCellValue = ($behaviour === self::MERGE_CELL_CONTENT_MERGE) ? [$this->getCell($upperLeft)->getFormattedValue()] : []; foreach ($this->getRowIterator($firstRow, $lastRow) as $row) { $iterator = $row->getCellIterator($firstColumn); $iterator->setIterateOnlyExistingCells(true); foreach ($iterator as $cell) { $column = $cell->getColumn(); $columnIndex = Coordinate::columnIndexFromString($column); if ($columnIndex > $lastColumnIndex) { break; } $leftCellValue = $this->mergeCellBehaviour($cell, $upperLeft, $behaviour, $leftCellValue); } } if ($behaviour === self::MERGE_CELL_CONTENT_MERGE) { $this->getCell($upperLeft)->setValueExplicit(implode(' ', $leftCellValue), DataType::TYPE_STRING); } } public function mergeCellBehaviour(Cell $cell, string $upperLeft, string $behaviour, array $leftCellValue): array { if ($cell->getCoordinate() !== $upperLeft) { Calculation::getInstance($cell->getWorksheet()->getParentOrThrow())->flushInstance(); if ($behaviour === self::MERGE_CELL_CONTENT_MERGE) { $cellValue = $cell->getFormattedValue(); if ($cellValue !== '') { $leftCellValue[] = $cellValue; } } $cell->setValueExplicit(null, DataType::TYPE_NULL); } return $leftCellValue; } /** * Remove merge on a cell range. * * @param AddressRange|AddressRange|AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $range A simple string containing a Cell range like 'A1:E10' * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), * or an AddressRange. * * @return $this */ public function unmergeCells($range) { $range = Functions::trimSheetFromCellReference(Validations::validateCellRange($range)); if (str_contains($range, ':')) { if (isset($this->mergeCells[$range])) { unset($this->mergeCells[$range]); } else { throw new Exception('Cell range ' . $range . ' not known as merged.'); } } else { throw new Exception('Merge can only be removed from a range of cells.'); } return $this; } /** * Get merge cells array. * * @return string[] */ public function getMergeCells(): array { return $this->mergeCells; } /** * Set merge cells array for the entire sheet. Use instead mergeCells() to merge * a single cell range. * * @param string[] $mergeCells * * @return $this */ public function setMergeCells(array $mergeCells) { $this->mergeCells = $mergeCells; return $this; } /** * Set protection on a cell or cell range. * * @param AddressRange|AddressRange|AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|CellAddress|int|string $range A simple string containing a Cell range like 'A1:E10' * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), * or a CellAddress or AddressRange object. * @param string $password Password to unlock the protection * @param bool $alreadyHashed If the password has already been hashed, set this to true * * @return $this */ public function protectCells($range, string $password = '', bool $alreadyHashed = false, string $name = '', string $securityDescriptor = '') { $range = Functions::trimSheetFromCellReference(Validations::validateCellOrCellRange($range)); if (!$alreadyHashed && $password !== '') { $password = Shared\PasswordHasher::hashPassword($password); } $this->protectedCells[$range] = new ProtectedRange($range, $password, $name, $securityDescriptor); return $this; } /** * Remove protection on a cell or cell range. * * @param AddressRange|AddressRange|AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|CellAddress|int|string $range A simple string containing a Cell range like 'A1:E10' * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), * or a CellAddress or AddressRange object. * * @return $this */ public function unprotectCells($range) { $range = Functions::trimSheetFromCellReference(Validations::validateCellOrCellRange($range)); if (isset($this->protectedCells[$range])) { unset($this->protectedCells[$range]); } else { throw new Exception('Cell range ' . $range . ' not known as protected.'); } return $this; } /** * Get protected cells. * * @return ProtectedRange[] */ public function getProtectedCellRanges(): array { return $this->protectedCells; } /** * Get Autofilter. */ public function getAutoFilter(): AutoFilter { return $this->autoFilter; } /** * Set AutoFilter. * * @param AddressRange|AddressRange|AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|AutoFilter|string $autoFilterOrRange * A simple string containing a Cell range like 'A1:E10' is permitted for backward compatibility * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), * or an AddressRange. * * @return $this */ public function setAutoFilter($autoFilterOrRange) { if (is_object($autoFilterOrRange) && ($autoFilterOrRange instanceof AutoFilter)) { $this->autoFilter = $autoFilterOrRange; } else { $cellRange = Functions::trimSheetFromCellReference(Validations::validateCellRange($autoFilterOrRange)); $this->autoFilter->setRange($cellRange); } return $this; } /** * Remove autofilter. */ public function removeAutoFilter(): self { $this->autoFilter->setRange(''); return $this; } /** * Get collection of Tables. * * @return ArrayObject */ public function getTableCollection(): ArrayObject { return $this->tableCollection; } /** * Add Table. * * @return $this */ public function addTable(Table $table): self { $table->setWorksheet($this); $this->tableCollection[] = $table; return $this; } /** * @return string[] array of Table names */ public function getTableNames(): array { $tableNames = []; foreach ($this->tableCollection as $table) { /** @var Table $table */ $tableNames[] = $table->getName(); } return $tableNames; } /** * @param string $name the table name to search * * @return null|Table The table from the tables collection, or null if not found */ public function getTableByName(string $name): ?Table { $tableIndex = $this->getTableIndexByName($name); return ($tableIndex === null) ? null : $this->tableCollection[$tableIndex]; } /** * @param string $name the table name to search * * @return null|int The index of the located table in the tables collection, or null if not found */ protected function getTableIndexByName(string $name): ?int { $name = StringHelper::strToUpper($name); foreach ($this->tableCollection as $index => $table) { /** @var Table $table */ if (StringHelper::strToUpper($table->getName()) === $name) { return $index; } } return null; } /** * Remove Table by name. * * @param string $name Table name * * @return $this */ public function removeTableByName(string $name): self { $tableIndex = $this->getTableIndexByName($name); if ($tableIndex !== null) { unset($this->tableCollection[$tableIndex]); } return $this; } /** * Remove collection of Tables. */ public function removeTableCollection(): self { $this->tableCollection = new ArrayObject(); return $this; } /** * Get Freeze Pane. */ public function getFreezePane(): ?string { return $this->freezePane; } /** * Freeze Pane. * * Examples: * * - A2 will freeze the rows above cell A2 (i.e row 1) * - B1 will freeze the columns to the left of cell B1 (i.e column A) * - B2 will freeze the rows above and to the left of cell B2 (i.e row 1 and column A) * * @param null|array{0: int, 1: int}|CellAddress|string $coordinate Coordinate of the cell as a string, eg: 'C5'; * or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. * Passing a null value for this argument will clear any existing freeze pane for this worksheet. * @param null|array{0: int, 1: int}|CellAddress|string $topLeftCell default position of the right bottom pane * Coordinate of the cell as a string, eg: 'C5'; or as an array of [$columnIndex, $row] (e.g. [3, 5]), * or a CellAddress object. * * @return $this */ public function freezePane($coordinate, $topLeftCell = null, bool $frozenSplit = false) { $this->panes = [ 'bottomRight' => null, 'bottomLeft' => null, 'topRight' => null, 'topLeft' => null, ]; $cellAddress = ($coordinate !== null) ? Functions::trimSheetFromCellReference(Validations::validateCellAddress($coordinate)) : null; if ($cellAddress !== null && Coordinate::coordinateIsRange($cellAddress)) { throw new Exception('Freeze pane can not be set on a range of cells.'); } $topLeftCell = ($topLeftCell !== null) ? Functions::trimSheetFromCellReference(Validations::validateCellAddress($topLeftCell)) : null; if ($cellAddress !== null && $topLeftCell === null) { $coordinate = Coordinate::coordinateFromString($cellAddress); $topLeftCell = $coordinate[0] . $coordinate[1]; } $topLeftCell = "$topLeftCell"; $this->paneTopLeftCell = $topLeftCell; $this->freezePane = $cellAddress; $this->topLeftCell = $topLeftCell; if ($cellAddress === null) { $this->paneState = ''; $this->xSplit = $this->ySplit = 0; $this->activePane = ''; } else { $coordinates = Coordinate::indexesFromString($cellAddress); $this->xSplit = $coordinates[0] - 1; $this->ySplit = $coordinates[1] - 1; if ($this->xSplit > 0 || $this->ySplit > 0) { $this->paneState = $frozenSplit ? self::PANE_FROZENSPLIT : self::PANE_FROZEN; $this->setSelectedCellsActivePane(); } else { $this->paneState = ''; $this->freezePane = null; $this->activePane = ''; } } return $this; } public function setTopLeftCell(string $topLeftCell): self { $this->topLeftCell = $topLeftCell; return $this; } /** * Unfreeze Pane. * * @return $this */ public function unfreezePane() { return $this->freezePane(null); } /** * Get the default position of the right bottom pane. */ public function getTopLeftCell(): ?string { return $this->topLeftCell; } public function getPaneTopLeftCell(): string { return $this->paneTopLeftCell; } public function setPaneTopLeftCell(string $paneTopLeftCell): self { $this->paneTopLeftCell = $paneTopLeftCell; return $this; } public function usesPanes(): bool { return $this->xSplit > 0 || $this->ySplit > 0; } public function getPane(string $position): ?Pane { return $this->panes[$position] ?? null; } public function setPane(string $position, ?Pane $pane): self { if (array_key_exists($position, $this->panes)) { $this->panes[$position] = $pane; } return $this; } /** @return (null|Pane)[] */ public function getPanes(): array { return $this->panes; } public function getActivePane(): string { return $this->activePane; } public function setActivePane(string $activePane): self { $this->activePane = array_key_exists($activePane, $this->panes) ? $activePane : ''; return $this; } public function getXSplit(): int { return $this->xSplit; } public function setXSplit(int $xSplit): self { $this->xSplit = $xSplit; if (in_array($this->paneState, self::VALIDFROZENSTATE, true)) { $this->freezePane([$this->xSplit + 1, $this->ySplit + 1], $this->topLeftCell, $this->paneState === self::PANE_FROZENSPLIT); } return $this; } public function getYSplit(): int { return $this->ySplit; } public function setYSplit(int $ySplit): self { $this->ySplit = $ySplit; if (in_array($this->paneState, self::VALIDFROZENSTATE, true)) { $this->freezePane([$this->xSplit + 1, $this->ySplit + 1], $this->topLeftCell, $this->paneState === self::PANE_FROZENSPLIT); } return $this; } public function getPaneState(): string { return $this->paneState; } public const PANE_FROZEN = 'frozen'; public const PANE_FROZENSPLIT = 'frozenSplit'; public const PANE_SPLIT = 'split'; private const VALIDPANESTATE = [self::PANE_FROZEN, self::PANE_SPLIT, self::PANE_FROZENSPLIT]; private const VALIDFROZENSTATE = [self::PANE_FROZEN, self::PANE_FROZENSPLIT]; public function setPaneState(string $paneState): self { $this->paneState = in_array($paneState, self::VALIDPANESTATE, true) ? $paneState : ''; if (in_array($this->paneState, self::VALIDFROZENSTATE, true)) { $this->freezePane([$this->xSplit + 1, $this->ySplit + 1], $this->topLeftCell, $this->paneState === self::PANE_FROZENSPLIT); } else { $this->freezePane = null; } return $this; } /** * Insert a new row, updating all possible related data. * * @param int $before Insert before this row number * @param int $numberOfRows Number of new rows to insert * * @return $this */ public function insertNewRowBefore(int $before, int $numberOfRows = 1) { if ($before >= 1) { $objReferenceHelper = ReferenceHelper::getInstance(); $objReferenceHelper->insertNewBefore('A' . $before, 0, $numberOfRows, $this); } else { throw new Exception('Rows can only be inserted before at least row 1.'); } return $this; } /** * Insert a new column, updating all possible related data. * * @param string $before Insert before this column Name, eg: 'A' * @param int $numberOfColumns Number of new columns to insert * * @return $this */ public function insertNewColumnBefore(string $before, int $numberOfColumns = 1) { if (!is_numeric($before)) { $objReferenceHelper = ReferenceHelper::getInstance(); $objReferenceHelper->insertNewBefore($before . '1', $numberOfColumns, 0, $this); } else { throw new Exception('Column references should not be numeric.'); } return $this; } /** * Insert a new column, updating all possible related data. * * @param int $beforeColumnIndex Insert before this column ID (numeric column coordinate of the cell) * @param int $numberOfColumns Number of new columns to insert * * @return $this */ public function insertNewColumnBeforeByIndex(int $beforeColumnIndex, int $numberOfColumns = 1) { if ($beforeColumnIndex >= 1) { return $this->insertNewColumnBefore(Coordinate::stringFromColumnIndex($beforeColumnIndex), $numberOfColumns); } throw new Exception('Columns can only be inserted before at least column A (1).'); } /** * Delete a row, updating all possible related data. * * @param int $row Remove rows, starting with this row number * @param int $numberOfRows Number of rows to remove * * @return $this */ public function removeRow(int $row, int $numberOfRows = 1) { if ($row < 1) { throw new Exception('Rows to be deleted should at least start from row 1.'); } $holdRowDimensions = $this->removeRowDimensions($row, $numberOfRows); $highestRow = $this->getHighestDataRow(); $removedRowsCounter = 0; for ($r = 0; $r < $numberOfRows; ++$r) { if ($row + $r <= $highestRow) { $this->cellCollection->removeRow($row + $r); ++$removedRowsCounter; } } $objReferenceHelper = ReferenceHelper::getInstance(); $objReferenceHelper->insertNewBefore('A' . ($row + $numberOfRows), 0, -$numberOfRows, $this); for ($r = 0; $r < $removedRowsCounter; ++$r) { $this->cellCollection->removeRow($highestRow); --$highestRow; } $this->rowDimensions = $holdRowDimensions; return $this; } private function removeRowDimensions(int $row, int $numberOfRows): array { $highRow = $row + $numberOfRows - 1; $holdRowDimensions = []; foreach ($this->rowDimensions as $rowDimension) { $num = $rowDimension->getRowIndex(); if ($num < $row) { $holdRowDimensions[$num] = $rowDimension; } elseif ($num > $highRow) { $num -= $numberOfRows; $cloneDimension = clone $rowDimension; $cloneDimension->setRowIndex($num); $holdRowDimensions[$num] = $cloneDimension; } } return $holdRowDimensions; } /** * Remove a column, updating all possible related data. * * @param string $column Remove columns starting with this column name, eg: 'A' * @param int $numberOfColumns Number of columns to remove * * @return $this */ public function removeColumn(string $column, int $numberOfColumns = 1) { if (is_numeric($column)) { throw new Exception('Column references should not be numeric.'); } $highestColumn = $this->getHighestDataColumn(); $highestColumnIndex = Coordinate::columnIndexFromString($highestColumn); $pColumnIndex = Coordinate::columnIndexFromString($column); $holdColumnDimensions = $this->removeColumnDimensions($pColumnIndex, $numberOfColumns); $column = Coordinate::stringFromColumnIndex($pColumnIndex + $numberOfColumns); $objReferenceHelper = ReferenceHelper::getInstance(); $objReferenceHelper->insertNewBefore($column . '1', -$numberOfColumns, 0, $this); $this->columnDimensions = $holdColumnDimensions; if ($pColumnIndex > $highestColumnIndex) { return $this; } $maxPossibleColumnsToBeRemoved = $highestColumnIndex - $pColumnIndex + 1; for ($c = 0, $n = min($maxPossibleColumnsToBeRemoved, $numberOfColumns); $c < $n; ++$c) { $this->cellCollection->removeColumn($highestColumn); $highestColumn = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($highestColumn) - 1); } $this->garbageCollect(); return $this; } private function removeColumnDimensions(int $pColumnIndex, int $numberOfColumns): array { $highCol = $pColumnIndex + $numberOfColumns - 1; $holdColumnDimensions = []; foreach ($this->columnDimensions as $columnDimension) { $num = $columnDimension->getColumnNumeric(); if ($num < $pColumnIndex) { $str = $columnDimension->getColumnIndex(); $holdColumnDimensions[$str] = $columnDimension; } elseif ($num > $highCol) { $cloneDimension = clone $columnDimension; $cloneDimension->setColumnNumeric($num - $numberOfColumns); $str = $cloneDimension->getColumnIndex(); $holdColumnDimensions[$str] = $cloneDimension; } } return $holdColumnDimensions; } /** * Remove a column, updating all possible related data. * * @param int $columnIndex Remove starting with this column Index (numeric column coordinate) * @param int $numColumns Number of columns to remove * * @return $this */ public function removeColumnByIndex(int $columnIndex, int $numColumns = 1) { if ($columnIndex >= 1) { return $this->removeColumn(Coordinate::stringFromColumnIndex($columnIndex), $numColumns); } throw new Exception('Columns to be deleted should at least start from column A (1)'); } /** * Show gridlines? */ public function getShowGridlines(): bool { return $this->showGridlines; } /** * Set show gridlines. * * @param bool $showGridLines Show gridlines (true/false) * * @return $this */ public function setShowGridlines(bool $showGridLines): self { $this->showGridlines = $showGridLines; return $this; } /** * Print gridlines? */ public function getPrintGridlines(): bool { return $this->printGridlines; } /** * Set print gridlines. * * @param bool $printGridLines Print gridlines (true/false) * * @return $this */ public function setPrintGridlines(bool $printGridLines): self { $this->printGridlines = $printGridLines; return $this; } /** * Show row and column headers? */ public function getShowRowColHeaders(): bool { return $this->showRowColHeaders; } /** * Set show row and column headers. * * @param bool $showRowColHeaders Show row and column headers (true/false) * * @return $this */ public function setShowRowColHeaders(bool $showRowColHeaders): self { $this->showRowColHeaders = $showRowColHeaders; return $this; } /** * Show summary below? (Row/Column outlining). */ public function getShowSummaryBelow(): bool { return $this->showSummaryBelow; } /** * Set show summary below. * * @param bool $showSummaryBelow Show summary below (true/false) * * @return $this */ public function setShowSummaryBelow(bool $showSummaryBelow): self { $this->showSummaryBelow = $showSummaryBelow; return $this; } /** * Show summary right? (Row/Column outlining). */ public function getShowSummaryRight(): bool { return $this->showSummaryRight; } /** * Set show summary right. * * @param bool $showSummaryRight Show summary right (true/false) * * @return $this */ public function setShowSummaryRight(bool $showSummaryRight): self { $this->showSummaryRight = $showSummaryRight; return $this; } /** * Get comments. * * @return Comment[] */ public function getComments(): array { return $this->comments; } /** * Set comments array for the entire sheet. * * @param Comment[] $comments * * @return $this */ public function setComments(array $comments): self { $this->comments = $comments; return $this; } /** * Remove comment from cell. * * @param array{0: int, 1: int}|CellAddress|string $cellCoordinate Coordinate of the cell as a string, eg: 'C5'; * or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. * * @return $this */ public function removeComment($cellCoordinate): self { $cellAddress = Functions::trimSheetFromCellReference(Validations::validateCellAddress($cellCoordinate)); if (Coordinate::coordinateIsRange($cellAddress)) { throw new Exception('Cell coordinate string can not be a range of cells.'); } elseif (str_contains($cellAddress, '$')) { throw new Exception('Cell coordinate string must not be absolute.'); } elseif ($cellAddress == '') { throw new Exception('Cell coordinate can not be zero-length string.'); } // Check if we have a comment for this cell and delete it if (isset($this->comments[$cellAddress])) { unset($this->comments[$cellAddress]); } return $this; } /** * Get comment for cell. * * @param array{0: int, 1: int}|CellAddress|string $cellCoordinate Coordinate of the cell as a string, eg: 'C5'; * or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. */ public function getComment($cellCoordinate, bool $attachNew = true): Comment { $cellAddress = Functions::trimSheetFromCellReference(Validations::validateCellAddress($cellCoordinate)); if (Coordinate::coordinateIsRange($cellAddress)) { throw new Exception('Cell coordinate string can not be a range of cells.'); } elseif (str_contains($cellAddress, '$')) { throw new Exception('Cell coordinate string must not be absolute.'); } elseif ($cellAddress == '') { throw new Exception('Cell coordinate can not be zero-length string.'); } // Check if we already have a comment for this cell. if (isset($this->comments[$cellAddress])) { return $this->comments[$cellAddress]; } // If not, create a new comment. $newComment = new Comment(); if ($attachNew) { $this->comments[$cellAddress] = $newComment; } return $newComment; } /** * Get active cell. * * @return string Example: 'A1' */ public function getActiveCell(): string { return $this->activeCell; } /** * Get selected cells. */ public function getSelectedCells(): string { return $this->selectedCells; } /** * Selected cell. * * @param string $coordinate Cell (i.e. A1) * * @return $this */ public function setSelectedCell(string $coordinate) { return $this->setSelectedCells($coordinate); } /** * Select a range of cells. * * @param AddressRange|AddressRange|AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|CellAddress|int|string $coordinate A simple string containing a Cell range like 'A1:E10' * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), * or a CellAddress or AddressRange object. * * @return $this */ public function setSelectedCells($coordinate) { if (is_string($coordinate)) { $coordinate = Validations::definedNameToCoordinate($coordinate, $this); } $coordinate = Validations::validateCellOrCellRange($coordinate); if (Coordinate::coordinateIsRange($coordinate)) { [$first] = Coordinate::splitRange($coordinate); $this->activeCell = $first[0]; } else { $this->activeCell = $coordinate; } $this->selectedCells = $coordinate; $this->setSelectedCellsActivePane(); return $this; } private function setSelectedCellsActivePane(): void { if (!empty($this->freezePane)) { $coordinateC = Coordinate::indexesFromString($this->freezePane); $coordinateT = Coordinate::indexesFromString($this->activeCell); if ($coordinateC[0] === 1) { $activePane = ($coordinateT[1] <= $coordinateC[1]) ? 'topLeft' : 'bottomLeft'; } elseif ($coordinateC[1] === 1) { $activePane = ($coordinateT[0] <= $coordinateC[0]) ? 'topLeft' : 'topRight'; } elseif ($coordinateT[1] <= $coordinateC[1]) { $activePane = ($coordinateT[0] <= $coordinateC[0]) ? 'topLeft' : 'topRight'; } else { $activePane = ($coordinateT[0] <= $coordinateC[0]) ? 'bottomLeft' : 'bottomRight'; } $this->setActivePane($activePane); $this->panes[$activePane] = new Pane($activePane, $this->selectedCells, $this->activeCell); } } /** * Get right-to-left. */ public function getRightToLeft(): bool { return $this->rightToLeft; } /** * Set right-to-left. * * @param bool $value Right-to-left true/false * * @return $this */ public function setRightToLeft(bool $value) { $this->rightToLeft = $value; return $this; } /** * Fill worksheet from values in array. * * @param array $source Source array * @param mixed $nullValue Value in source array that stands for blank cell * @param string $startCell Insert array starting from this cell address as the top left coordinate * @param bool $strictNullComparison Apply strict comparison when testing for null values in the array * * @return $this */ public function fromArray(array $source, $nullValue = null, string $startCell = 'A1', bool $strictNullComparison = false) { // Convert a 1-D array to 2-D (for ease of looping) if (!is_array(end($source))) { $source = [$source]; } // start coordinate [$startColumn, $startRow] = Coordinate::coordinateFromString($startCell); // Loop through $source if ($strictNullComparison) { foreach ($source as $rowData) { $currentColumn = $startColumn; foreach ($rowData as $cellValue) { if ($cellValue !== $nullValue) { // Set cell value $this->getCell($currentColumn . $startRow)->setValue($cellValue); } ++$currentColumn; } ++$startRow; } } else { foreach ($source as $rowData) { $currentColumn = $startColumn; foreach ($rowData as $cellValue) { if ($cellValue != $nullValue) { // Set cell value $this->getCell($currentColumn . $startRow)->setValue($cellValue); } ++$currentColumn; } ++$startRow; } } return $this; } /** * @param mixed $nullValue value to use when null * * @throws Exception * @throws \TablePress\PhpOffice\PhpSpreadsheet\Calculation\Exception * @return mixed */ protected function cellToArray(Cell $cell, bool $calculateFormulas, bool $formatData, $nullValue) { $returnValue = $nullValue; if ($cell->getValue() !== null) { if ($cell->getValue() instanceof RichText) { $returnValue = $cell->getValue()->getPlainText(); } else { $returnValue = ($calculateFormulas) ? $cell->getCalculatedValue() : $cell->getValue(); } if ($formatData) { $style = $this->getParentOrThrow()->getCellXfByIndex($cell->getXfIndex()); /** @var null|bool|float|int|RichText|string */ $returnValuex = $returnValue; $returnValue = NumberFormat::toFormattedString( $returnValuex, $style->getNumberFormat()->getFormatCode() ?? NumberFormat::FORMAT_GENERAL ); } } return $returnValue; } /** * Create array from a range of cells. * * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist * @param bool $calculateFormulas Should formulas be calculated? * @param bool $formatData Should formatting be applied to cell values? * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero * True - Return rows and columns indexed by their actual row and column IDs * @param bool $ignoreHidden False - Return values for rows/columns even if they are defined as hidden. * True - Don't return values for rows/columns that are defined as hidden. */ public function rangeToArray( string $range, $nullValue = null, bool $calculateFormulas = true, bool $formatData = true, bool $returnCellRef = false, bool $ignoreHidden = false, bool $reduceArrays = false ): array { $returnValue = []; // Loop through rows foreach ($this->rangeToArrayYieldRows($range, $nullValue, $calculateFormulas, $formatData, $returnCellRef, $ignoreHidden, $reduceArrays) as $rowRef => $rowArray) { $returnValue[$rowRef] = $rowArray; } // Return return $returnValue; } /** * Create array from a multiple ranges of cells. (such as A1:A3,A15,B17:C17). * * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist * @param bool $calculateFormulas Should formulas be calculated? * @param bool $formatData Should formatting be applied to cell values? * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero * True - Return rows and columns indexed by their actual row and column IDs * @param bool $ignoreHidden False - Return values for rows/columns even if they are defined as hidden. * True - Don't return values for rows/columns that are defined as hidden. */ public function rangesToArray( string $ranges, $nullValue = null, bool $calculateFormulas = true, bool $formatData = true, bool $returnCellRef = false, bool $ignoreHidden = false, bool $reduceArrays = false ): array { $returnValue = []; $parts = explode(',', $ranges); foreach ($parts as $part) { // Loop through rows foreach ($this->rangeToArrayYieldRows($part, $nullValue, $calculateFormulas, $formatData, $returnCellRef, $ignoreHidden, $reduceArrays) as $rowRef => $rowArray) { $returnValue[$rowRef] = $rowArray; } } // Return return $returnValue; } /** * Create array from a range of cells, yielding each row in turn. * * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist * @param bool $calculateFormulas Should formulas be calculated? * @param bool $formatData Should formatting be applied to cell values? * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero * True - Return rows and columns indexed by their actual row and column IDs * @param bool $ignoreHidden False - Return values for rows/columns even if they are defined as hidden. * True - Don't return values for rows/columns that are defined as hidden. * * @return Generator */ public function rangeToArrayYieldRows( string $range, $nullValue = null, bool $calculateFormulas = true, bool $formatData = true, bool $returnCellRef = false, bool $ignoreHidden = false, bool $reduceArrays = false ) { $range = Validations::validateCellOrCellRange($range); // Identify the range that we need to extract from the worksheet [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($range); $minCol = Coordinate::stringFromColumnIndex($rangeStart[0]); $minRow = $rangeStart[1]; $maxCol = Coordinate::stringFromColumnIndex($rangeEnd[0]); $maxRow = $rangeEnd[1]; $minColInt = $rangeStart[0]; $maxColInt = $rangeEnd[0]; ++$maxCol; /** @var array */ $hiddenColumns = []; $nullRow = $this->buildNullRow($nullValue, $minCol, $maxCol, $returnCellRef, $ignoreHidden, $hiddenColumns); $hideColumns = !empty($hiddenColumns); $keys = $this->cellCollection->getSortedCoordinatesInt(); $keyIndex = 0; $keysCount = count($keys); // Loop through rows for ($row = $minRow; $row <= $maxRow; ++$row) { if (($ignoreHidden === true) && ($this->isRowVisible($row) === false)) { continue; } $rowRef = $returnCellRef ? $row : ($row - $minRow); $returnValue = $nullRow; $index = ($row - 1) * AddressRange::MAX_COLUMN_INT + 1; $indexPlus = $index + AddressRange::MAX_COLUMN_INT - 1; while ($keyIndex < $keysCount && $keys[$keyIndex] < $index) { ++$keyIndex; } while ($keyIndex < $keysCount && $keys[$keyIndex] <= $indexPlus) { $key = $keys[$keyIndex]; $thisRow = intdiv($key - 1, AddressRange::MAX_COLUMN_INT) + 1; $thisCol = ($key % AddressRange::MAX_COLUMN_INT) ?: AddressRange::MAX_COLUMN_INT; if ($thisCol >= $minColInt && $thisCol <= $maxColInt) { $col = Coordinate::stringFromColumnIndex($thisCol); if ($hideColumns === false || !isset($hiddenColumns[$col])) { $columnRef = $returnCellRef ? $col : ($thisCol - $minColInt); $cell = $this->cellCollection->get("{$col}{$thisRow}"); if ($cell !== null) { $value = $this->cellToArray($cell, $calculateFormulas, $formatData, $nullValue); if ($reduceArrays) { while (is_array($value)) { $value = array_shift($value); } } if ($value !== $nullValue) { $returnValue[$columnRef] = $value; } } } } ++$keyIndex; } yield $rowRef => $returnValue; } } /** * Prepare a row data filled with null values to deduplicate the memory areas for empty rows. * * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist * @param string $minCol Start column of the range * @param string $maxCol End column of the range * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero * True - Return rows and columns indexed by their actual row and column IDs * @param bool $ignoreHidden False - Return values for rows/columns even if they are defined as hidden. * True - Don't return values for rows/columns that are defined as hidden. * @param array $hiddenColumns */ private function buildNullRow( $nullValue, string $minCol, string $maxCol, bool $returnCellRef, bool $ignoreHidden, array &$hiddenColumns ): array { $nullRow = []; $c = -1; for ($col = $minCol; $col !== $maxCol; ++$col) { if ($ignoreHidden === true && $this->columnDimensionExists($col) && $this->getColumnDimension($col)->getVisible() === false) { $hiddenColumns[$col] = true; // @phpstan-ignore-line } else { $columnRef = $returnCellRef ? $col : ++$c; $nullRow[$columnRef] = $nullValue; } } return $nullRow; } private function validateNamedRange(string $definedName, bool $returnNullIfInvalid = false): ?DefinedName { $namedRange = DefinedName::resolveName($definedName, $this); if ($namedRange === null) { if ($returnNullIfInvalid) { return null; } throw new Exception('Named Range ' . $definedName . ' does not exist.'); } if ($namedRange->isFormula()) { if ($returnNullIfInvalid) { return null; } throw new Exception('Defined Named ' . $definedName . ' is a formula, not a range or cell.'); } if ($namedRange->getLocalOnly()) { $worksheet = $namedRange->getWorksheet(); if ($worksheet === null || $this->hash !== $worksheet->getHashInt()) { if ($returnNullIfInvalid) { return null; } throw new Exception( 'Named range ' . $definedName . ' is not accessible from within sheet ' . $this->getTitle() ); } } return $namedRange; } /** * Create array from a range of cells. * * @param string $definedName The Named Range that should be returned * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist * @param bool $calculateFormulas Should formulas be calculated? * @param bool $formatData Should formatting be applied to cell values? * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero * True - Return rows and columns indexed by their actual row and column IDs * @param bool $ignoreHidden False - Return values for rows/columns even if they are defined as hidden. * True - Don't return values for rows/columns that are defined as hidden. */ public function namedRangeToArray( string $definedName, $nullValue = null, bool $calculateFormulas = true, bool $formatData = true, bool $returnCellRef = false, bool $ignoreHidden = false, bool $reduceArrays = false ): array { $retVal = []; $namedRange = $this->validateNamedRange($definedName); if ($namedRange !== null) { $cellRange = ltrim(substr($namedRange->getValue(), (int) strrpos($namedRange->getValue(), '!')), '!'); $cellRange = str_replace('$', '', $cellRange); $workSheet = $namedRange->getWorksheet(); if ($workSheet !== null) { $retVal = $workSheet->rangeToArray($cellRange, $nullValue, $calculateFormulas, $formatData, $returnCellRef, $ignoreHidden, $reduceArrays); } } return $retVal; } /** * Create array from worksheet. * * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist * @param bool $calculateFormulas Should formulas be calculated? * @param bool $formatData Should formatting be applied to cell values? * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero * True - Return rows and columns indexed by their actual row and column IDs * @param bool $ignoreHidden False - Return values for rows/columns even if they are defined as hidden. * True - Don't return values for rows/columns that are defined as hidden. */ public function toArray( $nullValue = null, bool $calculateFormulas = true, bool $formatData = true, bool $returnCellRef = false, bool $ignoreHidden = false, bool $reduceArrays = false ): array { // Garbage collect... $this->garbageCollect(); $this->calculateArrays($calculateFormulas); // Identify the range that we need to extract from the worksheet $maxCol = $this->getHighestColumn(); $maxRow = $this->getHighestRow(); // Return return $this->rangeToArray("A1:{$maxCol}{$maxRow}", $nullValue, $calculateFormulas, $formatData, $returnCellRef, $ignoreHidden, $reduceArrays); } /** * Get row iterator. * * @param int $startRow The row number at which to start iterating * @param ?int $endRow The row number at which to stop iterating */ public function getRowIterator(int $startRow = 1, ?int $endRow = null): RowIterator { return new RowIterator($this, $startRow, $endRow); } /** * Get column iterator. * * @param string $startColumn The column address at which to start iterating * @param ?string $endColumn The column address at which to stop iterating */ public function getColumnIterator(string $startColumn = 'A', ?string $endColumn = null): ColumnIterator { return new ColumnIterator($this, $startColumn, $endColumn); } /** * Run PhpSpreadsheet garbage collector. * * @return $this */ public function garbageCollect() { // Flush cache $this->cellCollection->get('A1'); // Lookup highest column and highest row if cells are cleaned $colRow = $this->cellCollection->getHighestRowAndColumn(); $highestRow = $colRow['row']; $highestColumn = Coordinate::columnIndexFromString($colRow['column']); // Loop through column dimensions foreach ($this->columnDimensions as $dimension) { $highestColumn = max($highestColumn, Coordinate::columnIndexFromString($dimension->getColumnIndex())); } // Loop through row dimensions foreach ($this->rowDimensions as $dimension) { $highestRow = max($highestRow, $dimension->getRowIndex()); } // Cache values if ($highestColumn < 1) { $this->cachedHighestColumn = 1; } else { $this->cachedHighestColumn = $highestColumn; } $this->cachedHighestRow = $highestRow; // Return return $this; } public function getHashInt(): int { return $this->hash; } /** * Extract worksheet title from range. * * Example: extractSheetTitle("testSheet!A1") ==> 'A1' * Example: extractSheetTitle("testSheet!A1:C3") ==> 'A1:C3' * Example: extractSheetTitle("'testSheet 1'!A1", true) ==> ['testSheet 1', 'A1']; * Example: extractSheetTitle("'testSheet 1'!A1:C3", true) ==> ['testSheet 1', 'A1:C3']; * Example: extractSheetTitle("A1", true) ==> ['', 'A1']; * Example: extractSheetTitle("A1:C3", true) ==> ['', 'A1:C3'] * * @param ?string $range Range to extract title from * @param bool $returnRange Return range? (see example) * * @return ($range is non-empty-string ? ($returnRange is true ? array{0: string, 1: string} : string) : ($returnRange is true ? array{0: null, 1: null} : null)) */ public static function extractSheetTitle(?string $range, bool $returnRange = false, bool $unapostrophize = false) { if (empty($range)) { return $returnRange ? [null, null] : null; } // Sheet title included? if (($sep = strrpos($range, '!')) === false) { return $returnRange ? ['', $range] : ''; } if ($returnRange) { $title = substr($range, 0, $sep); if ($unapostrophize) { $title = self::unApostrophizeTitle($title); } return [$title, substr($range, $sep + 1)]; } return substr($range, $sep + 1); } public static function unApostrophizeTitle(?string $title): string { $title ??= ''; if ($title[0] === "'" && substr($title, -1) === "'") { $title = str_replace("''", "'", substr($title, 1, -1)); } return $title; } /** * Get hyperlink. * * @param string $cellCoordinate Cell coordinate to get hyperlink for, eg: 'A1' */ public function getHyperlink(string $cellCoordinate): Hyperlink { // return hyperlink if we already have one if (isset($this->hyperlinkCollection[$cellCoordinate])) { return $this->hyperlinkCollection[$cellCoordinate]; } // else create hyperlink $this->hyperlinkCollection[$cellCoordinate] = new Hyperlink(); return $this->hyperlinkCollection[$cellCoordinate]; } /** * Set hyperlink. * * @param string $cellCoordinate Cell coordinate to insert hyperlink, eg: 'A1' * * @return $this */ public function setHyperlink(string $cellCoordinate, ?Hyperlink $hyperlink = null) { if ($hyperlink === null) { unset($this->hyperlinkCollection[$cellCoordinate]); } else { $this->hyperlinkCollection[$cellCoordinate] = $hyperlink; } return $this; } /** * Hyperlink at a specific coordinate exists? * * @param string $coordinate eg: 'A1' */ public function hyperlinkExists(string $coordinate): bool { return isset($this->hyperlinkCollection[$coordinate]); } /** * Get collection of hyperlinks. * * @return Hyperlink[] */ public function getHyperlinkCollection(): array { return $this->hyperlinkCollection; } /** * Get data validation. * * @param string $cellCoordinate Cell coordinate to get data validation for, eg: 'A1' */ public function getDataValidation(string $cellCoordinate): DataValidation { // return data validation if we already have one if (isset($this->dataValidationCollection[$cellCoordinate])) { return $this->dataValidationCollection[$cellCoordinate]; } // or if cell is part of a data validation range foreach ($this->dataValidationCollection as $key => $dataValidation) { $keyParts = explode(' ', $key); foreach ($keyParts as $keyPart) { if ($keyPart === $cellCoordinate) { return $dataValidation; } if (str_contains($keyPart, ':')) { if (Coordinate::coordinateIsInsideRange($keyPart, $cellCoordinate)) { return $dataValidation; } } } } // else create data validation $dataValidation = new DataValidation(); $dataValidation->setSqref($cellCoordinate); $this->dataValidationCollection[$cellCoordinate] = $dataValidation; return $dataValidation; } /** * Set data validation. * * @param string $cellCoordinate Cell coordinate to insert data validation, eg: 'A1' * * @return $this */ public function setDataValidation(string $cellCoordinate, ?DataValidation $dataValidation = null) { if ($dataValidation === null) { unset($this->dataValidationCollection[$cellCoordinate]); } else { $dataValidation->setSqref($cellCoordinate); $this->dataValidationCollection[$cellCoordinate] = $dataValidation; } return $this; } /** * Data validation at a specific coordinate exists? * * @param string $coordinate eg: 'A1' */ public function dataValidationExists(string $coordinate): bool { if (isset($this->dataValidationCollection[$coordinate])) { return true; } foreach ($this->dataValidationCollection as $key => $dataValidation) { $keyParts = explode(' ', $key); foreach ($keyParts as $keyPart) { if ($keyPart === $coordinate) { return true; } if (str_contains($keyPart, ':')) { if (Coordinate::coordinateIsInsideRange($keyPart, $coordinate)) { return true; } } } } return false; } /** * Get collection of data validations. * * @return DataValidation[] */ public function getDataValidationCollection(): array { $collectionCells = []; $collectionRanges = []; foreach ($this->dataValidationCollection as $key => $dataValidation) { if (preg_match('/[: ]/', $key) === 1) { $collectionRanges[$key] = $dataValidation; } else { $collectionCells[$key] = $dataValidation; } } return array_merge($collectionCells, $collectionRanges); } /** * Accepts a range, returning it as a range that falls within the current highest row and column of the worksheet. * * @return string Adjusted range value */ public function shrinkRangeToFit(string $range): string { $maxCol = $this->getHighestColumn(); $maxRow = $this->getHighestRow(); $maxCol = Coordinate::columnIndexFromString($maxCol); $rangeBlocks = explode(' ', $range); foreach ($rangeBlocks as &$rangeSet) { $rangeBoundaries = Coordinate::getRangeBoundaries($rangeSet); if (Coordinate::columnIndexFromString($rangeBoundaries[0][0]) > $maxCol) { $rangeBoundaries[0][0] = Coordinate::stringFromColumnIndex($maxCol); } if ($rangeBoundaries[0][1] > $maxRow) { $rangeBoundaries[0][1] = $maxRow; } if (Coordinate::columnIndexFromString($rangeBoundaries[1][0]) > $maxCol) { $rangeBoundaries[1][0] = Coordinate::stringFromColumnIndex($maxCol); } if ($rangeBoundaries[1][1] > $maxRow) { $rangeBoundaries[1][1] = $maxRow; } $rangeSet = $rangeBoundaries[0][0] . $rangeBoundaries[0][1] . ':' . $rangeBoundaries[1][0] . $rangeBoundaries[1][1]; } unset($rangeSet); return implode(' ', $rangeBlocks); } /** * Get tab color. */ public function getTabColor(): Color { if ($this->tabColor === null) { $this->tabColor = new Color(); } return $this->tabColor; } /** * Reset tab color. * * @return $this */ public function resetTabColor() { $this->tabColor = null; return $this; } /** * Tab color set? */ public function isTabColorSet(): bool { return $this->tabColor !== null; } /** * Copy worksheet (!= clone!). * @return static */ public function copy() { return clone $this; } /** * Returns a boolean true if the specified row contains no cells. By default, this means that no cell records * exist in the collection for this row. false will be returned otherwise. * This rule can be modified by passing a $definitionOfEmptyFlags value: * 1 - CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL If the only cells in the collection are null value * cells, then the row will be considered empty. * 2 - CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL If the only cells in the collection are empty * string value cells, then the row will be considered empty. * 3 - CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL | CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL * If the only cells in the collection are null value or empty string value cells, then the row * will be considered empty. * * @param int $definitionOfEmptyFlags * Possible Flag Values are: * CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL * CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL */ public function isEmptyRow(int $rowId, int $definitionOfEmptyFlags = 0): bool { try { $iterator = new RowIterator($this, $rowId, $rowId); $iterator->seek($rowId); $row = $iterator->current(); } catch (Exception $exception) { return true; } return $row->isEmpty($definitionOfEmptyFlags); } /** * Returns a boolean true if the specified column contains no cells. By default, this means that no cell records * exist in the collection for this column. false will be returned otherwise. * This rule can be modified by passing a $definitionOfEmptyFlags value: * 1 - CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL If the only cells in the collection are null value * cells, then the column will be considered empty. * 2 - CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL If the only cells in the collection are empty * string value cells, then the column will be considered empty. * 3 - CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL | CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL * If the only cells in the collection are null value or empty string value cells, then the column * will be considered empty. * * @param int $definitionOfEmptyFlags * Possible Flag Values are: * CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL * CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL */ public function isEmptyColumn(string $columnId, int $definitionOfEmptyFlags = 0): bool { try { $iterator = new ColumnIterator($this, $columnId, $columnId); $iterator->seek($columnId); $column = $iterator->current(); } catch (Exception $exception) { return true; } return $column->isEmpty($definitionOfEmptyFlags); } /** * Implement PHP __clone to create a deep clone, not just a shallow copy. */ public function __clone() { foreach (get_object_vars($this) as $key => $val) { if ($key == 'parent') { continue; } if (is_object($val) || (is_array($val))) { if ($key === 'cellCollection') { $newCollection = $this->cellCollection->cloneCellCollection($this); $this->cellCollection = $newCollection; } elseif ($key === 'drawingCollection') { $currentCollection = $this->drawingCollection; $this->drawingCollection = new ArrayObject(); foreach ($currentCollection as $item) { $newDrawing = clone $item; $newDrawing->setWorksheet($this); } } elseif ($key === 'tableCollection') { $currentCollection = $this->tableCollection; $this->tableCollection = new ArrayObject(); foreach ($currentCollection as $item) { $newTable = clone $item; $newTable->setName($item->getName() . 'clone'); $this->addTable($newTable); } } elseif ($key === 'chartCollection') { $currentCollection = $this->chartCollection; $this->chartCollection = new ArrayObject(); foreach ($currentCollection as $item) { $newChart = clone $item; $this->addChart($newChart); } } elseif ($key === 'autoFilter') { $newAutoFilter = clone $this->autoFilter; $this->autoFilter = $newAutoFilter; $this->autoFilter->setParent($this); } else { $this->{$key} = unserialize(serialize($val)); } } } $this->hash = spl_object_id($this); } /** * Define the code name of the sheet. * * @param string $codeName Same rule as Title minus space not allowed (but, like Excel, change * silently space to underscore) * @param bool $validate False to skip validation of new title. WARNING: This should only be set * at parse time (by Readers), where titles can be assumed to be valid. * * @return $this */ public function setCodeName(string $codeName, bool $validate = true) { // Is this a 'rename' or not? if ($this->getCodeName() == $codeName) { return $this; } if ($validate) { $codeName = str_replace(' ', '_', $codeName); //Excel does this automatically without flinching, we are doing the same // Syntax check // throw an exception if not valid self::checkSheetCodeName($codeName); // We use the same code that setTitle to find a valid codeName else not using a space (Excel don't like) but a '_' if ($this->parent !== null) { // Is there already such sheet name? if ($this->parent->sheetCodeNameExists($codeName)) { // Use name, but append with lowest possible integer if (StringHelper::countCharacters($codeName) > 29) { $codeName = StringHelper::substring($codeName, 0, 29); } $i = 1; while ($this->getParentOrThrow()->sheetCodeNameExists($codeName . '_' . $i)) { ++$i; if ($i == 10) { if (StringHelper::countCharacters($codeName) > 28) { $codeName = StringHelper::substring($codeName, 0, 28); } } elseif ($i == 100) { if (StringHelper::countCharacters($codeName) > 27) { $codeName = StringHelper::substring($codeName, 0, 27); } } } $codeName .= '_' . $i; // ok, we have a valid name } } } $this->codeName = $codeName; return $this; } /** * Return the code name of the sheet. */ public function getCodeName(): ?string { return $this->codeName; } /** * Sheet has a code name ? */ public function hasCodeName(): bool { return $this->codeName !== null; } public static function nameRequiresQuotes(string $sheetName): bool { return preg_match(self::SHEET_NAME_REQUIRES_NO_QUOTES, $sheetName) !== 1; } public function isRowVisible(int $row): bool { return !$this->rowDimensionExists($row) || $this->getRowDimension($row)->getVisible(); } /** * Same as Cell->isLocked, but without creating cell if it doesn't exist. */ public function isCellLocked(string $coordinate): bool { if ($this->getProtection()->getsheet() !== true) { return false; } if ($this->cellExists($coordinate)) { return $this->getCell($coordinate)->isLocked(); } $spreadsheet = $this->parent; $xfIndex = $this->getXfIndex($coordinate); if ($spreadsheet === null || $xfIndex === null) { return true; } return $spreadsheet->getCellXfByIndex($xfIndex)->getProtection()->getLocked() !== StyleProtection::PROTECTION_UNPROTECTED; } /** * Same as Cell->isHiddenOnFormulaBar, but without creating cell if it doesn't exist. */ public function isCellHiddenOnFormulaBar(string $coordinate): bool { if ($this->cellExists($coordinate)) { return $this->getCell($coordinate)->isHiddenOnFormulaBar(); } // cell doesn't exist, therefore isn't a formula, // therefore isn't hidden on formula bar. return false; } private function getXfIndex(string $coordinate): ?int { [$column, $row] = Coordinate::coordinateFromString($coordinate); $row = (int) $row; $xfIndex = null; if ($this->rowDimensionExists($row)) { $xfIndex = $this->getRowDimension($row)->getXfIndex(); } if ($xfIndex === null && $this->ColumnDimensionExists($column)) { $xfIndex = $this->getColumnDimension($column)->getXfIndex(); } return $xfIndex; } private string $backgroundImage = ''; private string $backgroundMime = ''; private string $backgroundExtension = ''; public function getBackgroundImage(): string { return $this->backgroundImage; } public function getBackgroundMime(): string { return $this->backgroundMime; } public function getBackgroundExtension(): string { return $this->backgroundExtension; } /** * Set background image. * Used on read/write for Xlsx. * Used on write for Html. * * @param string $backgroundImage Image represented as a string, e.g. results of file_get_contents */ public function setBackgroundImage(string $backgroundImage): self { $imageArray = getimagesizefromstring($backgroundImage) ?: ['mime' => '']; $mime = $imageArray['mime']; if ($mime !== '') { $extension = explode('/', $mime); $extension = $extension[1]; $this->backgroundImage = $backgroundImage; $this->backgroundMime = $mime; $this->backgroundExtension = $extension; } return $this; } /** * Copy cells, adjusting relative cell references in formulas. * Acts similarly to Excel "fill handle" feature. * * @param string $fromCell Single source cell, e.g. C3 * @param string $toCells Single cell or cell range, e.g. C4 or C4:C10 * @param bool $copyStyle Copy styles as well as values, defaults to true */ public function copyCells(string $fromCell, string $toCells, bool $copyStyle = true): void { $toArray = Coordinate::extractAllCellReferencesInRange($toCells); $valueString = $this->getCell($fromCell)->getValueString(); $style = $this->getStyle($fromCell)->exportArray(); $fromIndexes = Coordinate::indexesFromString($fromCell); $referenceHelper = ReferenceHelper::getInstance(); foreach ($toArray as $destination) { if ($destination !== $fromCell) { $toIndexes = Coordinate::indexesFromString($destination); $this->getCell($destination)->setValue($referenceHelper->updateFormulaReferences($valueString, 'A1', $toIndexes[0] - $fromIndexes[0], $toIndexes[1] - $fromIndexes[1])); if ($copyStyle) { $this->getCell($destination)->getStyle()->applyFromArray($style); } } } } public function calculateArrays(bool $preCalculateFormulas = true): void { if ($preCalculateFormulas && Calculation::getInstance($this->parent)->getInstanceArrayReturnType() === Calculation::RETURN_ARRAY_AS_ARRAY) { $keys = $this->cellCollection->getCoordinates(); foreach ($keys as $key) { if ($this->getCell($key)->getDataType() === DataType::TYPE_FORMULA) { if (preg_match(self::FUNCTION_LIKE_GROUPBY, $this->getCell($key)->getValueString()) !== 1) { $this->getCell($key)->getCalculatedValue(); } } } } } public function isCellInSpillRange(string $coordinate): bool { if (Calculation::getInstance($this->parent)->getInstanceArrayReturnType() !== Calculation::RETURN_ARRAY_AS_ARRAY) { return false; } $this->calculateArrays(); $keys = $this->cellCollection->getCoordinates(); foreach ($keys as $key) { $attributes = $this->getCell($key)->getFormulaAttributes(); if (isset($attributes['ref'])) { if (Coordinate::coordinateIsInsideRange($attributes['ref'], $coordinate)) { // false for first cell in range, true otherwise return $coordinate !== $key; } } } return false; } public function applyStylesFromArray(string $coordinate, array $styleArray): bool { $spreadsheet = $this->parent; if ($spreadsheet === null) { return false; } $activeSheetIndex = $spreadsheet->getActiveSheetIndex(); $originalSelected = $this->selectedCells; $this->getStyle($coordinate)->applyFromArray($styleArray); $this->setSelectedCells($originalSelected); if ($activeSheetIndex >= 0) { $spreadsheet->setActiveSheetIndex($activeSheetIndex); } return true; } } PK!cRGG7libraries/vendor/PhpSpreadsheet/Worksheet/Dimension.phpnu[xfIndex = $initialValue; } /** * Get Visible. */ public function getVisible(): bool { return $this->visible; } /** * Set Visible. * * @return $this */ public function setVisible(bool $visible) { $this->visible = $visible; return $this; } /** * Get Outline Level. */ public function getOutlineLevel(): int { return $this->outlineLevel; } /** * Set Outline Level. * Value must be between 0 and 7. * * @return $this */ public function setOutlineLevel(int $level) { if ($level < 0 || $level > 7) { throw new PhpSpreadsheetException('Outline level must range between 0 and 7.'); } $this->outlineLevel = $level; return $this; } /** * Get Collapsed. */ public function getCollapsed(): bool { return $this->collapsed; } /** * Set Collapsed. * * @return $this */ public function setCollapsed(bool $collapsed) { $this->collapsed = $collapsed; return $this; } /** * Get index to cellXf. */ public function getXfIndex(): ?int { return $this->xfIndex; } /** * Set index to cellXf. * * @return $this */ public function setXfIndex(int $XfIndex) { $this->xfIndex = $XfIndex; return $this; } } PK!n_OO<libraries/vendor/PhpSpreadsheet/Worksheet/ProtectedRange.phpnu[sqref = $sqref; $this->name = $name; $this->password = $password; $this->securityDescriptor = $securityDescriptor; } public function getSqref(): string { return $this->sqref; } public function getName(): string { return $this->name ?: ('p' . md5($this->sqref)); } public function getPassword(): string { return $this->password; } public function getSecurityDescriptor(): string { return $this->securityDescriptor; } } PK!_h =libraries/vendor/PhpSpreadsheet/Worksheet/ColumnDimension.phpnu[columnIndex = $index; // set dimension as unformatted by default parent::__construct(0); } /** * Get column index as string eg: 'A'. */ public function getColumnIndex(): ?string { return $this->columnIndex; } /** * Set column index as string eg: 'A'. */ public function setColumnIndex(string $index): self { $this->columnIndex = $index; return $this; } /** * Get column index as numeric. */ public function getColumnNumeric(): int { return Coordinate::columnIndexFromString($this->columnIndex ?? ''); } /** * Set column index as numeric. */ public function setColumnNumeric(int $index): self { $this->columnIndex = Coordinate::stringFromColumnIndex($index); return $this; } /** * Get Width. * * Each unit of column width is equal to the width of one character in the default font size. A value of -1 * tells Excel to display this column in its default width. * By default, this will be the return value; but this method also accepts an optional unit of measure argument * and will convert the returned value to the specified UoM.. */ public function getWidth(?string $unitOfMeasure = null): float { return ($unitOfMeasure === null || $this->width < 0) ? $this->width : (new CssDimension((string) $this->width))->toUnit($unitOfMeasure); } /** * Set Width. * * Each unit of column width is equal to the width of one character in the default font size. A value of -1 * tells Excel to display this column in its default width. * By default, this will be the unit of measure for the passed value; but this method also accepts an * optional unit of measure argument, and will convert the value from the specified UoM using an * approximation method. * * @return $this */ public function setWidth(float $width, ?string $unitOfMeasure = null) { $this->width = ($unitOfMeasure === null || $width < 0) ? $width : (new CssDimension("{$width}{$unitOfMeasure}"))->width(); return $this; } /** * Get Auto Size. */ public function getAutoSize(): bool { return $this->autoSize; } /** * Set Auto Size. * * @return $this */ public function setAutoSize(bool $autosizeEnabled) { $this->autoSize = $autosizeEnabled; return $this; } } PK!댤 5libraries/vendor/PhpSpreadsheet/Worksheet/AutoFit.phpnu[worksheet = $worksheet; } public function getAutoFilterIndentRanges(): array { $autoFilterIndentRanges = []; $autoFilterIndentRanges[] = $this->getAutoFilterIndentRange($this->worksheet->getAutoFilter()); foreach ($this->worksheet->getTableCollection() as $table) { /** @var Table $table */ if ($table->getShowHeaderRow() === true && $table->getAllowFilter() === true) { $autoFilter = $table->getAutoFilter(); $autoFilterIndentRanges[] = $this->getAutoFilterIndentRange($autoFilter); } } return array_filter($autoFilterIndentRanges); } private function getAutoFilterIndentRange(AutoFilter $autoFilter): ?string { $autoFilterRange = $autoFilter->getRange(); $autoFilterIndentRange = null; if (!empty($autoFilterRange)) { $autoFilterRangeBoundaries = Coordinate::rangeBoundaries($autoFilterRange); $autoFilterIndentRange = (string) new CellRange( CellAddress::fromColumnAndRow($autoFilterRangeBoundaries[0][0], $autoFilterRangeBoundaries[0][1]), CellAddress::fromColumnAndRow($autoFilterRangeBoundaries[1][0], $autoFilterRangeBoundaries[0][1]) ); } return $autoFilterIndentRange; } } PK!1Y~7libraries/vendor/PhpSpreadsheet/Worksheet/SheetView.phpnu[zoomScale; } /** * Set ZoomScale. * Valid values range from 10 to 400. * * @return $this */ public function setZoomScale(?int $zoomScale) { // Microsoft Office Excel 2007 only allows setting a scale between 10 and 400 via the user interface, // but it is apparently still able to handle any scale >= 1 if ($zoomScale === null || $zoomScale >= 1) { $this->zoomScale = $zoomScale; } else { throw new PhpSpreadsheetException('Scale must be greater than or equal to 1.'); } return $this; } /** * Get ZoomScaleNormal. */ public function getZoomScaleNormal(): ?int { return $this->zoomScaleNormal; } /** * Set ZoomScale. * Valid values range from 10 to 400. * * @return $this */ public function setZoomScaleNormal(?int $zoomScaleNormal) { if ($zoomScaleNormal === null || $zoomScaleNormal >= 1) { $this->zoomScaleNormal = $zoomScaleNormal; } else { throw new PhpSpreadsheetException('Scale must be greater than or equal to 1.'); } return $this; } public function getZoomScalePageLayoutView(): int { return $this->zoomScalePageLayoutView; } /** * @return static */ public function setZoomScalePageLayoutView(int $zoomScalePageLayoutView) { if ($zoomScalePageLayoutView >= 1) { $this->zoomScalePageLayoutView = $zoomScalePageLayoutView; } else { throw new PhpSpreadsheetException('Scale must be greater than or equal to 1.'); } return $this; } public function getZoomScaleSheetLayoutView(): int { return $this->zoomScaleSheetLayoutView; } /** * @return static */ public function setZoomScaleSheetLayoutView(int $zoomScaleSheetLayoutView) { if ($zoomScaleSheetLayoutView >= 1) { $this->zoomScaleSheetLayoutView = $zoomScaleSheetLayoutView; } else { throw new PhpSpreadsheetException('Scale must be greater than or equal to 1.'); } return $this; } /** * Set ShowZeroes setting. */ public function setShowZeros(bool $showZeros): void { $this->showZeros = $showZeros; } public function getShowZeros(): bool { return $this->showZeros; } /** * Get View. */ public function getView(): string { return $this->sheetviewType; } /** * Set View. * * Valid values are * 'normal' self::SHEETVIEW_NORMAL * 'pageLayout' self::SHEETVIEW_PAGE_LAYOUT * 'pageBreakPreview' self::SHEETVIEW_PAGE_BREAK_PREVIEW * * @return $this */ public function setView(?string $sheetViewType) { // MS Excel 2007 allows setting the view to 'normal', 'pageLayout' or 'pageBreakPreview' via the user interface if ($sheetViewType === null) { $sheetViewType = self::SHEETVIEW_NORMAL; } if (in_array($sheetViewType, self::SHEET_VIEW_TYPES)) { $this->sheetviewType = $sheetViewType; } else { throw new PhpSpreadsheetException('Invalid sheetview layout type.'); } return $this; } } PK!?a  2libraries/vendor/PhpSpreadsheet/Worksheet/Pane.phpnu[sqref = $sqref; $this->activeCell = $activeCell; $this->position = $position; } public function getPosition(): string { return $this->position; } public function getSqref(): string { return $this->sqref; } public function setSqref(string $sqref): self { $this->sqref = $sqref; return $this; } public function getActiveCell(): string { return $this->activeCell; } public function setActiveCell(string $activeCell): self { $this->activeCell = $activeCell; return $this; } } PK!,]ǰ%%:libraries/vendor/PhpSpreadsheet/Worksheet/HeaderFooter.phpnu[ * Header/Footer Formatting Syntax taken from Office Open XML Part 4 - Markup Language Reference, page 1970:. * * There are a number of formatting codes that can be written inline with the actual header / footer text, which * affect the formatting in the header or footer. * * Example: This example shows the text "Center Bold Header" on the first line (center section), and the date on * the second line (center section). * &CCenter &"-,Bold"Bold&"-,Regular"Header_x000A_&D * * General Rules: * There is no required order in which these codes must appear. * * The first occurrence of the following codes turns the formatting ON, the second occurrence turns it OFF again: * - strikethrough * - superscript * - subscript * Superscript and subscript cannot both be ON at same time. Whichever comes first wins and the other is ignored, * while the first is ON. * &L - code for "left section" (there are three header / footer locations, "left", "center", and "right"). When * two or more occurrences of this section marker exist, the contents from all markers are concatenated, in the * order of appearance, and placed into the left section. * &P - code for "current page #" * &N - code for "total pages" * &font size - code for "text font size", where font size is a font size in points. * &K - code for "text font color" * RGB Color is specified as RRGGBB * Theme Color is specifed as TTSNN where TT is the theme color Id, S is either "+" or "-" of the tint/shade * value, NN is the tint/shade value. * &S - code for "text strikethrough" on / off * &X - code for "text super script" on / off * &Y - code for "text subscript" on / off * &C - code for "center section". When two or more occurrences of this section marker exist, the contents * from all markers are concatenated, in the order of appearance, and placed into the center section. * * &D - code for "date" * &T - code for "time" * &G - code for "picture as background" * &U - code for "text single underline" * &E - code for "double underline" * &R - code for "right section". When two or more occurrences of this section marker exist, the contents * from all markers are concatenated, in the order of appearance, and placed into the right section. * &Z - code for "this workbook's file path" * &F - code for "this workbook's file name" * &A - code for "sheet tab name" * &+ - code for add to page #. * &- - code for subtract from page #. * &"font name,font type" - code for "text font name" and "text font type", where font name and font type * are strings specifying the name and type of the font, separated by a comma. When a hyphen appears in font * name, it means "none specified". Both of font name and font type can be localized values. * &"-,Bold" - code for "bold font style" * &B - also means "bold font style". * &"-,Regular" - code for "regular font style" * &"-,Italic" - code for "italic font style" * &I - also means "italic font style" * &"-,Bold Italic" code for "bold italic font style" * &O - code for "outline style" * &H - code for "shadow style" * */ class HeaderFooter { // Header/footer image location const IMAGE_HEADER_LEFT = 'LH'; const IMAGE_HEADER_CENTER = 'CH'; const IMAGE_HEADER_RIGHT = 'RH'; const IMAGE_FOOTER_LEFT = 'LF'; const IMAGE_FOOTER_CENTER = 'CF'; const IMAGE_FOOTER_RIGHT = 'RF'; /** * OddHeader. */ private string $oddHeader = ''; /** * OddFooter. */ private string $oddFooter = ''; /** * EvenHeader. */ private string $evenHeader = ''; /** * EvenFooter. */ private string $evenFooter = ''; /** * FirstHeader. */ private string $firstHeader = ''; /** * FirstFooter. */ private string $firstFooter = ''; /** * Different header for Odd/Even, defaults to false. */ private bool $differentOddEven = false; /** * Different header for first page, defaults to false. */ private bool $differentFirst = false; /** * Scale with document, defaults to true. */ private bool $scaleWithDocument = true; /** * Align with margins, defaults to true. */ private bool $alignWithMargins = true; /** * Header/footer images. * * @var HeaderFooterDrawing[] */ private array $headerFooterImages = []; /** * Create a new HeaderFooter. */ public function __construct() { } /** * Get OddHeader. */ public function getOddHeader(): string { return $this->oddHeader; } /** * Set OddHeader. * * @return $this */ public function setOddHeader(string $oddHeader) { $this->oddHeader = $oddHeader; return $this; } /** * Get OddFooter. */ public function getOddFooter(): string { return $this->oddFooter; } /** * Set OddFooter. * * @return $this */ public function setOddFooter(string $oddFooter) { $this->oddFooter = $oddFooter; return $this; } /** * Get EvenHeader. */ public function getEvenHeader(): string { return $this->evenHeader; } /** * Set EvenHeader. * * @return $this */ public function setEvenHeader(string $eventHeader) { $this->evenHeader = $eventHeader; return $this; } /** * Get EvenFooter. */ public function getEvenFooter(): string { return $this->evenFooter; } /** * Set EvenFooter. * * @return $this */ public function setEvenFooter(string $evenFooter) { $this->evenFooter = $evenFooter; return $this; } /** * Get FirstHeader. */ public function getFirstHeader(): string { return $this->firstHeader; } /** * Set FirstHeader. * * @return $this */ public function setFirstHeader(string $firstHeader) { $this->firstHeader = $firstHeader; return $this; } /** * Get FirstFooter. */ public function getFirstFooter(): string { return $this->firstFooter; } /** * Set FirstFooter. * * @return $this */ public function setFirstFooter(string $firstFooter) { $this->firstFooter = $firstFooter; return $this; } /** * Get DifferentOddEven. */ public function getDifferentOddEven(): bool { return $this->differentOddEven; } /** * Set DifferentOddEven. * * @return $this */ public function setDifferentOddEven(bool $differentOddEvent) { $this->differentOddEven = $differentOddEvent; return $this; } /** * Get DifferentFirst. */ public function getDifferentFirst(): bool { return $this->differentFirst; } /** * Set DifferentFirst. * * @return $this */ public function setDifferentFirst(bool $differentFirst) { $this->differentFirst = $differentFirst; return $this; } /** * Get ScaleWithDocument. */ public function getScaleWithDocument(): bool { return $this->scaleWithDocument; } /** * Set ScaleWithDocument. * * @return $this */ public function setScaleWithDocument(bool $scaleWithDocument) { $this->scaleWithDocument = $scaleWithDocument; return $this; } /** * Get AlignWithMargins. */ public function getAlignWithMargins(): bool { return $this->alignWithMargins; } /** * Set AlignWithMargins. * * @return $this */ public function setAlignWithMargins(bool $alignWithMargins) { $this->alignWithMargins = $alignWithMargins; return $this; } /** * Add header/footer image. * * @return $this */ public function addImage(HeaderFooterDrawing $image, string $location = self::IMAGE_HEADER_LEFT) { $this->headerFooterImages[$location] = $image; return $this; } /** * Remove header/footer image. * * @return $this */ public function removeImage(string $location = self::IMAGE_HEADER_LEFT) { if (isset($this->headerFooterImages[$location])) { unset($this->headerFooterImages[$location]); } return $this; } /** * Set header/footer images. * * @param HeaderFooterDrawing[] $images * * @return $this */ public function setImages(array $images) { $this->headerFooterImages = $images; return $this; } /** * Get header/footer images. * * @return HeaderFooterDrawing[] */ public function getImages(): array { // Sort array $images = []; if (isset($this->headerFooterImages[self::IMAGE_HEADER_LEFT])) { $images[self::IMAGE_HEADER_LEFT] = $this->headerFooterImages[self::IMAGE_HEADER_LEFT]; } if (isset($this->headerFooterImages[self::IMAGE_HEADER_CENTER])) { $images[self::IMAGE_HEADER_CENTER] = $this->headerFooterImages[self::IMAGE_HEADER_CENTER]; } if (isset($this->headerFooterImages[self::IMAGE_HEADER_RIGHT])) { $images[self::IMAGE_HEADER_RIGHT] = $this->headerFooterImages[self::IMAGE_HEADER_RIGHT]; } if (isset($this->headerFooterImages[self::IMAGE_FOOTER_LEFT])) { $images[self::IMAGE_FOOTER_LEFT] = $this->headerFooterImages[self::IMAGE_FOOTER_LEFT]; } if (isset($this->headerFooterImages[self::IMAGE_FOOTER_CENTER])) { $images[self::IMAGE_FOOTER_CENTER] = $this->headerFooterImages[self::IMAGE_FOOTER_CENTER]; } if (isset($this->headerFooterImages[self::IMAGE_FOOTER_RIGHT])) { $images[self::IMAGE_FOOTER_RIGHT] = $this->headerFooterImages[self::IMAGE_FOOTER_RIGHT]; } $this->headerFooterImages = $images; return $this->headerFooterImages; } /** * Implement PHP __clone to create a deep clone, not just a shallow copy. */ public function __clone() { $vars = get_object_vars($this); foreach ($vars as $key => $value) { if (is_object($value)) { $this->$key = clone $value; } else { $this->$key = $value; } } } } PK!j+ ))9libraries/vendor/PhpSpreadsheet/Worksheet/BaseDrawing.phpnu[setShadow(); // Set image index ++self::$imageCounter; $this->imageIndex = self::$imageCounter; } public function __destruct() { $this->worksheet = null; } public function getImageIndex(): int { return $this->imageIndex; } public function getName(): string { return $this->name; } public function setName(string $name): self { $this->name = $name; return $this; } public function getDescription(): string { return $this->description; } public function setDescription(string $description): self { $this->description = $description; return $this; } public function getWorksheet(): ?Worksheet { return $this->worksheet; } /** * Set Worksheet. * * @param bool $overrideOld If a Worksheet has already been assigned, overwrite it and remove image from old Worksheet? */ public function setWorksheet(?Worksheet $worksheet = null, bool $overrideOld = false): self { if ($this->worksheet === null) { // Add drawing to Worksheet if ($worksheet !== null) { $this->worksheet = $worksheet; if (!($this instanceof Drawing && $this->getPath() === '')) { $this->worksheet->getCell($this->coordinates); } $this->worksheet->getDrawingCollection() ->append($this); } } else { if ($overrideOld) { // Remove drawing from old Worksheet $iterator = $this->worksheet->getDrawingCollection()->getIterator(); while ($iterator->valid()) { if ($iterator->current()->getHashCode() === $this->getHashCode()) { $this->worksheet->getDrawingCollection()->offsetUnset($iterator->key()); $this->worksheet = null; break; } } // Set new Worksheet $this->setWorksheet($worksheet); } else { throw new PhpSpreadsheetException('A Worksheet has already been assigned. Drawings can only exist on one Worksheet.'); } } return $this; } public function getCoordinates(): string { return $this->coordinates; } public function setCoordinates(string $coordinates): self { $this->coordinates = $coordinates; if ($this->worksheet !== null) { if (!($this instanceof Drawing && $this->getPath() === '')) { $this->worksheet->getCell($this->coordinates); } } return $this; } public function getOffsetX(): int { return $this->offsetX; } public function setOffsetX(int $offsetX): self { $this->offsetX = $offsetX; return $this; } public function getOffsetY(): int { return $this->offsetY; } public function setOffsetY(int $offsetY): self { $this->offsetY = $offsetY; return $this; } public function getCoordinates2(): string { return $this->coordinates2; } public function setCoordinates2(string $coordinates2): self { $this->coordinates2 = $coordinates2; return $this; } public function getOffsetX2(): int { return $this->offsetX2; } public function setOffsetX2(int $offsetX2): self { $this->offsetX2 = $offsetX2; return $this; } public function getOffsetY2(): int { return $this->offsetY2; } public function setOffsetY2(int $offsetY2): self { $this->offsetY2 = $offsetY2; return $this; } public function getWidth(): int { return $this->width; } public function setWidth(int $width): self { // Resize proportional? if ($this->resizeProportional && $width != 0) { $ratio = $this->height / ($this->width != 0 ? $this->width : 1); $this->height = (int) round($ratio * $width); } // Set width $this->width = $width; return $this; } public function getHeight(): int { return $this->height; } public function setHeight(int $height): self { // Resize proportional? if ($this->resizeProportional && $height != 0) { $ratio = $this->width / ($this->height != 0 ? $this->height : 1); $this->width = (int) round($ratio * $height); } // Set height $this->height = $height; return $this; } /** * Set width and height with proportional resize. * * Example: * * $objDrawing->setResizeProportional(true); * $objDrawing->setWidthAndHeight(160,120); * * * @author Vincent@luo MSN:kele_100@hotmail.com */ public function setWidthAndHeight(int $width, int $height): self { if ($this->width === 0 || $this->height === 0 || $width === 0 || $height === 0 || !$this->resizeProportional) { $this->width = $width; $this->height = $height; } else { $xratio = $width / $this->width; $yratio = $height / $this->height; if (($xratio * $this->height) < $height) { $this->height = (int) ceil($xratio * $this->height); $this->width = $width; } else { $this->width = (int) ceil($yratio * $this->width); $this->height = $height; } } return $this; } public function getResizeProportional(): bool { return $this->resizeProportional; } public function setResizeProportional(bool $resizeProportional): self { $this->resizeProportional = $resizeProportional; return $this; } public function getRotation(): int { return $this->rotation; } public function setRotation(int $rotation): self { $this->rotation = $rotation; return $this; } public function getShadow(): Shadow { return $this->shadow; } public function setShadow(?Shadow $shadow = null): self { $this->shadow = $shadow ?? new Shadow(); return $this; } /** * Get hash code. * * @return string Hash code */ public function getHashCode(): string { return md5( $this->name . $this->description . (($this->worksheet === null) ? '' : (string) $this->worksheet->getHashInt()) . $this->coordinates . $this->offsetX . $this->offsetY . $this->coordinates2 . $this->offsetX2 . $this->offsetY2 . $this->width . $this->height . $this->rotation . $this->shadow->getHashCode() . __CLASS__ ); } /** * Implement PHP __clone to create a deep clone, not just a shallow copy. */ public function __clone() { $vars = get_object_vars($this); foreach ($vars as $key => $value) { if ($key == 'worksheet') { $this->worksheet = null; } elseif (is_object($value)) { $this->$key = clone $value; } else { $this->$key = $value; } } } public function setHyperlink(?Hyperlink $hyperlink = null): void { $this->hyperlink = $hyperlink; } public function getHyperlink(): ?Hyperlink { return $this->hyperlink; } /** * Set Fact Sizes and Type of Image. */ protected function setSizesAndType(string $path): void { if ($this->imageWidth === 0 && $this->imageHeight === 0 && $this->type === IMAGETYPE_UNKNOWN) { $imageData = getimagesize($path); if (!empty($imageData)) { $this->imageWidth = $imageData[0]; $this->imageHeight = $imageData[1]; $this->type = $imageData[2]; } } if ($this->width === 0 && $this->height === 0) { $this->width = $this->imageWidth; $this->height = $this->imageHeight; } } /** * Get Image Type. */ public function getType(): int { return $this->type; } public function getImageWidth(): int { return $this->imageWidth; } public function getImageHeight(): int { return $this->imageHeight; } public function getEditAs(): string { return $this->editAs; } public function setEditAs(string $editAs): self { $this->editAs = $editAs; return $this; } public function validEditAs(): bool { return in_array($this->editAs, self::VALID_EDIT_AS, true); } /** * @return null|SimpleXMLElement|string[] */ public function getSrcRect() { return $this->srcRect; } /** * @param null|SimpleXMLElement|string[] $srcRect */ public function setSrcRect($srcRect): self { $this->srcRect = $srcRect; return $this; } public function setFlipHorizontal(bool $flipHorizontal): self { $this->flipHorizontal = $flipHorizontal; return $this; } public function getFlipHorizontal(): bool { return $this->flipHorizontal; } public function setFlipVertical(bool $flipVertical): self { $this->flipVertical = $flipVertical; return $this; } public function getFlipVertical(): bool { return $this->flipVertical; } public function setOpacity(?int $opacity): self { $this->opacity = $opacity; return $this; } public function getOpacity(): ?int { return $this->opacity; } } PK!0QQ<libraries/vendor/PhpSpreadsheet/Worksheet/ColumnIterator.phpnu[ */ class ColumnIterator implements NativeIterator { /** * Worksheet to iterate. */ private Worksheet $worksheet; /** * Current iterator position. */ private int $currentColumnIndex = 1; /** * Start position. */ private int $startColumnIndex = 1; /** * End position. */ private int $endColumnIndex = 1; /** * Create a new column iterator. * * @param Worksheet $worksheet The worksheet to iterate over * @param string $startColumn The column address at which to start iterating * @param ?string $endColumn Optionally, the column address at which to stop iterating */ public function __construct(Worksheet $worksheet, string $startColumn = 'A', ?string $endColumn = null) { // Set subject $this->worksheet = $worksheet; $this->resetEnd($endColumn); $this->resetStart($startColumn); } /** * Destructor. */ public function __destruct() { unset($this->worksheet); } /** * (Re)Set the start column and the current column pointer. * * @param string $startColumn The column address at which to start iterating * * @return $this */ public function resetStart(string $startColumn = 'A') { $startColumnIndex = Coordinate::columnIndexFromString($startColumn); if ($startColumnIndex > Coordinate::columnIndexFromString($this->worksheet->getHighestColumn())) { throw new Exception( "Start column ({$startColumn}) is beyond highest column ({$this->worksheet->getHighestColumn()})" ); } $this->startColumnIndex = $startColumnIndex; if ($this->endColumnIndex < $this->startColumnIndex) { $this->endColumnIndex = $this->startColumnIndex; } $this->seek($startColumn); return $this; } /** * (Re)Set the end column. * * @param ?string $endColumn The column address at which to stop iterating * * @return $this */ public function resetEnd(?string $endColumn = null) { $endColumn = $endColumn ?: $this->worksheet->getHighestColumn(); $this->endColumnIndex = Coordinate::columnIndexFromString($endColumn); return $this; } /** * Set the column pointer to the selected column. * * @param string $column The column address to set the current pointer at * * @return $this */ public function seek(string $column = 'A') { $column = Coordinate::columnIndexFromString($column); if (($column < $this->startColumnIndex) || ($column > $this->endColumnIndex)) { throw new PhpSpreadsheetException( "Column $column is out of range ({$this->startColumnIndex} - {$this->endColumnIndex})" ); } $this->currentColumnIndex = $column; return $this; } /** * Rewind the iterator to the starting column. */ public function rewind(): void { $this->currentColumnIndex = $this->startColumnIndex; } /** * Return the current column in this worksheet. */ public function current(): Column { return new Column($this->worksheet, Coordinate::stringFromColumnIndex($this->currentColumnIndex)); } /** * Return the current iterator key. */ public function key(): string { return Coordinate::stringFromColumnIndex($this->currentColumnIndex); } /** * Set the iterator to its next value. */ public function next(): void { ++$this->currentColumnIndex; } /** * Set the iterator to its previous value. */ public function prev(): void { --$this->currentColumnIndex; } /** * Indicate if more columns exist in the worksheet range of columns that we're iterating. */ public function valid(): bool { return $this->currentColumnIndex <= $this->endColumnIndex && $this->currentColumnIndex >= $this->startColumnIndex; } } PK!^%9libraries/vendor/PhpSpreadsheet/Worksheet/Validations.phpnu[getTitle()) { // throw new Exception('Reference is not for this worksheet'); // } return empty($worksheet) ? strtoupper("$address") : $worksheet . '!' . strtoupper("$address"); } if (is_array($cellAddress)) { $cellAddress = CellAddress::fromColumnRowArray($cellAddress); } return (string) $cellAddress; } /** * Validate a cell address or cell range. * * @param AddressRange|AddressRange|AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|CellAddress|int|string $cellRange Coordinate of the cells as a string, eg: 'C5:F12'; * or as an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 12]), * or as a CellAddress or AddressRange object. */ public static function validateCellOrCellRange($cellRange): string { if (is_string($cellRange) || is_numeric($cellRange)) { // Convert a single column reference like 'A' to 'A:A', // a single row reference like '1' to '1:1' $cellRange = Preg::replace('/^([A-Z]+|\d+)$/', '${1}:${1}', (string) $cellRange); } elseif (is_object($cellRange) && $cellRange instanceof CellAddress) { $cellRange = new CellRange($cellRange, $cellRange); } return self::validateCellRange($cellRange); } private const SETMAXROW = '${1}1:${2}' . AddressRange::MAX_ROW; private const SETMAXCOL = 'A${1}:' . AddressRange::MAX_COLUMN . '${2}'; /** * Convert Column ranges like 'A:C' to 'A1:C1048576' * or Row ranges like '1:3' to 'A1:XFD3'. */ public static function convertWholeRowColumn(?string $addressRange): string { return Preg::replace( ['/^([A-Z]+):([A-Z]+)$/i', '/^(\d+):(\d+)$/'], [self::SETMAXROW, self::SETMAXCOL], $addressRange ?? '' ); } /** * Validate a cell range. * * @param AddressRange|AddressRange|AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $cellRange Coordinate of the cells as a string, eg: 'C5:F12'; * or as an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 12]), * or as an AddressRange object. */ public static function validateCellRange($cellRange): string { if (is_string($cellRange)) { [$worksheet, $addressRange] = Worksheet::extractSheetTitle($cellRange, true); // Convert Column ranges like 'A:C' to 'A1:C1048576' // or Row ranges like '1:3' to 'A1:XFD3' $addressRange = self::convertWholeRowColumn($addressRange); return empty($worksheet) ? strtoupper($addressRange) : $worksheet . '!' . strtoupper($addressRange); } if (is_array($cellRange)) { switch (count($cellRange)) { case 4: $from = [$cellRange[0], $cellRange[1]]; $to = [$cellRange[2], $cellRange[3]]; break; case 2: $from = [$cellRange[0], $cellRange[1]]; $to = [$cellRange[0], $cellRange[1]]; break; default: throw new SpreadsheetException('CellRange array length must be 2 or 4'); } $cellRange = new CellRange(CellAddress::fromColumnRowArray($from), CellAddress::fromColumnRowArray($to)); } return (string) $cellRange; } public static function definedNameToCoordinate(string $coordinate, Worksheet $worksheet): string { // Uppercase coordinate $coordinate = strtoupper($coordinate); // Eliminate leading equal sign $testCoordinate = Preg::replace('/^=/', '', $coordinate); $defined = $worksheet->getParentOrThrow()->getDefinedName($testCoordinate, $worksheet); if ($defined !== null) { if ($defined->getWorksheet() === $worksheet && !$defined->isFormula()) { $coordinate = Preg::replace('/^=/', '', $defined->getValue()); } } return $coordinate; } } PK!.]]7libraries/vendor/PhpSpreadsheet/Worksheet/PageSetup.phpnu[ * Paper size taken from Office Open XML Part 4 - Markup Language Reference, page 1988:. * * 1 = Letter paper (8.5 in. by 11 in.) * 2 = Letter small paper (8.5 in. by 11 in.) * 3 = Tabloid paper (11 in. by 17 in.) * 4 = Ledger paper (17 in. by 11 in.) * 5 = Legal paper (8.5 in. by 14 in.) * 6 = Statement paper (5.5 in. by 8.5 in.) * 7 = Executive paper (7.25 in. by 10.5 in.) * 8 = A3 paper (297 mm by 420 mm) * 9 = A4 paper (210 mm by 297 mm) * 10 = A4 small paper (210 mm by 297 mm) * 11 = A5 paper (148 mm by 210 mm) * 12 = B4 paper (250 mm by 353 mm) * 13 = B5 paper (176 mm by 250 mm) * 14 = Folio paper (8.5 in. by 13 in.) * 15 = Quarto paper (215 mm by 275 mm) * 16 = Standard paper (10 in. by 14 in.) * 17 = Standard paper (11 in. by 17 in.) * 18 = Note paper (8.5 in. by 11 in.) * 19 = #9 envelope (3.875 in. by 8.875 in.) * 20 = #10 envelope (4.125 in. by 9.5 in.) * 21 = #11 envelope (4.5 in. by 10.375 in.) * 22 = #12 envelope (4.75 in. by 11 in.) * 23 = #14 envelope (5 in. by 11.5 in.) * 24 = C paper (17 in. by 22 in.) * 25 = D paper (22 in. by 34 in.) * 26 = E paper (34 in. by 44 in.) * 27 = DL envelope (110 mm by 220 mm) * 28 = C5 envelope (162 mm by 229 mm) * 29 = C3 envelope (324 mm by 458 mm) * 30 = C4 envelope (229 mm by 324 mm) * 31 = C6 envelope (114 mm by 162 mm) * 32 = C65 envelope (114 mm by 229 mm) * 33 = B4 envelope (250 mm by 353 mm) * 34 = B5 envelope (176 mm by 250 mm) * 35 = B6 envelope (176 mm by 125 mm) * 36 = Italy envelope (110 mm by 230 mm) * 37 = Monarch envelope (3.875 in. by 7.5 in.). * 38 = 6 3/4 envelope (3.625 in. by 6.5 in.) * 39 = US standard fanfold (14.875 in. by 11 in.) * 40 = German standard fanfold (8.5 in. by 12 in.) * 41 = German legal fanfold (8.5 in. by 13 in.) * 42 = ISO B4 (250 mm by 353 mm) * 43 = Japanese double postcard (200 mm by 148 mm) * 44 = Standard paper (9 in. by 11 in.) * 45 = Standard paper (10 in. by 11 in.) * 46 = Standard paper (15 in. by 11 in.) * 47 = Invite envelope (220 mm by 220 mm) * 50 = Letter extra paper (9.275 in. by 12 in.) * 51 = Legal extra paper (9.275 in. by 15 in.) * 52 = Tabloid extra paper (11.69 in. by 18 in.) * 53 = A4 extra paper (236 mm by 322 mm) * 54 = Letter transverse paper (8.275 in. by 11 in.) * 55 = A4 transverse paper (210 mm by 297 mm) * 56 = Letter extra transverse paper (9.275 in. by 12 in.) * 57 = SuperA/SuperA/A4 paper (227 mm by 356 mm) * 58 = SuperB/SuperB/A3 paper (305 mm by 487 mm) * 59 = Letter plus paper (8.5 in. by 12.69 in.) * 60 = A4 plus paper (210 mm by 330 mm) * 61 = A5 transverse paper (148 mm by 210 mm) * 62 = JIS B5 transverse paper (182 mm by 257 mm) * 63 = A3 extra paper (322 mm by 445 mm) * 64 = A5 extra paper (174 mm by 235 mm) * 65 = ISO B5 extra paper (201 mm by 276 mm) * 66 = A2 paper (420 mm by 594 mm) * 67 = A3 transverse paper (297 mm by 420 mm) * 68 = A3 extra transverse paper (322 mm by 445 mm) * */ class PageSetup { // Paper size const PAPERSIZE_LETTER = 1; const PAPERSIZE_LETTER_SMALL = 2; const PAPERSIZE_TABLOID = 3; const PAPERSIZE_LEDGER = 4; const PAPERSIZE_LEGAL = 5; const PAPERSIZE_STATEMENT = 6; const PAPERSIZE_EXECUTIVE = 7; const PAPERSIZE_A3 = 8; const PAPERSIZE_A4 = 9; const PAPERSIZE_A4_SMALL = 10; const PAPERSIZE_A5 = 11; const PAPERSIZE_B4 = 12; const PAPERSIZE_B5 = 13; const PAPERSIZE_FOLIO = 14; const PAPERSIZE_QUARTO = 15; const PAPERSIZE_STANDARD_1 = 16; const PAPERSIZE_STANDARD_2 = 17; const PAPERSIZE_NOTE = 18; const PAPERSIZE_NO9_ENVELOPE = 19; const PAPERSIZE_NO10_ENVELOPE = 20; const PAPERSIZE_NO11_ENVELOPE = 21; const PAPERSIZE_NO12_ENVELOPE = 22; const PAPERSIZE_NO14_ENVELOPE = 23; const PAPERSIZE_C = 24; const PAPERSIZE_D = 25; const PAPERSIZE_E = 26; const PAPERSIZE_DL_ENVELOPE = 27; const PAPERSIZE_C5_ENVELOPE = 28; const PAPERSIZE_C3_ENVELOPE = 29; const PAPERSIZE_C4_ENVELOPE = 30; const PAPERSIZE_C6_ENVELOPE = 31; const PAPERSIZE_C65_ENVELOPE = 32; const PAPERSIZE_B4_ENVELOPE = 33; const PAPERSIZE_B5_ENVELOPE = 34; const PAPERSIZE_B6_ENVELOPE = 35; const PAPERSIZE_ITALY_ENVELOPE = 36; const PAPERSIZE_MONARCH_ENVELOPE = 37; const PAPERSIZE_6_3_4_ENVELOPE = 38; const PAPERSIZE_US_STANDARD_FANFOLD = 39; const PAPERSIZE_GERMAN_STANDARD_FANFOLD = 40; const PAPERSIZE_GERMAN_LEGAL_FANFOLD = 41; const PAPERSIZE_ISO_B4 = 42; const PAPERSIZE_JAPANESE_DOUBLE_POSTCARD = 43; const PAPERSIZE_STANDARD_PAPER_1 = 44; const PAPERSIZE_STANDARD_PAPER_2 = 45; const PAPERSIZE_STANDARD_PAPER_3 = 46; const PAPERSIZE_INVITE_ENVELOPE = 47; const PAPERSIZE_LETTER_EXTRA_PAPER = 48; const PAPERSIZE_LEGAL_EXTRA_PAPER = 49; const PAPERSIZE_TABLOID_EXTRA_PAPER = 50; const PAPERSIZE_A4_EXTRA_PAPER = 51; const PAPERSIZE_LETTER_TRANSVERSE_PAPER = 52; const PAPERSIZE_A4_TRANSVERSE_PAPER = 53; const PAPERSIZE_LETTER_EXTRA_TRANSVERSE_PAPER = 54; const PAPERSIZE_SUPERA_SUPERA_A4_PAPER = 55; const PAPERSIZE_SUPERB_SUPERB_A3_PAPER = 56; const PAPERSIZE_LETTER_PLUS_PAPER = 57; const PAPERSIZE_A4_PLUS_PAPER = 58; const PAPERSIZE_A5_TRANSVERSE_PAPER = 59; const PAPERSIZE_JIS_B5_TRANSVERSE_PAPER = 60; const PAPERSIZE_A3_EXTRA_PAPER = 61; const PAPERSIZE_A5_EXTRA_PAPER = 62; const PAPERSIZE_ISO_B5_EXTRA_PAPER = 63; const PAPERSIZE_A2_PAPER = 64; const PAPERSIZE_A3_TRANSVERSE_PAPER = 65; const PAPERSIZE_A3_EXTRA_TRANSVERSE_PAPER = 66; // Page orientation const ORIENTATION_DEFAULT = 'default'; const ORIENTATION_LANDSCAPE = 'landscape'; const ORIENTATION_PORTRAIT = 'portrait'; // Print Range Set Method const SETPRINTRANGE_OVERWRITE = 'O'; const SETPRINTRANGE_INSERT = 'I'; const PAGEORDER_OVER_THEN_DOWN = 'overThenDown'; const PAGEORDER_DOWN_THEN_OVER = 'downThenOver'; /** * Paper size default. */ private static int $paperSizeDefault = self::PAPERSIZE_LETTER; /** * Paper size. */ private ?int $paperSize = null; /** * Orientation default. */ private static string $orientationDefault = self::ORIENTATION_DEFAULT; /** * Orientation. */ private string $orientation; /** * Scale (Print Scale). * * Print scaling. Valid values range from 10 to 400 * This setting is overridden when fitToWidth and/or fitToHeight are in use */ private ?int $scale = 100; /** * Fit To Page * Whether scale or fitToWith / fitToHeight applies. */ private bool $fitToPage = false; /** * Fit To Height * Number of vertical pages to fit on. */ private ?int $fitToHeight = 1; /** * Fit To Width * Number of horizontal pages to fit on. */ private ?int $fitToWidth = 1; /** * Columns to repeat at left. * * @var array Containing start column and end column, empty array if option unset */ private array $columnsToRepeatAtLeft = ['', '']; /** * Rows to repeat at top. * * @var array Containing start row number and end row number, empty array if option unset */ private array $rowsToRepeatAtTop = [0, 0]; /** * Center page horizontally. */ private bool $horizontalCentered = false; /** * Center page vertically. */ private bool $verticalCentered = false; /** * Print area. */ private ?string $printArea = null; /** * First page number. */ private ?int $firstPageNumber = null; private string $pageOrder = self::PAGEORDER_DOWN_THEN_OVER; /** * Create a new PageSetup. */ public function __construct() { $this->orientation = self::$orientationDefault; } /** * Get Paper Size. */ public function getPaperSize(): int { return $this->paperSize ?? self::$paperSizeDefault; } /** * Set Paper Size. * * @param int $paperSize see self::PAPERSIZE_* * * @return $this */ public function setPaperSize(int $paperSize) { $this->paperSize = $paperSize; return $this; } /** * Get Paper Size default. */ public static function getPaperSizeDefault(): int { return self::$paperSizeDefault; } /** * Set Paper Size Default. */ public static function setPaperSizeDefault(int $paperSize): void { self::$paperSizeDefault = $paperSize; } /** * Get Orientation. */ public function getOrientation(): string { return $this->orientation; } /** * Set Orientation. * * @param string $orientation see self::ORIENTATION_* * * @return $this */ public function setOrientation(string $orientation) { if ($orientation === self::ORIENTATION_LANDSCAPE || $orientation === self::ORIENTATION_PORTRAIT || $orientation === self::ORIENTATION_DEFAULT) { $this->orientation = $orientation; } return $this; } public static function getOrientationDefault(): string { return self::$orientationDefault; } public static function setOrientationDefault(string $orientation): void { if ($orientation === self::ORIENTATION_LANDSCAPE || $orientation === self::ORIENTATION_PORTRAIT || $orientation === self::ORIENTATION_DEFAULT) { self::$orientationDefault = $orientation; } } /** * Get Scale. */ public function getScale(): ?int { return $this->scale; } /** * Set Scale. * Print scaling. Valid values range from 10 to 400 * This setting is overridden when fitToWidth and/or fitToHeight are in use. * * @param bool $update Update fitToPage so scaling applies rather than fitToHeight / fitToWidth * * @return $this */ public function setScale(?int $scale, bool $update = true) { // Microsoft Office Excel 2007 only allows setting a scale between 10 and 400 via the user interface, // but it is apparently still able to handle any scale >= 0, where 0 results in 100 if ($scale === null || $scale >= 0) { $this->scale = $scale; if ($update) { $this->fitToPage = false; } } else { throw new PhpSpreadsheetException('Scale must not be negative'); } return $this; } /** * Get Fit To Page. */ public function getFitToPage(): bool { return $this->fitToPage; } /** * Set Fit To Page. * * @return $this */ public function setFitToPage(bool $fitToPage) { $this->fitToPage = $fitToPage; return $this; } /** * Get Fit To Height. */ public function getFitToHeight(): ?int { return $this->fitToHeight; } /** * Set Fit To Height. * * @param bool $update Update fitToPage so it applies rather than scaling * * @return $this */ public function setFitToHeight(?int $fitToHeight, bool $update = true) { $this->fitToHeight = $fitToHeight; if ($update) { $this->fitToPage = true; } return $this; } /** * Get Fit To Width. */ public function getFitToWidth(): ?int { return $this->fitToWidth; } /** * Set Fit To Width. * * @param bool $update Update fitToPage so it applies rather than scaling * * @return $this */ public function setFitToWidth(?int $value, bool $update = true) { $this->fitToWidth = $value; if ($update) { $this->fitToPage = true; } return $this; } /** * Is Columns to repeat at left set? */ public function isColumnsToRepeatAtLeftSet(): bool { if (!empty($this->columnsToRepeatAtLeft)) { if ($this->columnsToRepeatAtLeft[0] != '' && $this->columnsToRepeatAtLeft[1] != '') { return true; } } return false; } /** * Get Columns to repeat at left. * * @return array Containing start column and end column, empty array if option unset */ public function getColumnsToRepeatAtLeft(): array { return $this->columnsToRepeatAtLeft; } /** * Set Columns to repeat at left. * * @param array $columnsToRepeatAtLeft Containing start column and end column, empty array if option unset * * @return $this */ public function setColumnsToRepeatAtLeft(array $columnsToRepeatAtLeft) { $this->columnsToRepeatAtLeft = $columnsToRepeatAtLeft; return $this; } /** * Set Columns to repeat at left by start and end. * * @param string $start eg: 'A' * @param string $end eg: 'B' * * @return $this */ public function setColumnsToRepeatAtLeftByStartAndEnd(string $start, string $end) { $this->columnsToRepeatAtLeft = [$start, $end]; return $this; } /** * Is Rows to repeat at top set? */ public function isRowsToRepeatAtTopSet(): bool { if (!empty($this->rowsToRepeatAtTop)) { if ($this->rowsToRepeatAtTop[0] != 0 && $this->rowsToRepeatAtTop[1] != 0) { return true; } } return false; } /** * Get Rows to repeat at top. * * @return array Containing start column and end column, empty array if option unset */ public function getRowsToRepeatAtTop(): array { return $this->rowsToRepeatAtTop; } /** * Set Rows to repeat at top. * * @param array $rowsToRepeatAtTop Containing start column and end column, empty array if option unset * * @return $this */ public function setRowsToRepeatAtTop(array $rowsToRepeatAtTop) { $this->rowsToRepeatAtTop = $rowsToRepeatAtTop; return $this; } /** * Set Rows to repeat at top by start and end. * * @param int $start eg: 1 * @param int $end eg: 1 * * @return $this */ public function setRowsToRepeatAtTopByStartAndEnd(int $start, int $end) { $this->rowsToRepeatAtTop = [$start, $end]; return $this; } /** * Get center page horizontally. */ public function getHorizontalCentered(): bool { return $this->horizontalCentered; } /** * Set center page horizontally. * * @return $this */ public function setHorizontalCentered(bool $value) { $this->horizontalCentered = $value; return $this; } /** * Get center page vertically. */ public function getVerticalCentered(): bool { return $this->verticalCentered; } /** * Set center page vertically. * * @return $this */ public function setVerticalCentered(bool $value) { $this->verticalCentered = $value; return $this; } /** * Get print area. * * @param int $index Identifier for a specific print area range if several ranges have been set * Default behaviour, or a index value of 0, will return all ranges as a comma-separated string * Otherwise, the specific range identified by the value of $index will be returned * Print areas are numbered from 1 */ public function getPrintArea(int $index = 0): string { if ($index == 0) { return (string) $this->printArea; } $printAreas = explode(',', (string) $this->printArea); if (isset($printAreas[$index - 1])) { return $printAreas[$index - 1]; } throw new PhpSpreadsheetException('Requested Print Area does not exist'); } /** * Is print area set? * * @param int $index Identifier for a specific print area range if several ranges have been set * Default behaviour, or an index value of 0, will identify whether any print range is set * Otherwise, existence of the range identified by the value of $index will be returned * Print areas are numbered from 1 */ public function isPrintAreaSet(int $index = 0): bool { if ($index == 0) { return $this->printArea !== null; } $printAreas = explode(',', (string) $this->printArea); return isset($printAreas[$index - 1]); } /** * Clear a print area. * * @param int $index Identifier for a specific print area range if several ranges have been set * Default behaviour, or an index value of 0, will clear all print ranges that are set * Otherwise, the range identified by the value of $index will be removed from the series * Print areas are numbered from 1 * * @return $this */ public function clearPrintArea(int $index = 0) { if ($index == 0) { $this->printArea = null; } else { $printAreas = explode(',', (string) $this->printArea); if (isset($printAreas[$index - 1])) { unset($printAreas[$index - 1]); $this->printArea = implode(',', $printAreas); } } return $this; } /** * Set print area. e.g. 'A1:D10' or 'A1:D10,G5:M20'. * * @param int $index Identifier for a specific print area range allowing several ranges to be set * When the method is "O"verwrite, then a positive integer index will overwrite that indexed * entry in the print areas list; a negative index value will identify which entry to * overwrite working bacward through the print area to the list, with the last entry as -1. * Specifying an index value of 0, will overwrite all existing print ranges. * When the method is "I"nsert, then a positive index will insert after that indexed entry in * the print areas list, while a negative index will insert before the indexed entry. * Specifying an index value of 0, will always append the new print range at the end of the * list. * Print areas are numbered from 1 * @param string $method Determines the method used when setting multiple print areas * Default behaviour, or the "O" method, overwrites existing print area * The "I" method, inserts the new print area before any specified index, or at the end of the list * * @return $this */ public function setPrintArea(string $value, int $index = 0, string $method = self::SETPRINTRANGE_OVERWRITE) { if (str_contains($value, '!')) { throw new PhpSpreadsheetException('Cell coordinate must not specify a worksheet.'); } elseif (!str_contains($value, ':')) { throw new PhpSpreadsheetException('Cell coordinate must be a range of cells.'); } elseif (str_contains($value, '$')) { throw new PhpSpreadsheetException('Cell coordinate must not be absolute.'); } $value = strtoupper($value); if (!$this->printArea) { $index = 0; } if ($method == self::SETPRINTRANGE_OVERWRITE) { if ($index == 0) { $this->printArea = $value; } else { $printAreas = explode(',', (string) $this->printArea); if ($index < 0) { $index = count($printAreas) - abs($index) + 1; } if (($index <= 0) || ($index > count($printAreas))) { throw new PhpSpreadsheetException('Invalid index for setting print range.'); } $printAreas[$index - 1] = $value; $this->printArea = implode(',', $printAreas); } } elseif ($method == self::SETPRINTRANGE_INSERT) { if ($index == 0) { $this->printArea = $this->printArea ? ($this->printArea . ',' . $value) : $value; } else { $printAreas = explode(',', (string) $this->printArea); if ($index < 0) { $index = (int) abs($index) - 1; } if ($index > count($printAreas)) { throw new PhpSpreadsheetException('Invalid index for setting print range.'); } $printAreas = array_merge(array_slice($printAreas, 0, $index), [$value], array_slice($printAreas, $index)); $this->printArea = implode(',', $printAreas); } } else { throw new PhpSpreadsheetException('Invalid method for setting print range.'); } return $this; } /** * Add a new print area (e.g. 'A1:D10' or 'A1:D10,G5:M20') to the list of print areas. * * @param int $index Identifier for a specific print area range allowing several ranges to be set * A positive index will insert after that indexed entry in the print areas list, while a * negative index will insert before the indexed entry. * Specifying an index value of 0, will always append the new print range at the end of the * list. * Print areas are numbered from 1 * * @return $this */ public function addPrintArea(string $value, int $index = -1) { return $this->setPrintArea($value, $index, self::SETPRINTRANGE_INSERT); } /** * Set print area. * * @param int $column1 Column 1 * @param int $row1 Row 1 * @param int $column2 Column 2 * @param int $row2 Row 2 * @param int $index Identifier for a specific print area range allowing several ranges to be set * When the method is "O"verwrite, then a positive integer index will overwrite that indexed * entry in the print areas list; a negative index value will identify which entry to * overwrite working backward through the print area to the list, with the last entry as -1. * Specifying an index value of 0, will overwrite all existing print ranges. * When the method is "I"nsert, then a positive index will insert after that indexed entry in * the print areas list, while a negative index will insert before the indexed entry. * Specifying an index value of 0, will always append the new print range at the end of the * list. * Print areas are numbered from 1 * @param string $method Determines the method used when setting multiple print areas * Default behaviour, or the "O" method, overwrites existing print area * The "I" method, inserts the new print area before any specified index, or at the end of the list * * @return $this */ public function setPrintAreaByColumnAndRow(int $column1, int $row1, int $column2, int $row2, int $index = 0, string $method = self::SETPRINTRANGE_OVERWRITE) { return $this->setPrintArea( Coordinate::stringFromColumnIndex($column1) . $row1 . ':' . Coordinate::stringFromColumnIndex($column2) . $row2, $index, $method ); } /** * Add a new print area to the list of print areas. * * @param int $column1 Start Column for the print area * @param int $row1 Start Row for the print area * @param int $column2 End Column for the print area * @param int $row2 End Row for the print area * @param int $index Identifier for a specific print area range allowing several ranges to be set * A positive index will insert after that indexed entry in the print areas list, while a * negative index will insert before the indexed entry. * Specifying an index value of 0, will always append the new print range at the end of the * list. * Print areas are numbered from 1 * * @return $this */ public function addPrintAreaByColumnAndRow(int $column1, int $row1, int $column2, int $row2, int $index = -1) { return $this->setPrintArea( Coordinate::stringFromColumnIndex($column1) . $row1 . ':' . Coordinate::stringFromColumnIndex($column2) . $row2, $index, self::SETPRINTRANGE_INSERT ); } /** * Get first page number. */ public function getFirstPageNumber(): ?int { return $this->firstPageNumber; } /** * Set first page number. * * @return $this */ public function setFirstPageNumber(?int $value) { $this->firstPageNumber = $value; return $this; } /** * Reset first page number. * * @return $this */ public function resetFirstPageNumber() { return $this->setFirstPageNumber(null); } public function getPageOrder(): string { return $this->pageOrder; } public function setPageOrder(?string $pageOrder): self { if ($pageOrder === null || $pageOrder === self::PAGEORDER_DOWN_THEN_OVER || $pageOrder === self::PAGEORDER_OVER_THEN_DOWN) { $this->pageOrder = $pageOrder ?? self::PAGEORDER_DOWN_THEN_OVER; } return $this; } } PK!ԯ8libraries/vendor/PhpSpreadsheet/Worksheet/AutoFilter.phpnu[evaluated; } public function setEvaluated(bool $value): void { $this->evaluated = $value; } /** * Create a new AutoFilter. * * @param AddressRange|AddressRange|AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $range * A simple string containing a Cell range like 'A1:E10' is permitted * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), * or an AddressRange object. */ public function __construct($range = '', ?Worksheet $worksheet = null) { if ($range !== '') { [, $range] = Worksheet::extractSheetTitle(Validations::validateCellRange($range), true); } $this->range = $range ?? ''; $this->workSheet = $worksheet; } public function __destruct() { $this->workSheet = null; } /** * Get AutoFilter Parent Worksheet. */ public function getParent(): ?\TablePress\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet { return $this->workSheet; } /** * Set AutoFilter Parent Worksheet. * * @return $this */ public function setParent(?Worksheet $worksheet = null) { $this->evaluated = false; $this->workSheet = $worksheet; return $this; } /** * Get AutoFilter Range. */ public function getRange(): string { return $this->range; } /** * Set AutoFilter Cell Range. * * @param AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $range * A simple string containing a Cell range like 'A1:E10' or a Cell address like 'A1' is permitted * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), * or an AddressRange object. */ public function setRange($range = ''): self { $this->evaluated = false; // extract coordinate if ($range !== '') { [, $range] = Worksheet::extractSheetTitle(Validations::validateCellRange($range), true); } if (empty($range)) { // Discard all column rules $this->columns = []; $this->range = ''; return $this; } if (ctype_digit($range) || ctype_alpha($range)) { throw new Exception("{$range} is an invalid range for AutoFilter"); } $this->range = $range; // Discard any column rules that are no longer valid within this range [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range); foreach ($this->columns as $key => $value) { $colIndex = Coordinate::columnIndexFromString($key); if (($rangeStart[0] > $colIndex) || ($rangeEnd[0] < $colIndex)) { unset($this->columns[$key]); } } return $this; } public function setRangeToMaxRow(): self { $this->evaluated = false; if ($this->workSheet !== null) { $thisrange = $this->range; $range = (string) preg_replace('/\d+$/', (string) $this->workSheet->getHighestRow(), $thisrange); if ($range !== $thisrange) { $this->setRange($range); } } return $this; } /** * Get all AutoFilter Columns. * * @return AutoFilter\Column[] */ public function getColumns(): array { return $this->columns; } /** * Validate that the specified column is in the AutoFilter range. * * @param string $column Column name (e.g. A) * * @return int The column offset within the autofilter range */ public function testColumnInRange(string $column): int { if (empty($this->range)) { throw new Exception('No autofilter range is defined.'); } $columnIndex = Coordinate::columnIndexFromString($column); [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range); if (($rangeStart[0] > $columnIndex) || ($rangeEnd[0] < $columnIndex)) { throw new Exception('Column is outside of current autofilter range.'); } return $columnIndex - $rangeStart[0]; } /** * Get a specified AutoFilter Column Offset within the defined AutoFilter range. * * @param string $column Column name (e.g. A) * * @return int The offset of the specified column within the autofilter range */ public function getColumnOffset(string $column): int { return $this->testColumnInRange($column); } /** * Get a specified AutoFilter Column. * * @param string $column Column name (e.g. A) */ public function getColumn(string $column): AutoFilter\Column { $this->testColumnInRange($column); if (!isset($this->columns[$column])) { $this->columns[$column] = new AutoFilter\Column($column, $this); } return $this->columns[$column]; } /** * Get a specified AutoFilter Column by it's offset. * * @param int $columnOffset Column offset within range (starting from 0) */ public function getColumnByOffset(int $columnOffset): AutoFilter\Column { [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range); $pColumn = Coordinate::stringFromColumnIndex($rangeStart[0] + $columnOffset); return $this->getColumn($pColumn); } /** * Set AutoFilter. * * @param AutoFilter\Column|string $columnObjectOrString * A simple string containing a Column ID like 'A' is permitted * * @return $this */ public function setColumn($columnObjectOrString) { $this->evaluated = false; if ((is_string($columnObjectOrString)) && (!empty($columnObjectOrString))) { $column = $columnObjectOrString; } elseif ($columnObjectOrString instanceof AutoFilter\Column) { $column = $columnObjectOrString->getColumnIndex(); } else { throw new Exception('Column is not within the autofilter range.'); } $this->testColumnInRange($column); if (is_string($columnObjectOrString)) { $this->columns[$columnObjectOrString] = new AutoFilter\Column($columnObjectOrString, $this); } else { $columnObjectOrString->setParent($this); $this->columns[$column] = $columnObjectOrString; } ksort($this->columns); return $this; } /** * Clear a specified AutoFilter Column. * * @param string $column Column name (e.g. A) * * @return $this */ public function clearColumn(string $column) { $this->evaluated = false; $this->testColumnInRange($column); if (isset($this->columns[$column])) { unset($this->columns[$column]); } return $this; } /** * Shift an AutoFilter Column Rule to a different column. * * Note: This method bypasses validation of the destination column to ensure it is within this AutoFilter range. * Nor does it verify whether any column rule already exists at $toColumn, but will simply override any existing value. * Use with caution. * * @param string $fromColumn Column name (e.g. A) * @param string $toColumn Column name (e.g. B) * * @return $this */ public function shiftColumn(string $fromColumn, string $toColumn) { $this->evaluated = false; $fromColumn = strtoupper($fromColumn); $toColumn = strtoupper($toColumn); if (isset($this->columns[$fromColumn])) { $this->columns[$fromColumn]->setParent(); $this->columns[$fromColumn]->setColumnIndex($toColumn); $this->columns[$toColumn] = $this->columns[$fromColumn]; $this->columns[$toColumn]->setParent($this); unset($this->columns[$fromColumn]); ksort($this->columns); } return $this; } /** * Test if cell value is in the defined set of values. * * @param array{blanks: bool, filterValues: array>} $dataSet * @param mixed $cellValue */ protected static function filterTestInSimpleDataSet($cellValue, array $dataSet): bool { $dataSetValues = $dataSet['filterValues']; $blanks = $dataSet['blanks']; if (($cellValue === '') || ($cellValue === null)) { return $blanks; } return in_array($cellValue, $dataSetValues); } /** * Test if cell value is in the defined set of Excel date values. * * @param array{blanks: bool, filterValues: array>} $dataSet * @param mixed $cellValue */ protected static function filterTestInDateGroupSet($cellValue, array $dataSet): bool { $dateSet = $dataSet['filterValues']; $blanks = $dataSet['blanks']; if (($cellValue === '') || ($cellValue === null)) { return $blanks; } $timeZone = new DateTimeZone('UTC'); if (is_numeric($cellValue)) { $dateTime = Date::excelToDateTimeObject((float) $cellValue, $timeZone); $cellValue = (float) $cellValue; if ($cellValue < 1) { // Just the time part $dtVal = $dateTime->format('His'); $dateSet = $dateSet['time']; } elseif ($cellValue == floor($cellValue)) { // Just the date part $dtVal = $dateTime->format('Ymd'); $dateSet = $dateSet['date']; } else { // date and time parts $dtVal = $dateTime->format('YmdHis'); $dateSet = $dateSet['dateTime']; } foreach ($dateSet as $dateValue) { // Use of substr to extract value at the appropriate group level if (str_starts_with($dtVal, $dateValue)) { return true; } } } return false; } /** * Test if cell value is within a set of values defined by a ruleset. * * @param mixed[] $ruleSet * @param mixed $cellValue */ protected static function filterTestInCustomDataSet($cellValue, array $ruleSet): bool { /** @var array[] $dataSet */ $dataSet = $ruleSet['filterRules']; $join = $ruleSet['join']; $customRuleForBlanks = $ruleSet['customRuleForBlanks'] ?? false; if (!$customRuleForBlanks) { // Blank cells are always ignored, so return a FALSE if (($cellValue === '') || ($cellValue === null)) { return false; } } $returnVal = ($join == AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_AND); foreach ($dataSet as $rule) { /** @var string $ruleValue */ $ruleValue = $rule['value']; /** @var string $ruleOperator */ $ruleOperator = $rule['operator']; /** @var string $cellValueString */ $cellValueString = $cellValue ?? ''; $retVal = false; if (is_numeric($ruleValue)) { // Numeric values are tested using the appropriate operator $numericTest = is_numeric($cellValue); switch ($ruleOperator) { case Rule::AUTOFILTER_COLUMN_RULE_EQUAL: $retVal = $numericTest && ($cellValue == $ruleValue); break; case Rule::AUTOFILTER_COLUMN_RULE_NOTEQUAL: $retVal = !$numericTest || ($cellValue != $ruleValue); break; case Rule::AUTOFILTER_COLUMN_RULE_GREATERTHAN: $retVal = $numericTest && ($cellValue > $ruleValue); break; case Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL: $retVal = $numericTest && ($cellValue >= $ruleValue); break; case Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN: $retVal = $numericTest && ($cellValue < $ruleValue); break; case Rule::AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL: $retVal = $numericTest && ($cellValue <= $ruleValue); break; } } elseif ($ruleValue == '') { switch ($ruleOperator) { case Rule::AUTOFILTER_COLUMN_RULE_EQUAL: $retVal = ($cellValue === '') || ($cellValue === null); break; case Rule::AUTOFILTER_COLUMN_RULE_NOTEQUAL: $retVal = $cellValue != ''; break; default: $retVal = true; break; } } else { // String values are always tested for equality, factoring in for wildcards (hence a regexp test) switch ($ruleOperator) { case Rule::AUTOFILTER_COLUMN_RULE_EQUAL: $retVal = (bool) preg_match('/^' . $ruleValue . '$/i', $cellValueString); break; case Rule::AUTOFILTER_COLUMN_RULE_NOTEQUAL: $retVal = !((bool) preg_match('/^' . $ruleValue . '$/i', $cellValueString)); break; case Rule::AUTOFILTER_COLUMN_RULE_GREATERTHAN: $retVal = strcasecmp($cellValueString, $ruleValue) > 0; break; case Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL: $retVal = strcasecmp($cellValueString, $ruleValue) >= 0; break; case Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN: $retVal = strcasecmp($cellValueString, $ruleValue) < 0; break; case Rule::AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL: $retVal = strcasecmp($cellValueString, $ruleValue) <= 0; break; } } // If there are multiple conditions, then we need to test both using the appropriate join operator switch ($join) { case AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_OR: $returnVal = $returnVal || $retVal; // Break as soon as we have a TRUE match for OR joins, // to avoid unnecessary additional code execution if ($returnVal) { return $returnVal; } break; case AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_AND: $returnVal = $returnVal && $retVal; break; } } return $returnVal; } /** * Test if cell date value is matches a set of values defined by a set of months. * * @param mixed[] $monthSet * @param mixed $cellValue */ protected static function filterTestInPeriodDateSet($cellValue, array $monthSet): bool { // Blank cells are always ignored, so return a FALSE if (($cellValue === '') || ($cellValue === null)) { return false; } if (is_numeric($cellValue)) { $dateObject = Date::excelToDateTimeObject((float) $cellValue, new DateTimeZone('UTC')); $dateValue = (int) $dateObject->format('m'); if (in_array($dateValue, $monthSet)) { return true; } } return false; } private static function makeDateObject(int $year, int $month, int $day, int $hour = 0, int $minute = 0, int $second = 0): DateTime { $baseDate = new DateTime(); $baseDate->setDate($year, $month, $day); $baseDate->setTime($hour, $minute, $second); return $baseDate; } private const DATE_FUNCTIONS = [ Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTMONTH => 'dynamicLastMonth', Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTQUARTER => 'dynamicLastQuarter', Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTWEEK => 'dynamicLastWeek', Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTYEAR => 'dynamicLastYear', Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTMONTH => 'dynamicNextMonth', Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTQUARTER => 'dynamicNextQuarter', Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTWEEK => 'dynamicNextWeek', Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTYEAR => 'dynamicNextYear', Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISMONTH => 'dynamicThisMonth', Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISQUARTER => 'dynamicThisQuarter', Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISWEEK => 'dynamicThisWeek', Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISYEAR => 'dynamicThisYear', Rule::AUTOFILTER_RULETYPE_DYNAMIC_TODAY => 'dynamicToday', Rule::AUTOFILTER_RULETYPE_DYNAMIC_TOMORROW => 'dynamicTomorrow', Rule::AUTOFILTER_RULETYPE_DYNAMIC_YEARTODATE => 'dynamicYearToDate', Rule::AUTOFILTER_RULETYPE_DYNAMIC_YESTERDAY => 'dynamicYesterday', ]; private static function dynamicLastMonth(): array { $maxval = new DateTime(); $year = (int) $maxval->format('Y'); $month = (int) $maxval->format('m'); $maxval->setDate($year, $month, 1); $maxval->setTime(0, 0, 0); $val = clone $maxval; $val->modify('-1 month'); return [$val, $maxval]; } private static function firstDayOfQuarter(): DateTime { $val = new DateTime(); $year = (int) $val->format('Y'); $month = (int) $val->format('m'); $month = 3 * intdiv($month - 1, 3) + 1; $val->setDate($year, $month, 1); $val->setTime(0, 0, 0); return $val; } private static function dynamicLastQuarter(): array { $maxval = self::firstDayOfQuarter(); $val = clone $maxval; $val->modify('-3 months'); return [$val, $maxval]; } private static function dynamicLastWeek(): array { $val = new DateTime(); $val->setTime(0, 0, 0); $dayOfWeek = (int) $val->format('w'); // Sunday is 0 $subtract = $dayOfWeek + 7; // revert to prior Sunday $val->modify("-$subtract days"); $maxval = clone $val; $maxval->modify('+7 days'); return [$val, $maxval]; } private static function dynamicLastYear(): array { $val = new DateTime(); $year = (int) $val->format('Y'); $val = self::makeDateObject($year - 1, 1, 1); $maxval = self::makeDateObject($year, 1, 1); return [$val, $maxval]; } private static function dynamicNextMonth(): array { $val = new DateTime(); $year = (int) $val->format('Y'); $month = (int) $val->format('m'); $val->setDate($year, $month, 1); $val->setTime(0, 0, 0); $val->modify('+1 month'); $maxval = clone $val; $maxval->modify('+1 month'); return [$val, $maxval]; } private static function dynamicNextQuarter(): array { $val = self::firstDayOfQuarter(); $val->modify('+3 months'); $maxval = clone $val; $maxval->modify('+3 months'); return [$val, $maxval]; } private static function dynamicNextWeek(): array { $val = new DateTime(); $val->setTime(0, 0, 0); $dayOfWeek = (int) $val->format('w'); // Sunday is 0 $add = 7 - $dayOfWeek; // move to next Sunday $val->modify("+$add days"); $maxval = clone $val; $maxval->modify('+7 days'); return [$val, $maxval]; } private static function dynamicNextYear(): array { $val = new DateTime(); $year = (int) $val->format('Y'); $val = self::makeDateObject($year + 1, 1, 1); $maxval = self::makeDateObject($year + 2, 1, 1); return [$val, $maxval]; } private static function dynamicThisMonth(): array { $baseDate = new DateTime(); $baseDate->setTime(0, 0, 0); $year = (int) $baseDate->format('Y'); $month = (int) $baseDate->format('m'); $val = self::makeDateObject($year, $month, 1); $maxval = clone $val; $maxval->modify('+1 month'); return [$val, $maxval]; } private static function dynamicThisQuarter(): array { $val = self::firstDayOfQuarter(); $maxval = clone $val; $maxval->modify('+3 months'); return [$val, $maxval]; } private static function dynamicThisWeek(): array { $val = new DateTime(); $val->setTime(0, 0, 0); $dayOfWeek = (int) $val->format('w'); // Sunday is 0 $subtract = $dayOfWeek; // revert to Sunday $val->modify("-$subtract days"); $maxval = clone $val; $maxval->modify('+7 days'); return [$val, $maxval]; } private static function dynamicThisYear(): array { $val = new DateTime(); $year = (int) $val->format('Y'); $val = self::makeDateObject($year, 1, 1); $maxval = self::makeDateObject($year + 1, 1, 1); return [$val, $maxval]; } private static function dynamicToday(): array { $val = new DateTime(); $val->setTime(0, 0, 0); $maxval = clone $val; $maxval->modify('+1 day'); return [$val, $maxval]; } private static function dynamicTomorrow(): array { $val = new DateTime(); $val->setTime(0, 0, 0); $val->modify('+1 day'); $maxval = clone $val; $maxval->modify('+1 day'); return [$val, $maxval]; } private static function dynamicYearToDate(): array { $maxval = new DateTime(); $maxval->setTime(0, 0, 0); $val = self::makeDateObject((int) $maxval->format('Y'), 1, 1); $maxval->modify('+1 day'); return [$val, $maxval]; } private static function dynamicYesterday(): array { $maxval = new DateTime(); $maxval->setTime(0, 0, 0); $val = clone $maxval; $val->modify('-1 day'); return [$val, $maxval]; } /** * Convert a dynamic rule daterange to a custom filter range expression for ease of calculation. * * @return mixed[] */ private function dynamicFilterDateRange(string $dynamicRuleType, AutoFilter\Column &$filterColumn): array { $ruleValues = []; $callBack = [__CLASS__, self::DATE_FUNCTIONS[$dynamicRuleType]]; // What if not found? // Calculate start/end dates for the required date range based on current date // Val is lowest permitted value. // Maxval is greater than highest permitted value $val = $maxval = 0; if (is_callable($callBack)) { //* @phpstan-ignore-line [$val, $maxval] = $callBack(); } $val = Date::dateTimeToExcel($val); $maxval = Date::dateTimeToExcel($maxval); // Set the filter column rule attributes ready for writing $filterColumn->setAttributes(['val' => $val, 'maxVal' => $maxval]); // Set the rules for identifying rows for hide/show $ruleValues[] = ['operator' => Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL, 'value' => $val]; $ruleValues[] = ['operator' => Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN, 'value' => $maxval]; return ['method' => 'filterTestInCustomDataSet', 'arguments' => ['filterRules' => $ruleValues, 'join' => AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_AND]]; } /** * Apply the AutoFilter rules to the AutoFilter Range. * @param mixed $ruleValue * @return mixed */ private function calculateTopTenValue(string $columnID, int $startRow, int $endRow, ?string $ruleType, $ruleValue) { $range = $columnID . $startRow . ':' . $columnID . $endRow; $retVal = null; if ($this->workSheet !== null) { $dataValues = Functions::flattenArray($this->workSheet->rangeToArray($range, null, true, false)); $dataValues = array_filter($dataValues); if ($ruleType == Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP) { rsort($dataValues); } else { sort($dataValues); } if (is_numeric($ruleValue)) { $ruleValue = (int) $ruleValue; } if ($ruleValue === null || is_int($ruleValue)) { $slice = array_slice($dataValues, 0, $ruleValue); $retVal = array_pop($slice); } } return $retVal; } /** * Apply the AutoFilter rules to the AutoFilter Range. * * @return $this */ public function showHideRows() { if ($this->workSheet === null) { return $this; } [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range); // The heading row should always be visible $this->workSheet->getRowDimension($rangeStart[1])->setVisible(true); $columnFilterTests = []; foreach ($this->columns as $columnID => $filterColumn) { $rules = $filterColumn->getRules(); switch ($filterColumn->getFilterType()) { case AutoFilter\Column::AUTOFILTER_FILTERTYPE_FILTER: $ruleType = null; $ruleValues = []; // Build a list of the filter value selections foreach ($rules as $rule) { $ruleType = $rule->getRuleType(); $ruleValues[] = $rule->getValue(); } // Test if we want to include blanks in our filter criteria $blanks = false; $ruleDataSet = array_filter($ruleValues); if (count($ruleValues) != count($ruleDataSet)) { $blanks = true; } if ($ruleType == Rule::AUTOFILTER_RULETYPE_FILTER) { // Filter on absolute values $columnFilterTests[$columnID] = [ 'method' => 'filterTestInSimpleDataSet', 'arguments' => ['filterValues' => $ruleDataSet, 'blanks' => $blanks], ]; } elseif ($ruleType !== null) { // Filter on date group values $arguments = [ 'date' => [], 'time' => [], 'dateTime' => [], ]; foreach ($ruleDataSet as $ruleValue) { if (!is_array($ruleValue)) { continue; } $date = $time = ''; if ( (isset($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR])) && ($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR] !== '') ) { $date .= sprintf('%04d', $ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR]); } if ( (isset($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH])) && ($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH] != '') ) { $date .= sprintf('%02d', $ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH]); } if ( (isset($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY])) && ($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY] !== '') ) { $date .= sprintf('%02d', $ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY]); } if ( (isset($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR])) && ($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR] !== '') ) { $time .= sprintf('%02d', $ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR]); } if ( (isset($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE])) && ($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE] !== '') ) { $time .= sprintf('%02d', $ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE]); } if ( (isset($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND])) && ($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND] !== '') ) { $time .= sprintf('%02d', $ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND]); } $dateTime = $date . $time; $arguments['date'][] = $date; $arguments['time'][] = $time; $arguments['dateTime'][] = $dateTime; } // Remove empty elements $arguments['date'] = array_filter($arguments['date']); $arguments['time'] = array_filter($arguments['time']); $arguments['dateTime'] = array_filter($arguments['dateTime']); $columnFilterTests[$columnID] = [ 'method' => 'filterTestInDateGroupSet', 'arguments' => ['filterValues' => $arguments, 'blanks' => $blanks], ]; } break; case AutoFilter\Column::AUTOFILTER_FILTERTYPE_CUSTOMFILTER: $customRuleForBlanks = true; $ruleValues = []; // Build a list of the filter value selections foreach ($rules as $rule) { $ruleValue = $rule->getValue(); if (!is_array($ruleValue) && !is_numeric($ruleValue)) { // Convert to a regexp allowing for regexp reserved characters, wildcards and escaped wildcards $ruleValue = WildcardMatch::wildcard($ruleValue); if (trim($ruleValue) == '') { $customRuleForBlanks = true; $ruleValue = trim($ruleValue); } } $ruleValues[] = ['operator' => $rule->getOperator(), 'value' => $ruleValue]; } $join = $filterColumn->getJoin(); $columnFilterTests[$columnID] = [ 'method' => 'filterTestInCustomDataSet', 'arguments' => ['filterRules' => $ruleValues, 'join' => $join, 'customRuleForBlanks' => $customRuleForBlanks], ]; break; case AutoFilter\Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER: $ruleValues = []; foreach ($rules as $rule) { // We should only ever have one Dynamic Filter Rule anyway $dynamicRuleType = $rule->getGrouping(); if ( ($dynamicRuleType == Rule::AUTOFILTER_RULETYPE_DYNAMIC_ABOVEAVERAGE) || ($dynamicRuleType == Rule::AUTOFILTER_RULETYPE_DYNAMIC_BELOWAVERAGE) ) { // Number (Average) based // Calculate the average $averageFormula = '=AVERAGE(' . $columnID . ($rangeStart[1] + 1) . ':' . $columnID . $rangeEnd[1] . ')'; $average = Calculation::getInstance($this->workSheet->getParent())->calculateFormula($averageFormula, null, $this->workSheet->getCell('A1')); while (is_array($average)) { $average = array_pop($average); } // Set above/below rule based on greaterThan or LessTan $operator = ($dynamicRuleType === Rule::AUTOFILTER_RULETYPE_DYNAMIC_ABOVEAVERAGE) ? Rule::AUTOFILTER_COLUMN_RULE_GREATERTHAN : Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN; $ruleValues[] = [ 'operator' => $operator, 'value' => $average, ]; $columnFilterTests[$columnID] = [ 'method' => 'filterTestInCustomDataSet', 'arguments' => ['filterRules' => $ruleValues, 'join' => AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_OR], ]; } else { // Date based if ($dynamicRuleType[0] == 'M' || $dynamicRuleType[0] == 'Q') { $periodType = ''; $period = 0; // Month or Quarter sscanf($dynamicRuleType, '%[A-Z]%d', $periodType, $period); if ($periodType == 'M') { $ruleValues = [$period]; } else { --$period; $periodEnd = (1 + $period) * 3; $periodStart = 1 + $period * 3; $ruleValues = range($periodStart, $periodEnd); } $columnFilterTests[$columnID] = [ 'method' => 'filterTestInPeriodDateSet', 'arguments' => $ruleValues, ]; $filterColumn->setAttributes([]); } else { // Date Range $columnFilterTests[$columnID] = $this->dynamicFilterDateRange($dynamicRuleType, $filterColumn); break; } } } break; case AutoFilter\Column::AUTOFILTER_FILTERTYPE_TOPTENFILTER: $ruleValues = []; $dataRowCount = $rangeEnd[1] - $rangeStart[1]; $toptenRuleType = null; $ruleValue = 0; $ruleOperator = null; foreach ($rules as $rule) { // We should only ever have one Dynamic Filter Rule anyway $toptenRuleType = $rule->getGrouping(); $ruleValue = $rule->getValue(); $ruleOperator = $rule->getOperator(); } if (is_numeric($ruleValue) && $ruleOperator === Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT) { $ruleValue = (int) floor((float) $ruleValue * ($dataRowCount / 100)); } if (!is_array($ruleValue) && $ruleValue < 1) { $ruleValue = 1; } if (!is_array($ruleValue) && $ruleValue > 500) { $ruleValue = 500; } /** @var float|int|string */ $maxVal = $this->calculateTopTenValue($columnID, $rangeStart[1] + 1, (int) $rangeEnd[1], $toptenRuleType, $ruleValue); $operator = ($toptenRuleType == Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP) ? Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL : Rule::AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL; $ruleValues[] = ['operator' => $operator, 'value' => $maxVal]; $columnFilterTests[$columnID] = [ 'method' => 'filterTestInCustomDataSet', 'arguments' => ['filterRules' => $ruleValues, 'join' => AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_OR], ]; $filterColumn->setAttributes(['maxVal' => $maxVal]); break; } } $rangeEnd[1] = $this->autoExtendRange($rangeStart[1], $rangeEnd[1]); // Execute the column tests for each row in the autoFilter range to determine show/hide, for ($row = $rangeStart[1] + 1; $row <= $rangeEnd[1]; ++$row) { $result = true; foreach ($columnFilterTests as $columnID => $columnFilterTest) { $cellValue = $this->workSheet->getCell($columnID . $row)->getCalculatedValue(); // Execute the filter test /** @var callable */ $temp = [self::class, $columnFilterTest['method']]; /** @var bool */ $result // $result && // phpstan says $result is always true here = call_user_func_array($temp, [$cellValue, $columnFilterTest['arguments']]); // If filter test has resulted in FALSE, exit the loop straightaway rather than running any more tests if (!$result) { break; } } // Set show/hide for the row based on the result of the autoFilter result // If the RowDimension object has not been allocated yet and the row should be visible, // then we can avoid any operation since the rows are visible by default (saves a lot of memory) if ($result === false || $this->workSheet->rowDimensionExists((int) $row)) { $this->workSheet->getRowDimension((int) $row)->setVisible($result); } } $this->evaluated = true; return $this; } /** * Magic Range Auto-sizing. * For a single row rangeSet, we follow MS Excel rules, and search for the first empty row to determine our range. */ public function autoExtendRange(int $startRow, int $endRow): int { if ($startRow === $endRow && $this->workSheet !== null) { try { $rowIterator = $this->workSheet->getRowIterator($startRow + 1); } catch (Exception $exception) { // If there are no rows below $startRow return $startRow; } foreach ($rowIterator as $row) { if ($row->isEmpty(CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL | CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL) === true) { return $row->getRowIndex() - 1; } } } return $endRow; } /** * Implement PHP __clone to create a deep clone, not just a shallow copy. */ public function __clone() { $vars = get_object_vars($this); foreach ($vars as $key => $value) { if (is_object($value)) { if ($key === 'workSheet') { // Detach from worksheet $this->{$key} = null; } else { $this->{$key} = clone $value; } } elseif ((is_array($value)) && ($key == 'columns')) { // The columns array of \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\AutoFilter objects $this->{$key} = []; foreach ($value as $k => $v) { $this->{$key}[$k] = clone $v; //* @phpstan-ignore-line // attach the new cloned Column to this new cloned Autofilter object $this->{$key}[$k]->setParent($this); } } else { $this->{$key} = $value; } } } /** * toString method replicates previous behavior by returning the range if object is * referenced as a property of its parent. */ public function __toString(): string { return (string) $this->range; } } PK!iwj8 8 9libraries/vendor/PhpSpreadsheet/Worksheet/PageMargins.phpnu[left; } /** * Set Left. * * @return $this */ public function setLeft(float $left) { $this->left = $left; return $this; } /** * Get Right. */ public function getRight(): float { return $this->right; } /** * Set Right. * * @return $this */ public function setRight(float $right) { $this->right = $right; return $this; } /** * Get Top. */ public function getTop(): float { return $this->top; } /** * Set Top. * * @return $this */ public function setTop(float $top) { $this->top = $top; return $this; } /** * Get Bottom. */ public function getBottom(): float { return $this->bottom; } /** * Set Bottom. * * @return $this */ public function setBottom(float $bottom) { $this->bottom = $bottom; return $this; } /** * Get Header. */ public function getHeader(): float { return $this->header; } /** * Set Header. * * @return $this */ public function setHeader(float $header) { $this->header = $header; return $this; } /** * Get Footer. */ public function getFooter(): float { return $this->footer; } /** * Set Footer. * * @return $this */ public function setFooter(float $footer) { $this->footer = $footer; return $this; } public static function fromCentimeters(float $value): float { return $value / 2.54; } public static function toCentimeters(float $value): float { return $value * 2.54; } public static function fromMillimeters(float $value): float { return $value / 25.4; } public static function toMillimeters(float $value): float { return $value * 25.4; } public static function fromPoints(float $value): float { return $value / 72; } public static function toPoints(float $value): float { return $value * 72; } } PK!6;11;libraries/vendor/PhpSpreadsheet/Worksheet/MemoryDrawing.phpnu[renderingFunction = self::RENDERING_DEFAULT; $this->mimeType = self::MIMETYPE_DEFAULT; $this->uniqueName = md5(mt_rand(0, 9999) . time() . mt_rand(0, 9999)); // Initialize parent parent::__construct(); } public function __destruct() { if ($this->imageResource) { @imagedestroy($this->imageResource); $this->imageResource = null; } $this->worksheet = null; } public function __clone() { parent::__clone(); $this->cloneResource(); } private function cloneResource(): void { if (!$this->imageResource) { return; } $width = (int) imagesx($this->imageResource); $height = (int) imagesy($this->imageResource); if (imageistruecolor($this->imageResource)) { $clone = imagecreatetruecolor($width, $height); if (!$clone) { throw new Exception('Could not clone image resource'); } imagealphablending($clone, false); imagesavealpha($clone, true); } else { $clone = imagecreate($width, $height); if (!$clone) { throw new Exception('Could not clone image resource'); } // If the image has transparency... $transparent = imagecolortransparent($this->imageResource); if ($transparent >= 0) { // Starting with Php8.0, next function throws rather than return false $rgb = imagecolorsforindex($this->imageResource, $transparent); imagesavealpha($clone, true); $color = imagecolorallocatealpha($clone, $rgb['red'], $rgb['green'], $rgb['blue'], $rgb['alpha']); if ($color === false) { throw new Exception('Could not get image alpha color'); } imagefill($clone, 0, 0, $color); } } //Create the Clone!! imagecopy($clone, $this->imageResource, 0, 0, 0, 0, $width, $height); $this->imageResource = $clone; } /** * @param resource $imageStream Stream data to be converted to a Memory Drawing * * @throws Exception */ public static function fromStream($imageStream): self { $streamValue = stream_get_contents($imageStream); if ($streamValue === false) { throw new Exception('Unable to read data from stream'); } return self::fromString($streamValue); } /** * @param string $imageString String data to be converted to a Memory Drawing * * @throws Exception */ public static function fromString(string $imageString): self { $gdImage = @imagecreatefromstring($imageString); if ($gdImage === false) { throw new Exception('Value cannot be converted to an image'); } $mimeType = self::identifyMimeType($imageString); if (imageistruecolor($gdImage) || imagecolortransparent($gdImage) >= 0) { imagesavealpha($gdImage, true); } $renderingFunction = self::identifyRenderingFunction($mimeType); $drawing = new self(); $drawing->setImageResource($gdImage); $drawing->setRenderingFunction($renderingFunction); $drawing->setMimeType($mimeType); return $drawing; } /** @return callable-string */ private static function identifyRenderingFunction(string $mimeType): string { switch ($mimeType) { case self::MIMETYPE_PNG: return self::RENDERING_PNG; case self::MIMETYPE_JPEG: return self::RENDERING_JPEG; case self::MIMETYPE_GIF: return self::RENDERING_GIF; default: return self::RENDERING_DEFAULT; } } /** * @throws Exception */ private static function identifyMimeType(string $imageString): string { $temporaryFileName = File::temporaryFilename(); file_put_contents($temporaryFileName, $imageString); $mimeType = self::identifyMimeTypeUsingExif($temporaryFileName); if ($mimeType !== null) { unlink($temporaryFileName); return $mimeType; } $mimeType = self::identifyMimeTypeUsingGd($temporaryFileName); if ($mimeType !== null) { unlink($temporaryFileName); return $mimeType; } unlink($temporaryFileName); return self::MIMETYPE_DEFAULT; } private static function identifyMimeTypeUsingExif(string $temporaryFileName): ?string { if (function_exists('exif_imagetype')) { $imageType = @exif_imagetype($temporaryFileName); $mimeType = ($imageType) ? image_type_to_mime_type($imageType) : null; return self::supportedMimeTypes($mimeType); } return null; } private static function identifyMimeTypeUsingGd(string $temporaryFileName): ?string { if (function_exists('getimagesize')) { $imageSize = @getimagesize($temporaryFileName); if (is_array($imageSize)) { $mimeType = $imageSize['mime']; return self::supportedMimeTypes($mimeType); } } return null; } private static function supportedMimeTypes(?string $mimeType = null): ?string { if (in_array($mimeType, self::SUPPORTED_MIME_TYPES, true)) { return $mimeType; } return null; } /** * Get image resource. */ public function getImageResource(): ?GdImage { return $this->imageResource; } /** * Set image resource. * * @return $this */ public function setImageResource(?GdImage $value) { $this->imageResource = $value; if ($this->imageResource !== null) { // Get width/height $this->width = (int) imagesx($this->imageResource); $this->height = (int) imagesy($this->imageResource); } return $this; } /** * Get rendering function. * * @return callable-string */ public function getRenderingFunction(): string { return $this->renderingFunction; } /** * Set rendering function. * * @param callable-string $value see self::RENDERING_* * * @return $this */ public function setRenderingFunction(string $value) { $this->renderingFunction = $value; return $this; } /** * Get mime type. */ public function getMimeType(): string { return $this->mimeType; } /** * Set mime type. * * @param string $value see self::MIMETYPE_* * * @return $this */ public function setMimeType(string $value) { $this->mimeType = $value; return $this; } /** * Get indexed filename (using image index). */ public function getIndexedFilename(): string { $extension = strtolower($this->getMimeType()); $extension = explode('/', $extension); $extension = $extension[1]; return $this->uniqueName . $this->getImageIndex() . '.' . $extension; } /** * Get hash code. * * @return string Hash code */ public function getHashCode(): string { return md5( $this->renderingFunction . $this->mimeType . $this->uniqueName . parent::getHashCode() . __CLASS__ ); } } PK!zp -libraries/vendor/PhpSpreadsheet/HashTable.phpnu[ */ protected array $items = []; /** * HashTable key map. * * @var array */ protected array $keyMap = []; /** * Create a new HashTable. * * @param T[] $source Optional source array to create HashTable from */ public function __construct(?array $source = []) { if ($source !== null) { // Create HashTable $this->addFromSource($source); } } /** * Add HashTable items from source. * * @param T[] $source Source array to create HashTable from */ public function addFromSource(?array $source = null): void { // Check if an array was passed if ($source === null) { return; } foreach ($source as $item) { $this->add($item); } } /** * Add HashTable item. * * @param T $source Item to add */ public function add(IComparable $source): void { $hash = $source->getHashCode(); if (!isset($this->items[$hash])) { $this->items[$hash] = $source; $this->keyMap[count($this->items) - 1] = $hash; } } /** * Remove HashTable item. * * @param T $source Item to remove */ public function remove(IComparable $source): void { $hash = $source->getHashCode(); if (isset($this->items[$hash])) { unset($this->items[$hash]); $deleteKey = -1; foreach ($this->keyMap as $key => $value) { if ($deleteKey >= 0) { $this->keyMap[$key - 1] = $value; } if ($value == $hash) { $deleteKey = $key; } } unset($this->keyMap[count($this->keyMap) - 1]); } } /** * Clear HashTable. */ public function clear(): void { $this->items = []; $this->keyMap = []; } /** * Count. */ public function count(): int { return count($this->items); } /** * Get index for hash code. * @return int|false */ public function getIndexForHashCode(string $hashCode) { return array_search($hashCode, $this->keyMap, true); } /** * Get by index. * * @return null|T */ public function getByIndex(int $index): ?IComparable { if (isset($this->keyMap[$index])) { return $this->getByHashCode($this->keyMap[$index]); } return null; } /** * Get by hashcode. * * @return null|T */ public function getByHashCode(string $hashCode): ?IComparable { if (isset($this->items[$hashCode])) { return $this->items[$hashCode]; } return null; } /** * HashTable to array. * * @return T[] */ public function toArray(): array { return $this->items; } /** * Implement PHP __clone to create a deep clone, not just a shallow copy. */ public function __clone() { $vars = get_object_vars($this); foreach ($vars as $key => $value) { // each member of this class is an array if (is_array($value)) { $array1 = $value; foreach ($array1 as $key1 => $value1) { if (is_object($value1)) { $array1[$key1] = clone $value1; } } $this->$key = $array1; } } } } PK!2U ff7libraries/vendor/PhpSpreadsheet/CellReferenceHelper.phpnu[beforeColumnAbsolute = $beforeCellAddress[0] === '$'; $this->beforeRowAbsolute = strpos($beforeCellAddress, '$', 1) !== false; $this->beforeCellAddress = str_replace('$', '', $beforeCellAddress); $this->numberOfColumns = $numberOfColumns; $this->numberOfRows = $numberOfRows; // Get coordinate of $beforeCellAddress [$beforeColumn, $beforeRow] = Coordinate::coordinateFromString($beforeCellAddress); $this->beforeColumnString = $beforeColumn; $this->beforeColumn = (int) Coordinate::columnIndexFromString($beforeColumn); $this->beforeRow = (int) $beforeRow; } public function beforeCellAddress(): string { return $this->beforeCellAddress; } public function refreshRequired(string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): bool { return $this->beforeCellAddress !== $beforeCellAddress || $this->numberOfColumns !== $numberOfColumns || $this->numberOfRows !== $numberOfRows; } public function updateCellReference(string $cellReference = 'A1', bool $includeAbsoluteReferences = false, bool $onlyAbsoluteReferences = false, ?bool $topLeft = null): string { if (Coordinate::coordinateIsRange($cellReference)) { throw new Exception('Only single cell references may be passed to this method.'); } // Get coordinate of $cellReference [$newColumn, $newRow] = Coordinate::coordinateFromString($cellReference); $newColumnIndex = Coordinate::columnIndexFromString(str_replace('$', '', $newColumn)); $newRowIndex = (int) str_replace('$', '', $newRow); $absoluteColumn = $newColumn[0] === '$' ? '$' : ''; $absoluteRow = $newRow[0] === '$' ? '$' : ''; // Verify which parts should be updated if ($onlyAbsoluteReferences === true) { $updateColumn = (($absoluteColumn === '$') && $newColumnIndex >= $this->beforeColumn); $updateRow = (($absoluteRow === '$') && $newRowIndex >= $this->beforeRow); } elseif ($includeAbsoluteReferences === false) { $updateColumn = (($absoluteColumn !== '$') && $newColumnIndex >= $this->beforeColumn); $updateRow = (($absoluteRow !== '$') && $newRowIndex >= $this->beforeRow); } else { $newColumnIndex = $this->computeNewColumnIndex($newColumnIndex, $topLeft); $newColumn = $absoluteColumn . Coordinate::stringFromColumnIndex($newColumnIndex); $updateColumn = false; $newRowIndex = $this->computeNewRowIndex($newRowIndex, $topLeft); $newRow = $absoluteRow . $newRowIndex; $updateRow = false; } // Create new column reference if ($updateColumn) { $newColumn = $this->updateColumnReference($newColumnIndex, $absoluteColumn); } // Create new row reference if ($updateRow) { $newRow = $this->updateRowReference($newRowIndex, $absoluteRow); } // Return new reference return "{$newColumn}{$newRow}"; } public function computeNewColumnIndex(int $newColumnIndex, ?bool $topLeft): int { // A special case is removing the left/top or bottom/right edge of a range // $topLeft is null if we aren't adjusting a range at all. if ( $topLeft !== null && $this->numberOfColumns < 0 && $newColumnIndex >= $this->beforeColumn + $this->numberOfColumns && $newColumnIndex <= $this->beforeColumn - 1 ) { if ($topLeft) { $newColumnIndex = $this->beforeColumn + $this->numberOfColumns; } else { $newColumnIndex = $this->beforeColumn + $this->numberOfColumns - 1; } } elseif ($newColumnIndex >= $this->beforeColumn) { // Create new column reference $newColumnIndex += $this->numberOfColumns; } return $newColumnIndex; } public function computeNewRowIndex(int $newRowIndex, ?bool $topLeft): int { // A special case is removing the left/top or bottom/right edge of a range // $topLeft is null if we aren't adjusting a range at all. if ( $topLeft !== null && $this->numberOfRows < 0 && $newRowIndex >= $this->beforeRow + $this->numberOfRows && $newRowIndex <= $this->beforeRow - 1 ) { if ($topLeft) { $newRowIndex = $this->beforeRow + $this->numberOfRows; } else { $newRowIndex = $this->beforeRow + $this->numberOfRows - 1; } } elseif ($newRowIndex >= $this->beforeRow) { $newRowIndex = $newRowIndex + $this->numberOfRows; } return $newRowIndex; } public function cellAddressInDeleteRange(string $cellAddress): bool { [$cellColumn, $cellRow] = Coordinate::coordinateFromString($cellAddress); $cellColumnIndex = Coordinate::columnIndexFromString($cellColumn); // Is cell within the range of rows/columns if we're deleting if ( $this->numberOfRows < 0 && ($cellRow >= ($this->beforeRow + $this->numberOfRows)) && ($cellRow < $this->beforeRow) ) { return true; } elseif ( $this->numberOfColumns < 0 && ($cellColumnIndex >= ($this->beforeColumn + $this->numberOfColumns)) && ($cellColumnIndex < $this->beforeColumn) ) { return true; } return false; } protected function updateColumnReference(int $newColumnIndex, string $absoluteColumn): string { $newColumn = Coordinate::stringFromColumnIndex(min($newColumnIndex + $this->numberOfColumns, AddressRange::MAX_COLUMN_INT)); return "{$absoluteColumn}{$newColumn}"; } protected function updateRowReference(int $newRowIndex, string $absoluteRow): string { $newRow = $newRowIndex + $this->numberOfRows; $newRow = ($newRow > AddressRange::MAX_ROW) ? AddressRange::MAX_ROW : $newRow; return "{$absoluteRow}{$newRow}"; } } PK!&5nn3libraries/vendor/PhpSpreadsheet/ReferenceHelper.phpnu[getBreaks(); ($numberOfColumns > 0 || $numberOfRows > 0) ? uksort($aBreaks, [self::class, 'cellReverseSort']) : uksort($aBreaks, [self::class, 'cellSort']); foreach ($aBreaks as $cellAddress => $value) { /** @var CellReferenceHelper */ $cellReferenceHelper = $this->cellReferenceHelper; if ($cellReferenceHelper->cellAddressInDeleteRange($cellAddress) === true) { // If we're deleting, then clear any defined breaks that are within the range // of rows/columns that we're deleting $worksheet->setBreak($cellAddress, Worksheet::BREAK_NONE); } else { // Otherwise update any affected breaks by inserting a new break at the appropriate point // and removing the old affected break $newReference = $this->updateCellReference($cellAddress); if ($cellAddress !== $newReference) { $worksheet->setBreak($newReference, $value) ->setBreak($cellAddress, Worksheet::BREAK_NONE); } } } } /** * Update cell comments when inserting/deleting rows/columns. * * @param Worksheet $worksheet The worksheet that we're editing */ protected function adjustComments(Worksheet $worksheet): void { $aComments = $worksheet->getComments(); $aNewComments = []; // the new array of all comments foreach ($aComments as $cellAddress => &$value) { // Any comments inside a deleted range will be ignored /** @var CellReferenceHelper */ $cellReferenceHelper = $this->cellReferenceHelper; if ($cellReferenceHelper->cellAddressInDeleteRange($cellAddress) === false) { // Otherwise build a new array of comments indexed by the adjusted cell reference $newReference = $this->updateCellReference($cellAddress); $aNewComments[$newReference] = $value; } } // Replace the comments array with the new set of comments $worksheet->setComments($aNewComments); } /** * Update hyperlinks when inserting/deleting rows/columns. * * @param Worksheet $worksheet The worksheet that we're editing * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion) * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) */ protected function adjustHyperlinks(Worksheet $worksheet, int $numberOfColumns, int $numberOfRows): void { $aHyperlinkCollection = $worksheet->getHyperlinkCollection(); ($numberOfColumns > 0 || $numberOfRows > 0) ? uksort($aHyperlinkCollection, [self::class, 'cellReverseSort']) : uksort($aHyperlinkCollection, [self::class, 'cellSort']); foreach ($aHyperlinkCollection as $cellAddress => $value) { $newReference = $this->updateCellReference($cellAddress); /** @var CellReferenceHelper */ $cellReferenceHelper = $this->cellReferenceHelper; if ($cellReferenceHelper->cellAddressInDeleteRange($cellAddress) === true) { $worksheet->setHyperlink($cellAddress, null); } elseif ($cellAddress !== $newReference) { $worksheet->setHyperlink($cellAddress, null); if ($newReference) { $worksheet->setHyperlink($newReference, $value); } } } } /** * Update conditional formatting styles when inserting/deleting rows/columns. * * @param Worksheet $worksheet The worksheet that we're editing * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion) * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) */ protected function adjustConditionalFormatting(Worksheet $worksheet, int $numberOfColumns, int $numberOfRows): void { $aStyles = $worksheet->getConditionalStylesCollection(); ($numberOfColumns > 0 || $numberOfRows > 0) ? uksort($aStyles, [self::class, 'cellReverseSort']) : uksort($aStyles, [self::class, 'cellSort']); foreach ($aStyles as $cellAddress => $cfRules) { $worksheet->removeConditionalStyles($cellAddress); $newReference = $this->updateCellReference($cellAddress); foreach ($cfRules as &$cfRule) { /** @var Conditional $cfRule */ $conditions = $cfRule->getConditions(); foreach ($conditions as &$condition) { if (is_string($condition)) { /** @var CellReferenceHelper */ $cellReferenceHelper = $this->cellReferenceHelper; $condition = $this->updateFormulaReferences( $condition, $cellReferenceHelper->beforeCellAddress(), $numberOfColumns, $numberOfRows, $worksheet->getTitle(), true ); } } $cfRule->setConditions($conditions); } $worksheet->setConditionalStyles($newReference, $cfRules); } } /** * Update data validations when inserting/deleting rows/columns. * * @param Worksheet $worksheet The worksheet that we're editing * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion) * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) */ protected function adjustDataValidations(Worksheet $worksheet, int $numberOfColumns, int $numberOfRows, string $beforeCellAddress): void { $aDataValidationCollection = $worksheet->getDataValidationCollection(); ($numberOfColumns > 0 || $numberOfRows > 0) ? uksort($aDataValidationCollection, [self::class, 'cellReverseSort']) : uksort($aDataValidationCollection, [self::class, 'cellSort']); foreach ($aDataValidationCollection as $cellAddress => $dataValidation) { $formula = $dataValidation->getFormula1(); if ($formula !== '') { $dataValidation->setFormula1( $this->updateFormulaReferences( $formula, $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle(), true ) ); } $formula = $dataValidation->getFormula2(); if ($formula !== '') { $dataValidation->setFormula2( $this->updateFormulaReferences( $formula, $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle(), true ) ); } $addressParts = explode(' ', $cellAddress); $newReference = ''; $separator = ''; foreach ($addressParts as $addressPart) { $newReference .= $separator . $this->updateCellReference($addressPart); $separator = ' '; } if ($cellAddress !== $newReference) { $worksheet->setDataValidation($newReference, $dataValidation); $worksheet->setDataValidation($cellAddress, null); if ($newReference) { $worksheet->setDataValidation($newReference, $dataValidation); } } } } /** * Update merged cells when inserting/deleting rows/columns. * * @param Worksheet $worksheet The worksheet that we're editing */ protected function adjustMergeCells(Worksheet $worksheet): void { $aMergeCells = $worksheet->getMergeCells(); $aNewMergeCells = []; // the new array of all merge cells foreach ($aMergeCells as $cellAddress => &$value) { $newReference = $this->updateCellReference($cellAddress); if ($newReference) { $aNewMergeCells[$newReference] = $newReference; } } $worksheet->setMergeCells($aNewMergeCells); // replace the merge cells array } /** * Update protected cells when inserting/deleting rows/columns. * * @param Worksheet $worksheet The worksheet that we're editing * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion) * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) */ protected function adjustProtectedCells(Worksheet $worksheet, int $numberOfColumns, int $numberOfRows): void { $aProtectedCells = $worksheet->getProtectedCellRanges(); ($numberOfColumns > 0 || $numberOfRows > 0) ? uksort($aProtectedCells, [self::class, 'cellReverseSort']) : uksort($aProtectedCells, [self::class, 'cellSort']); foreach ($aProtectedCells as $cellAddress => $protectedRange) { $newReference = $this->updateCellReference($cellAddress); if ($cellAddress !== $newReference) { $worksheet->unprotectCells($cellAddress); if ($newReference) { $worksheet->protectCells($newReference, $protectedRange->getPassword(), true); } } } } /** * Update column dimensions when inserting/deleting rows/columns. * * @param Worksheet $worksheet The worksheet that we're editing */ protected function adjustColumnDimensions(Worksheet $worksheet): void { $aColumnDimensions = array_reverse($worksheet->getColumnDimensions(), true); if (!empty($aColumnDimensions)) { foreach ($aColumnDimensions as $objColumnDimension) { $newReference = $this->updateCellReference($objColumnDimension->getColumnIndex() . '1'); [$newReference] = Coordinate::coordinateFromString($newReference); if ($objColumnDimension->getColumnIndex() !== $newReference) { $objColumnDimension->setColumnIndex($newReference); } } $worksheet->refreshColumnDimensions(); } } /** * Update row dimensions when inserting/deleting rows/columns. * * @param Worksheet $worksheet The worksheet that we're editing * @param int $beforeRow Number of the row we're inserting/deleting before * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) */ protected function adjustRowDimensions(Worksheet $worksheet, int $beforeRow, int $numberOfRows): void { $aRowDimensions = array_reverse($worksheet->getRowDimensions(), true); if (!empty($aRowDimensions)) { foreach ($aRowDimensions as $objRowDimension) { $newReference = $this->updateCellReference('A' . $objRowDimension->getRowIndex()); [, $newReference] = Coordinate::coordinateFromString($newReference); $newRoweference = (int) $newReference; if ($objRowDimension->getRowIndex() !== $newRoweference) { $objRowDimension->setRowIndex($newRoweference); } } $worksheet->refreshRowDimensions(); $copyDimension = $worksheet->getRowDimension($beforeRow - 1); for ($i = $beforeRow; $i <= $beforeRow - 1 + $numberOfRows; ++$i) { $newDimension = $worksheet->getRowDimension($i); $newDimension->setRowHeight($copyDimension->getRowHeight()); $newDimension->setVisible($copyDimension->getVisible()); $newDimension->setOutlineLevel($copyDimension->getOutlineLevel()); $newDimension->setCollapsed($copyDimension->getCollapsed()); } } } /** * Insert a new column or row, updating all possible related data. * * @param string $beforeCellAddress Insert before this cell address (e.g. 'A1') * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion) * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) * @param Worksheet $worksheet The worksheet that we're editing */ public function insertNewBefore( string $beforeCellAddress, int $numberOfColumns, int $numberOfRows, Worksheet $worksheet ): void { $remove = ($numberOfColumns < 0 || $numberOfRows < 0); if ( $this->cellReferenceHelper === null || $this->cellReferenceHelper->refreshRequired($beforeCellAddress, $numberOfColumns, $numberOfRows) ) { $this->cellReferenceHelper = new CellReferenceHelper($beforeCellAddress, $numberOfColumns, $numberOfRows); } // Get coordinate of $beforeCellAddress [$beforeColumn, $beforeRow, $beforeColumnString] = Coordinate::indexesFromString($beforeCellAddress); // Clear cells if we are removing columns or rows $highestColumn = $worksheet->getHighestColumn(); $highestDataColumn = $worksheet->getHighestDataColumn(); $highestRow = $worksheet->getHighestRow(); $highestDataRow = $worksheet->getHighestDataRow(); // 1. Clear column strips if we are removing columns if ($numberOfColumns < 0 && $beforeColumn - 2 + $numberOfColumns > 0) { $this->clearColumnStrips($highestRow, $beforeColumn, $numberOfColumns, $worksheet); } // 2. Clear row strips if we are removing rows if ($numberOfRows < 0 && $beforeRow - 1 + $numberOfRows > 0) { $this->clearRowStrips($highestColumn, $beforeColumn, $beforeRow, $numberOfRows, $worksheet); } // Find missing coordinates. This is important when inserting or deleting column before the last column $startRow = $startCol = 1; $startColString = 'A'; if ($numberOfRows === 0) { $startCol = $beforeColumn; $startColString = $beforeColumnString; } elseif ($numberOfColumns === 0) { $startRow = $beforeRow; } $highColumn = Coordinate::columnIndexFromString($highestDataColumn); for ($row = $startRow; $row <= $highestDataRow; ++$row) { for ($col = $startCol, $colString = $startColString; $col <= $highColumn; ++$col, ++$colString) { $worksheet->getCell("$colString$row"); // create cell if it doesn't exist } } $allCoordinates = $worksheet->getCoordinates(); if ($remove) { // It's faster to reverse and pop than to use unshift, especially with large cell collections $allCoordinates = array_reverse($allCoordinates); } // Loop through cells, bottom-up, and change cell coordinate while ($coordinate = array_pop($allCoordinates)) { $cell = $worksheet->getCell($coordinate); $cellIndex = Coordinate::columnIndexFromString($cell->getColumn()); // Don't update cells that are being removed if ($numberOfColumns < 0 && $cellIndex >= $beforeColumn + $numberOfColumns && $cellIndex < $beforeColumn) { continue; } // New coordinate $newCoordinate = Coordinate::stringFromColumnIndex($cellIndex + $numberOfColumns) . ($cell->getRow() + $numberOfRows); // Should the cell be updated? Move value and cellXf index from one cell to another. if (($cellIndex >= $beforeColumn) && ($cell->getRow() >= $beforeRow)) { // Update cell styles $worksheet->getCell($newCoordinate)->setXfIndex($cell->getXfIndex()); // Insert this cell at its new location if ($cell->getDataType() === DataType::TYPE_FORMULA) { // Formula should be adjusted $worksheet->getCell($newCoordinate) ->setValue($this->updateFormulaReferences($cell->getValueString(), $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle(), true)); } else { // Cell value should not be adjusted $worksheet->getCell($newCoordinate)->setValueExplicit($cell->getValue(), $cell->getDataType()); } // Clear the original cell $worksheet->getCellCollection()->delete($coordinate); } else { /* We don't need to update styles for rows/columns before our insertion position, but we do still need to adjust any formulae in those cells */ if ($cell->getDataType() === DataType::TYPE_FORMULA) { // Formula should be adjusted $cell->setValue($this->updateFormulaReferences($cell->getValueString(), $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle(), true)); } } } // Duplicate styles for the newly inserted cells $highestColumn = $worksheet->getHighestColumn(); $highestRow = $worksheet->getHighestRow(); if ($numberOfColumns > 0 && $beforeColumn - 2 > 0) { $this->duplicateStylesByColumn($worksheet, $beforeColumn, $beforeRow, $highestRow, $numberOfColumns); } if ($numberOfRows > 0 && $beforeRow - 1 > 0) { $this->duplicateStylesByRow($worksheet, $beforeColumn, $beforeRow, $highestColumn, $numberOfRows); } // Update worksheet: column dimensions $this->adjustColumnDimensions($worksheet); // Update worksheet: row dimensions $this->adjustRowDimensions($worksheet, $beforeRow, $numberOfRows); // Update worksheet: page breaks $this->adjustPageBreaks($worksheet, $numberOfColumns, $numberOfRows); // Update worksheet: comments $this->adjustComments($worksheet); // Update worksheet: hyperlinks $this->adjustHyperlinks($worksheet, $numberOfColumns, $numberOfRows); // Update worksheet: conditional formatting styles $this->adjustConditionalFormatting($worksheet, $numberOfColumns, $numberOfRows); // Update worksheet: data validations $this->adjustDataValidations($worksheet, $numberOfColumns, $numberOfRows, $beforeCellAddress); // Update worksheet: merge cells $this->adjustMergeCells($worksheet); // Update worksheet: protected cells $this->adjustProtectedCells($worksheet, $numberOfColumns, $numberOfRows); // Update worksheet: autofilter $this->adjustAutoFilter($worksheet, $beforeCellAddress, $numberOfColumns); // Update worksheet: table $this->adjustTable($worksheet, $beforeCellAddress, $numberOfColumns); // Update worksheet: freeze pane if ($worksheet->getFreezePane()) { $splitCell = $worksheet->getFreezePane(); $topLeftCell = $worksheet->getTopLeftCell() ?? ''; $splitCell = $this->updateCellReference($splitCell); $topLeftCell = $this->updateCellReference($topLeftCell); $worksheet->freezePane($splitCell, $topLeftCell); } // Page setup if ($worksheet->getPageSetup()->isPrintAreaSet()) { $worksheet->getPageSetup()->setPrintArea( $this->updateCellReference($worksheet->getPageSetup()->getPrintArea()) ); } // Update worksheet: drawings $aDrawings = $worksheet->getDrawingCollection(); foreach ($aDrawings as $objDrawing) { $newReference = $this->updateCellReference($objDrawing->getCoordinates()); if ($objDrawing->getCoordinates() != $newReference) { $objDrawing->setCoordinates($newReference); } if ($objDrawing->getCoordinates2() !== '') { $newReference = $this->updateCellReference($objDrawing->getCoordinates2()); if ($objDrawing->getCoordinates2() != $newReference) { $objDrawing->setCoordinates2($newReference); } } } // Update workbook: define names if (count($worksheet->getParentOrThrow()->getDefinedNames()) > 0) { $this->updateDefinedNames($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows); } // Garbage collect $worksheet->garbageCollect(); } private static function matchSheetName(?string $match, string $worksheetName): bool { return $match === null || $match === '' || $match === "'\u{fffc}'" || $match === "'\u{fffb}'" || strcasecmp(trim($match, "'"), $worksheetName) === 0; } private static function sheetnameBeforeCells(string $match, string $worksheetName, string $cells): string { $toString = ($match > '') ? "$match!" : ''; return str_replace(["\u{fffc}", "'\u{fffb}'"], $worksheetName, $toString) . $cells; } /** * Update references within formulas. * * @param string $formula Formula to update * @param string $beforeCellAddress Insert before this one * @param int $numberOfColumns Number of columns to insert * @param int $numberOfRows Number of rows to insert * @param string $worksheetName Worksheet name/title * * @return string Updated formula */ public function updateFormulaReferences( string $formula = '', string $beforeCellAddress = 'A1', int $numberOfColumns = 0, int $numberOfRows = 0, string $worksheetName = '', bool $includeAbsoluteReferences = false, bool $onlyAbsoluteReferences = false ): string { $callback = fn (array $matches): string => (strcasecmp(trim($matches[2], "'"), $worksheetName) === 0) ? (($matches[2][0] === "'") ? "'\u{fffc}'!" : "'\u{fffb}'!") : "'\u{fffd}'!"; if ( $this->cellReferenceHelper === null || $this->cellReferenceHelper->refreshRequired($beforeCellAddress, $numberOfColumns, $numberOfRows) ) { $this->cellReferenceHelper = new CellReferenceHelper($beforeCellAddress, $numberOfColumns, $numberOfRows); } // Update cell references in the formula $formulaBlocks = explode('"', $formula); $i = false; foreach ($formulaBlocks as &$formulaBlock) { // Ignore blocks that were enclosed in quotes (alternating entries in the $formulaBlocks array after the explode) $i = $i === false; if ($i) { $adjustCount = 0; $newCellTokens = $cellTokens = []; // Search for row ranges (e.g. 'Sheet1'!3:5 or 3:5) with or without $ absolutes (e.g. $3:5) $formulaBlockx = ' ' . (preg_replace_callback(self::SHEETNAME_PART_WITH_SLASHES, $callback, $formulaBlock) ?? $formulaBlock) . ' '; $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_ROWRANGE . '/mui', $formulaBlockx, $matches, PREG_SET_ORDER); if ($matchCount > 0) { foreach ($matches as $match) { $fromString = self::sheetnameBeforeCells($match[2], $worksheetName, "{$match[3]}:{$match[4]}"); $modified3 = substr($this->updateCellReference('$A' . $match[3], $includeAbsoluteReferences, $onlyAbsoluteReferences, true), 2); $modified4 = substr($this->updateCellReference('$A' . $match[4], $includeAbsoluteReferences, $onlyAbsoluteReferences, false), 2); if ($match[3] . ':' . $match[4] !== $modified3 . ':' . $modified4) { if (self::matchSheetName($match[2], $worksheetName)) { $toString = self::sheetnameBeforeCells($match[2], $worksheetName, "$modified3:$modified4"); // Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more $column = 100000; $row = 10000000 + (int) trim($match[3], '$'); $cellIndex = "{$column}{$row}"; $newCellTokens[$cellIndex] = preg_quote($toString, '/'); $cellTokens[$cellIndex] = '/(? 0) { foreach ($matches as $match) { $fromString = self::sheetnameBeforeCells($match[2], $worksheetName, "{$match[3]}:{$match[4]}"); $modified3 = substr($this->updateCellReference($match[3] . '$1', $includeAbsoluteReferences, $onlyAbsoluteReferences, true), 0, -2); $modified4 = substr($this->updateCellReference($match[4] . '$1', $includeAbsoluteReferences, $onlyAbsoluteReferences, false), 0, -2); if ($match[3] . ':' . $match[4] !== $modified3 . ':' . $modified4) { if (self::matchSheetName($match[2], $worksheetName)) { $toString = self::sheetnameBeforeCells($match[2], $worksheetName, "$modified3:$modified4"); // Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more $column = Coordinate::columnIndexFromString(trim($match[3], '$')) + 100000; $row = 10000000; $cellIndex = "{$column}{$row}"; $newCellTokens[$cellIndex] = preg_quote($toString, '/'); $cellTokens[$cellIndex] = '/(? 0) { foreach ($matches as $match) { $fromString = self::sheetnameBeforeCells($match[2], $worksheetName, "{$match[3]}:{$match[4]}"); $modified3 = $this->updateCellReference($match[3], $includeAbsoluteReferences, $onlyAbsoluteReferences, true); $modified4 = $this->updateCellReference($match[4], $includeAbsoluteReferences, $onlyAbsoluteReferences, false); if ($match[3] . $match[4] !== $modified3 . $modified4) { if (self::matchSheetName($match[2], $worksheetName)) { $toString = self::sheetnameBeforeCells($match[2], $worksheetName, "$modified3:$modified4"); [$column, $row] = Coordinate::coordinateFromString($match[3]); // Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more $column = Coordinate::columnIndexFromString(trim($column, '$')) + 100000; $row = (int) trim($row, '$') + 10000000; $cellIndex = "{$column}{$row}"; $newCellTokens[$cellIndex] = preg_quote($toString, '/'); $cellTokens[$cellIndex] = '/(? 0) { foreach ($matches as $match) { $fromString = self::sheetnameBeforeCells($match[2], $worksheetName, "{$match[3]}"); $modified3 = $this->updateCellReference($match[3], $includeAbsoluteReferences, $onlyAbsoluteReferences, null); if ($match[3] !== $modified3) { if (self::matchSheetName($match[2], $worksheetName)) { $toString = self::sheetnameBeforeCells($match[2], $worksheetName, "$modified3"); [$column, $row] = Coordinate::coordinateFromString($match[3]); $columnAdditionalIndex = $column[0] === '$' ? 1 : 0; $rowAdditionalIndex = $row[0] === '$' ? 1 : 0; // Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more $column = Coordinate::columnIndexFromString(trim($column, '$')) + 100000; $row = (int) trim($row, '$') + 10000000; $cellIndex = $row . $rowAdditionalIndex . $column . $columnAdditionalIndex; $newCellTokens[$cellIndex] = preg_quote($toString, '/'); $cellTokens[$cellIndex] = '/(? 0) { if ($numberOfColumns > 0 || $numberOfRows > 0) { krsort($cellTokens); krsort($newCellTokens); } else { ksort($cellTokens); ksort($newCellTokens); } // Update cell references in the formula $formulaBlock = str_replace('\\', '', (string) preg_replace($cellTokens, $newCellTokens, $formulaBlock)); } } } unset($formulaBlock); // Then rebuild the formula string return implode('"', $formulaBlocks); } /** * Update all cell references within a formula, irrespective of worksheet. */ public function updateFormulaReferencesAnyWorksheet(string $formula = '', int $numberOfColumns = 0, int $numberOfRows = 0): string { $formula = $this->updateCellReferencesAllWorksheets($formula, $numberOfColumns, $numberOfRows); if ($numberOfColumns !== 0) { $formula = $this->updateColumnRangesAllWorksheets($formula, $numberOfColumns); } if ($numberOfRows !== 0) { $formula = $this->updateRowRangesAllWorksheets($formula, $numberOfRows); } return $formula; } private function updateCellReferencesAllWorksheets(string $formula, int $numberOfColumns, int $numberOfRows): string { $splitCount = preg_match_all( '/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/mui', $formula, $splitRanges, PREG_OFFSET_CAPTURE ); $columnLengths = array_map('strlen', array_column($splitRanges[6], 0)); $rowLengths = array_map('strlen', array_column($splitRanges[7], 0)); $columnOffsets = array_column($splitRanges[6], 1); $rowOffsets = array_column($splitRanges[7], 1); $columns = $splitRanges[6]; $rows = $splitRanges[7]; while ($splitCount > 0) { --$splitCount; $columnLength = $columnLengths[$splitCount]; $rowLength = $rowLengths[$splitCount]; $columnOffset = $columnOffsets[$splitCount]; $rowOffset = $rowOffsets[$splitCount]; $column = $columns[$splitCount][0]; $row = $rows[$splitCount][0]; if ($column[0] !== '$') { $column = ((Coordinate::columnIndexFromString($column) + $numberOfColumns) % AddressRange::MAX_COLUMN_INT) ?: AddressRange::MAX_COLUMN_INT; $column = Coordinate::stringFromColumnIndex($column); $rowOffset -= ($columnLength - strlen($column)); $formula = substr($formula, 0, $columnOffset) . $column . substr($formula, $columnOffset + $columnLength); } if (!empty($row) && $row[0] !== '$') { $row = (((int) $row + $numberOfRows) % AddressRange::MAX_ROW) ?: AddressRange::MAX_ROW; $formula = substr($formula, 0, $rowOffset) . $row . substr($formula, $rowOffset + $rowLength); } } return $formula; } private function updateColumnRangesAllWorksheets(string $formula, int $numberOfColumns): string { $splitCount = preg_match_all( '/' . Calculation::CALCULATION_REGEXP_COLUMNRANGE_RELATIVE . '/mui', $formula, $splitRanges, PREG_OFFSET_CAPTURE ); $fromColumnLengths = array_map('strlen', array_column($splitRanges[1], 0)); $fromColumnOffsets = array_column($splitRanges[1], 1); $toColumnLengths = array_map('strlen', array_column($splitRanges[2], 0)); $toColumnOffsets = array_column($splitRanges[2], 1); $fromColumns = $splitRanges[1]; $toColumns = $splitRanges[2]; while ($splitCount > 0) { --$splitCount; $fromColumnLength = $fromColumnLengths[$splitCount]; $toColumnLength = $toColumnLengths[$splitCount]; $fromColumnOffset = $fromColumnOffsets[$splitCount]; $toColumnOffset = $toColumnOffsets[$splitCount]; $fromColumn = $fromColumns[$splitCount][0]; $toColumn = $toColumns[$splitCount][0]; if (!empty($fromColumn) && $fromColumn[0] !== '$') { $fromColumn = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($fromColumn) + $numberOfColumns); $formula = substr($formula, 0, $fromColumnOffset) . $fromColumn . substr($formula, $fromColumnOffset + $fromColumnLength); } if (!empty($toColumn) && $toColumn[0] !== '$') { $toColumn = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($toColumn) + $numberOfColumns); $formula = substr($formula, 0, $toColumnOffset) . $toColumn . substr($formula, $toColumnOffset + $toColumnLength); } } return $formula; } private function updateRowRangesAllWorksheets(string $formula, int $numberOfRows): string { $splitCount = preg_match_all( '/' . Calculation::CALCULATION_REGEXP_ROWRANGE_RELATIVE . '/mui', $formula, $splitRanges, PREG_OFFSET_CAPTURE ); $fromRowLengths = array_map('strlen', array_column($splitRanges[1], 0)); $fromRowOffsets = array_column($splitRanges[1], 1); $toRowLengths = array_map('strlen', array_column($splitRanges[2], 0)); $toRowOffsets = array_column($splitRanges[2], 1); $fromRows = $splitRanges[1]; $toRows = $splitRanges[2]; while ($splitCount > 0) { --$splitCount; $fromRowLength = $fromRowLengths[$splitCount]; $toRowLength = $toRowLengths[$splitCount]; $fromRowOffset = $fromRowOffsets[$splitCount]; $toRowOffset = $toRowOffsets[$splitCount]; $fromRow = $fromRows[$splitCount][0]; $toRow = $toRows[$splitCount][0]; if (!empty($fromRow) && $fromRow[0] !== '$') { $fromRow = (int) $fromRow + $numberOfRows; $formula = substr($formula, 0, $fromRowOffset) . $fromRow . substr($formula, $fromRowOffset + $fromRowLength); } if (!empty($toRow) && $toRow[0] !== '$') { $toRow = (int) $toRow + $numberOfRows; $formula = substr($formula, 0, $toRowOffset) . $toRow . substr($formula, $toRowOffset + $toRowLength); } } return $formula; } /** * Update cell reference. * * @param string $cellReference Cell address or range of addresses * * @return string Updated cell range */ private function updateCellReference(string $cellReference = 'A1', bool $includeAbsoluteReferences = false, bool $onlyAbsoluteReferences = false, ?bool $topLeft = null) { // Is it in another worksheet? Will not have to update anything. if (str_contains($cellReference, '!')) { return $cellReference; } // Is it a range or a single cell? if (!Coordinate::coordinateIsRange($cellReference)) { // Single cell /** @var CellReferenceHelper */ $cellReferenceHelper = $this->cellReferenceHelper; return $cellReferenceHelper->updateCellReference($cellReference, $includeAbsoluteReferences, $onlyAbsoluteReferences, $topLeft); } // Range return $this->updateCellRange($cellReference, $includeAbsoluteReferences, $onlyAbsoluteReferences); } /** * Update named formulae (i.e. containing worksheet references / named ranges). * * @param Spreadsheet $spreadsheet Object to update * @param string $oldName Old name (name to replace) * @param string $newName New name */ public function updateNamedFormulae(Spreadsheet $spreadsheet, string $oldName = '', string $newName = ''): void { if ($oldName == '') { return; } foreach ($spreadsheet->getWorksheetIterator() as $sheet) { foreach ($sheet->getCoordinates(false) as $coordinate) { $cell = $sheet->getCell($coordinate); if ($cell->getDataType() === DataType::TYPE_FORMULA) { $formula = $cell->getValueString(); if (str_contains($formula, $oldName)) { $formula = str_replace("'" . $oldName . "'!", "'" . $newName . "'!", $formula); $formula = str_replace($oldName . '!', $newName . '!', $formula); $cell->setValueExplicit($formula, DataType::TYPE_FORMULA); } } } } } private function updateDefinedNames(Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): void { foreach ($worksheet->getParentOrThrow()->getDefinedNames() as $definedName) { if ($definedName->isFormula() === false) { $this->updateNamedRange($definedName, $worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows); } else { $this->updateNamedFormula($definedName, $worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows); } } } private function updateNamedRange(DefinedName $definedName, Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): void { $cellAddress = $definedName->getValue(); $asFormula = ($cellAddress[0] === '='); if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashInt() === $worksheet->getHashInt()) { /** * If we delete the entire range that is referenced by a Named Range, MS Excel sets the value to #REF! * PhpSpreadsheet still only does a basic adjustment, so the Named Range will still reference Cells. * Note that this applies only when deleting columns/rows; subsequent insertion won't fix the #REF! * TODO Can we work out a method to identify Named Ranges that cease to be valid, so that we can replace * them with a #REF! */ if ($asFormula === true) { $formula = $this->updateFormulaReferences($cellAddress, $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle(), true, true); $definedName->setValue($formula); } else { $definedName->setValue($this->updateCellReference(ltrim($cellAddress, '='), true)); } } } private function updateNamedFormula(DefinedName $definedName, Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): void { if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashInt() === $worksheet->getHashInt()) { /** * If we delete the entire range that is referenced by a Named Formula, MS Excel sets the value to #REF! * PhpSpreadsheet still only does a basic adjustment, so the Named Formula will still reference Cells. * Note that this applies only when deleting columns/rows; subsequent insertion won't fix the #REF! * TODO Can we work out a method to identify Named Ranges that cease to be valid, so that we can replace * them with a #REF! */ $formula = $definedName->getValue(); $formula = $this->updateFormulaReferences($formula, $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle(), true); $definedName->setValue($formula); } } /** * Update cell range. * * @param string $cellRange Cell range (e.g. 'B2:D4', 'B:C' or '2:3') * * @return string Updated cell range */ private function updateCellRange(string $cellRange = 'A1:A1', bool $includeAbsoluteReferences = false, bool $onlyAbsoluteReferences = false): string { if (!Coordinate::coordinateIsRange($cellRange)) { throw new Exception('Only cell ranges may be passed to this method.'); } // Update range $range = Coordinate::splitRange($cellRange); $ic = count($range); for ($i = 0; $i < $ic; ++$i) { $jc = count($range[$i]); for ($j = 0; $j < $jc; ++$j) { /** @var CellReferenceHelper */ $cellReferenceHelper = $this->cellReferenceHelper; if (ctype_alpha($range[$i][$j])) { $range[$i][$j] = Coordinate::coordinateFromString( $cellReferenceHelper->updateCellReference($range[$i][$j] . '1', $includeAbsoluteReferences, $onlyAbsoluteReferences, null) )[0]; } elseif (ctype_digit($range[$i][$j])) { $range[$i][$j] = Coordinate::coordinateFromString( $cellReferenceHelper->updateCellReference('A' . $range[$i][$j], $includeAbsoluteReferences, $onlyAbsoluteReferences, null) )[1]; } else { $range[$i][$j] = $cellReferenceHelper->updateCellReference($range[$i][$j], $includeAbsoluteReferences, $onlyAbsoluteReferences, null); } } } // Recreate range string return Coordinate::buildRange($range); } private function clearColumnStrips(int $highestRow, int $beforeColumn, int $numberOfColumns, Worksheet $worksheet): void { $startColumnId = Coordinate::stringFromColumnIndex($beforeColumn + $numberOfColumns); $endColumnId = Coordinate::stringFromColumnIndex($beforeColumn); for ($row = 1; $row <= $highestRow - 1; ++$row) { for ($column = $startColumnId; $column !== $endColumnId; ++$column) { $coordinate = $column . $row; $this->clearStripCell($worksheet, $coordinate); } } } private function clearRowStrips(string $highestColumn, int $beforeColumn, int $beforeRow, int $numberOfRows, Worksheet $worksheet): void { $startColumnId = Coordinate::stringFromColumnIndex($beforeColumn); ++$highestColumn; for ($column = $startColumnId; $column !== $highestColumn; ++$column) { for ($row = $beforeRow + $numberOfRows; $row <= $beforeRow - 1; ++$row) { $coordinate = $column . $row; $this->clearStripCell($worksheet, $coordinate); } } } private function clearStripCell(Worksheet $worksheet, string $coordinate): void { $worksheet->removeConditionalStyles($coordinate); $worksheet->setHyperlink($coordinate); $worksheet->setDataValidation($coordinate); $worksheet->removeComment($coordinate); if ($worksheet->cellExists($coordinate)) { $worksheet->getCell($coordinate)->setValueExplicit(null, DataType::TYPE_NULL); $worksheet->getCell($coordinate)->setXfIndex(0); } } private function adjustAutoFilter(Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns): void { $autoFilter = $worksheet->getAutoFilter(); $autoFilterRange = $autoFilter->getRange(); if (!empty($autoFilterRange)) { if ($numberOfColumns !== 0) { $autoFilterColumns = $autoFilter->getColumns(); if (count($autoFilterColumns) > 0) { $column = ''; $row = 0; sscanf($beforeCellAddress, '%[A-Z]%d', $column, $row); $columnIndex = Coordinate::columnIndexFromString((string) $column); [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($autoFilterRange); if ($columnIndex <= $rangeEnd[0]) { if ($numberOfColumns < 0) { $this->adjustAutoFilterDeleteRules($columnIndex, $numberOfColumns, $autoFilterColumns, $autoFilter); } $startCol = ($columnIndex > $rangeStart[0]) ? $columnIndex : $rangeStart[0]; // Shuffle columns in autofilter range if ($numberOfColumns > 0) { $this->adjustAutoFilterInsert($startCol, $numberOfColumns, $rangeEnd[0], $autoFilter); } else { $this->adjustAutoFilterDelete($startCol, $numberOfColumns, $rangeEnd[0], $autoFilter); } } } } $worksheet->setAutoFilter( $this->updateCellReference($autoFilterRange) ); } } private function adjustAutoFilterDeleteRules(int $columnIndex, int $numberOfColumns, array $autoFilterColumns, AutoFilter $autoFilter): void { // If we're actually deleting any columns that fall within the autofilter range, // then we delete any rules for those columns $deleteColumn = $columnIndex + $numberOfColumns - 1; $deleteCount = abs($numberOfColumns); for ($i = 1; $i <= $deleteCount; ++$i) { $columnName = Coordinate::stringFromColumnIndex($deleteColumn + 1); if (isset($autoFilterColumns[$columnName])) { $autoFilter->clearColumn($columnName); } ++$deleteColumn; } } private function adjustAutoFilterInsert(int $startCol, int $numberOfColumns, int $rangeEnd, AutoFilter $autoFilter): void { $startColRef = $startCol; $endColRef = $rangeEnd; $toColRef = $rangeEnd + $numberOfColumns; do { $autoFilter->shiftColumn(Coordinate::stringFromColumnIndex($endColRef), Coordinate::stringFromColumnIndex($toColRef)); --$endColRef; --$toColRef; } while ($startColRef <= $endColRef); } private function adjustAutoFilterDelete(int $startCol, int $numberOfColumns, int $rangeEnd, AutoFilter $autoFilter): void { // For delete, we shuffle from beginning to end to avoid overwriting $startColID = Coordinate::stringFromColumnIndex($startCol); $toColID = Coordinate::stringFromColumnIndex($startCol + $numberOfColumns); $endColID = Coordinate::stringFromColumnIndex($rangeEnd + 1); do { $autoFilter->shiftColumn($startColID, $toColID); ++$toColID; ++$startColID; // this confuses phpstan into thinking startColID is int/float } while ($startColID !== $endColID); // @phpstan-ignore-line } private function adjustTable(Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns): void { $tableCollection = $worksheet->getTableCollection(); foreach ($tableCollection as $table) { $tableRange = $table->getRange(); if (!empty($tableRange)) { if ($numberOfColumns !== 0) { $tableColumns = $table->getColumns(); if (count($tableColumns) > 0) { $column = ''; $row = 0; sscanf($beforeCellAddress, '%[A-Z]%d', $column, $row); $columnIndex = Coordinate::columnIndexFromString((string) $column); [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($tableRange); if ($columnIndex <= $rangeEnd[0]) { if ($numberOfColumns < 0) { $this->adjustTableDeleteRules($columnIndex, $numberOfColumns, $tableColumns, $table); } $startCol = ($columnIndex > $rangeStart[0]) ? $columnIndex : $rangeStart[0]; // Shuffle columns in table range if ($numberOfColumns > 0) { $this->adjustTableInsert($startCol, $numberOfColumns, $rangeEnd[0], $table); } else { $this->adjustTableDelete($startCol, $numberOfColumns, $rangeEnd[0], $table); } } } } $table->setRange($this->updateCellReference($tableRange)); } } } private function adjustTableDeleteRules(int $columnIndex, int $numberOfColumns, array $tableColumns, Table $table): void { // If we're actually deleting any columns that fall within the table range, // then we delete any rules for those columns $deleteColumn = $columnIndex + $numberOfColumns - 1; $deleteCount = abs($numberOfColumns); for ($i = 1; $i <= $deleteCount; ++$i) { $columnName = Coordinate::stringFromColumnIndex($deleteColumn + 1); if (isset($tableColumns[$columnName])) { $table->clearColumn($columnName); } ++$deleteColumn; } } private function adjustTableInsert(int $startCol, int $numberOfColumns, int $rangeEnd, Table $table): void { $startColRef = $startCol; $endColRef = $rangeEnd; $toColRef = $rangeEnd + $numberOfColumns; do { $table->shiftColumn(Coordinate::stringFromColumnIndex($endColRef), Coordinate::stringFromColumnIndex($toColRef)); --$endColRef; --$toColRef; } while ($startColRef <= $endColRef); } private function adjustTableDelete(int $startCol, int $numberOfColumns, int $rangeEnd, Table $table): void { // For delete, we shuffle from beginning to end to avoid overwriting $startColID = Coordinate::stringFromColumnIndex($startCol); $toColID = Coordinate::stringFromColumnIndex($startCol + $numberOfColumns); $endColID = Coordinate::stringFromColumnIndex($rangeEnd + 1); do { $table->shiftColumn($startColID, $toColID); ++$toColID; ++$startColID; // this confuses phpstan into thinking startColID is int/float } while ($startColID !== $endColID); // @phpstan-ignore-line } private function duplicateStylesByColumn(Worksheet $worksheet, int $beforeColumn, int $beforeRow, int $highestRow, int $numberOfColumns): void { $beforeColumnName = Coordinate::stringFromColumnIndex($beforeColumn - 1); for ($i = $beforeRow; $i <= $highestRow - 1; ++$i) { // Style $coordinate = $beforeColumnName . $i; if ($worksheet->cellExists($coordinate)) { $xfIndex = $worksheet->getCell($coordinate)->getXfIndex(); for ($j = $beforeColumn; $j <= $beforeColumn - 1 + $numberOfColumns; ++$j) { if (!empty($xfIndex) || $worksheet->cellExists([$j, $i])) { $worksheet->getCell([$j, $i])->setXfIndex($xfIndex); } } } } } private function duplicateStylesByRow(Worksheet $worksheet, int $beforeColumn, int $beforeRow, string $highestColumn, int $numberOfRows): void { $highestColumnIndex = Coordinate::columnIndexFromString($highestColumn); for ($i = $beforeColumn; $i <= $highestColumnIndex; ++$i) { // Style $coordinate = Coordinate::stringFromColumnIndex($i) . ($beforeRow - 1); if ($worksheet->cellExists($coordinate)) { $xfIndex = $worksheet->getCell($coordinate)->getXfIndex(); for ($j = $beforeRow; $j <= $beforeRow - 1 + $numberOfRows; ++$j) { if (!empty($xfIndex) || $worksheet->cellExists([$i, $j])) { $worksheet->getCell(Coordinate::stringFromColumnIndex($i) . $j)->setXfIndex($xfIndex); } } } } } /** * __clone implementation. Cloning should not be allowed in a Singleton! */ final public function __clone() { throw new Exception('Cloning a Singleton is not allowed!'); } } PK!M(MM0libraries/vendor/PhpSpreadsheet/NamedFormula.phpnu[value; } /** * Set the formula value. */ public function setFormula(string $formula): self { if (!empty($formula)) { $this->value = $formula; } return $this; } } PK!{{-libraries/vendor/PhpSpreadsheet/Exception.phpnu[getScriptFilename() === 'index'; } /** * Return the page title. */ public function getPageTitle(): string { return $this->isIndex() ? 'PHPSpreadsheet' : $this->getScriptFilename(); } /** * Return the page heading. */ public function getPageHeading(): string { return $this->isIndex() ? '' : '

    ' . str_replace('_', ' ', $this->getScriptFilename()) . '

    '; } /** * Returns an array of all known samples. * * @return string[][] [$name => $path] */ public function getSamples(): array { // Populate samples $baseDir = realpath(__DIR__ . '/../../../samples'); if ($baseDir === false) { // @codeCoverageIgnoreStart throw new RuntimeException('realpath returned false'); // @codeCoverageIgnoreEnd } $directory = new RecursiveDirectoryIterator($baseDir); $iterator = new RecursiveIteratorIterator($directory); $regex = new RegexIterator($iterator, '/^.+\.php$/', RecursiveRegexIterator::GET_MATCH); $files = []; /** @var string[] $file */ foreach ($regex as $file) { $file = str_replace(str_replace('\\', '/', $baseDir) . '/', '', str_replace('\\', '/', $file[0])); $info = pathinfo($file); $category = str_replace('_', ' ', $info['dirname'] ?? ''); $name = str_replace('_', ' ', (string) preg_replace('/(|\.php)/', '', $info['filename'])); if (!in_array($category, ['.', 'bootstrap', 'templates']) && $name !== 'Header') { if (!isset($files[$category])) { $files[$category] = []; } $files[$category][$name] = $file; } } // Sort everything ksort($files); foreach ($files as &$f) { asort($f); } return $files; } /** * Write documents. * * @param string[] $writers */ public function write(Spreadsheet $spreadsheet, string $filename, array $writers = ['Xlsx', 'Xls'], bool $withCharts = false, ?callable $writerCallback = null, bool $resetActiveSheet = true): void { // Set active sheet index to the first sheet, so Excel opens this as the first sheet if ($resetActiveSheet) { $spreadsheet->setActiveSheetIndex(0); } // Write documents foreach ($writers as $writerType) { $path = $this->getFilename($filename, mb_strtolower($writerType)); if (preg_match('/(mpdf|tcpdf)$/', $path)) { $path .= '.pdf'; } $writer = IOFactory::createWriter($spreadsheet, $writerType); $writer->setIncludeCharts($withCharts); if ($writerCallback !== null) { $writerCallback($writer); } $callStartTime = microtime(true); $writer->save($path); $this->logWrite($writer, $path, $callStartTime); if ($this->isCli() === false) { // @codeCoverageIgnoreStart echo 'Download ' . basename($path) . '
    '; // @codeCoverageIgnoreEnd } } $this->logEndingNotes(); } protected function isDirOrMkdir(string $folder): bool { return \is_dir($folder) || \mkdir($folder); } /** * Returns the temporary directory and make sure it exists. */ public function getTemporaryFolder(): string { $tempFolder = sys_get_temp_dir() . '/phpspreadsheet'; if (!$this->isDirOrMkdir($tempFolder)) { throw new RuntimeException(sprintf('Directory "%s" was not created', $tempFolder)); } return $tempFolder; } /** * Returns the filename that should be used for sample output. */ public function getFilename(string $filename, string $extension = 'xlsx'): string { $originalExtension = pathinfo($filename, PATHINFO_EXTENSION); return $this->getTemporaryFolder() . '/' . str_replace('.' . $originalExtension, '.' . $extension, basename($filename)); } /** * Return a random temporary file name. */ public function getTemporaryFilename(string $extension = 'xlsx'): string { $temporaryFilename = tempnam($this->getTemporaryFolder(), 'phpspreadsheet-'); if ($temporaryFilename === false) { // @codeCoverageIgnoreStart throw new RuntimeException('tempnam returned false'); // @codeCoverageIgnoreEnd } unlink($temporaryFilename); return $temporaryFilename . '.' . $extension; } /** * @param mixed $message */ public function log($message): void { $eol = $this->isCli() ? PHP_EOL : '
    '; echo ($this->isCli() ? date('H:i:s ') : '') . StringHelper::convertToString($message) . $eol; } /** * Render chart as part of running chart samples in browser. * Charts are not rendered in unit tests, which are command line. * * @codeCoverageIgnore */ public function renderChart(Chart $chart, string $fileName, ?Spreadsheet $spreadsheet = null): void { if ($this->isCli() === true) { return; } Settings::setChartRenderer(MtJpGraphRenderer::class); $fileName = $this->getFilename($fileName, 'png'); $title = $chart->getTitle(); $caption = null; if ($title !== null) { $calculatedTitle = $title->getCalculatedTitle($spreadsheet); if ($calculatedTitle !== null) { $caption = $title->getCaption(); $title->setCaption($calculatedTitle); } } try { $chart->render($fileName); $this->log('Rendered image: ' . $fileName); $imageData = @file_get_contents($fileName); if ($imageData !== false) { echo '
    '; } else { $this->log('Unable to open chart' . PHP_EOL); } } catch (Throwable $e) { $this->log('Error rendering chart: ' . $e->getMessage() . PHP_EOL); } if (isset($title, $caption)) { $title->setCaption($caption); } Settings::unsetChartRenderer(); } public function titles(string $category, string $functionName, ?string $description = null): void { $this->log(sprintf('%s Functions:', $category)); $description === null ? $this->log(sprintf('Function: %s()', rtrim($functionName, '()'))) : $this->log(sprintf('Function: %s() - %s.', rtrim($functionName, '()'), rtrim($description, '.'))); } public function displayGrid(array $matrix): void { $renderer = new TextGrid($matrix, $this->isCli()); echo $renderer->render(); } public function logCalculationResult( Worksheet $worksheet, string $functionName, string $formulaCell, ?string $descriptionCell = null ): void { if ($descriptionCell !== null) { $this->log($worksheet->getCell($descriptionCell)->getValueString()); } $this->log($worksheet->getCell($formulaCell)->getValueString()); $this->log(sprintf('%s() Result is ', $functionName) . $worksheet->getCell($formulaCell)->getCalculatedValueString()); } /** * Log ending notes. */ public function logEndingNotes(): void { // Do not show execution time for index $this->log('Peak memory usage: ' . (memory_get_peak_usage(true) / 1024 / 1024) . 'MB'); } /** * Log a line about the write operation. */ public function logWrite(IWriter $writer, string $path, float $callStartTime): void { $callEndTime = microtime(true); $callTime = $callEndTime - $callStartTime; $reflection = new ReflectionClass($writer); $format = $reflection->getShortName(); $codePath = $this->isCli() ? $path : "$path"; $message = "Write {$format} format to {$codePath} in " . sprintf('%.4f', $callTime) . ' seconds'; $this->log($message); } /** * Log a line about the read operation. */ public function logRead(string $format, string $path, float $callStartTime): void { $callEndTime = microtime(true); $callTime = $callEndTime - $callStartTime; $message = "Read {$format} format from {$path} in " . sprintf('%.4f', $callTime) . ' seconds'; $this->log($message); } } PK!q/libraries/vendor/PhpSpreadsheet/Helper/Size.phpnu[\d*\.?\d+)(?Ppt|px|em)?$/i'; protected bool $valid = false; protected string $size = ''; protected string $unit = ''; public function __construct(string $size) { if (1 === preg_match(self::REGEXP_SIZE_VALIDATION, $size, $matches)) { $this->valid = true; $this->size = $matches['size']; $this->unit = $matches['unit'] ?? 'pt'; } } public function valid(): bool { return $this->valid; } public function size(): string { return $this->size; } public function unit(): string { return $this->unit; } public function __toString(): string { return $this->size . $this->unit; } } PK!L؜3libraries/vendor/PhpSpreadsheet/Helper/TextGrid.phpnu[rows = array_keys($matrix); $this->columns = array_keys($matrix[$this->rows[0]]); $matrix = array_values($matrix); array_walk( $matrix, function (&$row): void { $row = array_values($row); } ); $this->matrix = $matrix; $this->isCli = $isCli; $this->rowDividers = $rowDividers; $this->rowHeaders = $rowHeaders; $this->columnHeaders = $columnHeaders; } public function render(): string { $this->gridDisplay = $this->isCli ? '' : ('
    ' . PHP_EOL);
    
    		if (!empty($this->rows)) {
    			$maxRow = max($this->rows);
    			$maxRowLength = strlen((string) $maxRow) + 1;
    			$columnWidths = $this->getColumnWidths();
    
    			$this->renderColumnHeader($maxRowLength, $columnWidths);
    			$this->renderRows($maxRowLength, $columnWidths);
    			if (!$this->rowDividers) {
    				$this->renderFooter($maxRowLength, $columnWidths);
    			}
    		}
    
    		$this->gridDisplay .= $this->isCli ? '' : '
    '; return $this->gridDisplay; } private function renderRows(int $maxRowLength, array $columnWidths): void { foreach ($this->matrix as $row => $rowData) { if ($this->rowHeaders) { $this->gridDisplay .= '|' . str_pad((string) $this->rows[$row], $maxRowLength, ' ', STR_PAD_LEFT) . ' '; } $this->renderCells($rowData, $columnWidths); $this->gridDisplay .= '|' . PHP_EOL; if ($this->rowDividers) { $this->renderFooter($maxRowLength, $columnWidths); } } } private function renderCells(array $rowData, array $columnWidths): void { foreach ($rowData as $column => $cell) { $valueForLength = $this->getString($cell); $displayCell = $this->isCli ? $valueForLength : htmlentities($valueForLength); $this->gridDisplay .= '| '; $this->gridDisplay .= $displayCell . str_repeat(' ', $columnWidths[$column] - $this->strlen($valueForLength) + 1); } } private function renderColumnHeader(int $maxRowLength, array &$columnWidths): void { if (!$this->columnHeaders) { $this->renderFooter($maxRowLength, $columnWidths); return; } foreach ($this->columns as $column => $reference) { $columnWidths[$column] = max($columnWidths[$column], $this->strlen($reference)); } if ($this->rowHeaders) { $this->gridDisplay .= str_repeat(' ', $maxRowLength + 2); } foreach ($this->columns as $column => $reference) { $this->gridDisplay .= '+-' . str_repeat('-', $columnWidths[$column] + 1); } $this->gridDisplay .= '+' . PHP_EOL; if ($this->rowHeaders) { $this->gridDisplay .= str_repeat(' ', $maxRowLength + 2); } foreach ($this->columns as $column => $reference) { $this->gridDisplay .= '| ' . str_pad((string) $reference, $columnWidths[$column] + 1, ' '); } $this->gridDisplay .= '|' . PHP_EOL; $this->renderFooter($maxRowLength, $columnWidths); } private function renderFooter(int $maxRowLength, array $columnWidths): void { if ($this->rowHeaders) { $this->gridDisplay .= '+' . str_repeat('-', $maxRowLength + 1); } foreach ($this->columns as $column => $reference) { $this->gridDisplay .= '+-'; $this->gridDisplay .= str_pad((string) '', $columnWidths[$column] + 1, '-'); } $this->gridDisplay .= '+' . PHP_EOL; } private function getColumnWidths(): array { $columnCount = count($this->matrix, COUNT_RECURSIVE) / count($this->matrix); $columnWidths = []; for ($column = 0; $column < $columnCount; ++$column) { $columnWidths[] = $this->getColumnWidth(array_column($this->matrix, $column)); } return $columnWidths; } private function getColumnWidth(array $columnData): int { $columnWidth = 0; $columnData = array_values($columnData); foreach ($columnData as $columnValue) { $columnWidth = max($columnWidth, $this->strlen($this->getString($columnValue))); } return $columnWidth; } /** * @param mixed $value */ protected function getString($value): string { return StringHelper::convertToString($value, true, '', true); } protected function strlen(string $value): int { return mb_strlen($value); } } PK!0libraries/vendor/PhpSpreadsheet/Helper/index.phpnu[ 96.0 / 2.54, self::UOM_MILLIMETERS => 96.0 / 25.4, self::UOM_INCHES => 96.0, self::UOM_PIXELS => 1.0, self::UOM_POINTS => 96.0 / 72, self::UOM_PICA => 96.0 * 12 / 72, ]; /** * Based on a standard column width of 8.54 units in MS Excel. */ const RELATIVE_UNITS = [ 'em' => 10.0 / 8.54, 'ex' => 10.0 / 8.54, 'ch' => 10.0 / 8.54, 'rem' => 10.0 / 8.54, 'vw' => 8.54, 'vh' => 8.54, 'vmin' => 8.54, 'vmax' => 8.54, '%' => 8.54 / 100, ]; /** * @var float|int If this is a width, then size is measured in pixels (if is set) * or in Excel's default column width units if $unit is null. * If this is a height, then size is measured in pixels () * or in points () if $unit is null. */ protected $size; protected ?string $unit = null; /** * Phpstan bug has been fixed; this function allows us to * pass Phpstan whether fixed or not. * @param mixed[]|int|null $value */ private static function stanBugFixed($value): array { return is_array($value) ? $value : [null, null]; } public function __construct(string $dimension) { [$size, $unit] = self::stanBugFixed(sscanf($dimension, '%[1234567890.]%s')); $unit = strtolower(trim($unit ?? '')); $size = (float) $size; // If a UoM is specified, then convert the size to pixels for internal storage if (isset(self::ABSOLUTE_UNITS[$unit])) { $size *= self::ABSOLUTE_UNITS[$unit]; $this->unit = self::UOM_PIXELS; } elseif (isset(self::RELATIVE_UNITS[$unit])) { $size *= self::RELATIVE_UNITS[$unit]; $size = round($size, 4); } $this->size = $size; } public function width(): float { return (float) ($this->unit === null) ? $this->size : round(Drawing::pixelsToCellDimension((int) $this->size, new Font(false)), 4); } public function height(): float { return (float) ($this->unit === null) ? $this->size : $this->toUnit(self::UOM_POINTS); } public function toUnit(string $unitOfMeasure): float { $unitOfMeasure = strtolower($unitOfMeasure); if (!array_key_exists($unitOfMeasure, self::ABSOLUTE_UNITS)) { throw new Exception("{$unitOfMeasure} is not a vaid unit of measure"); } $size = $this->size; if ($this->unit === null) { $size = Drawing::cellDimensionToPixels($size, new Font(false)); } return $size / self::ABSOLUTE_UNITS[$unitOfMeasure]; } } PK!,,sG G 5libraries/vendor/PhpSpreadsheet/Helper/Downloader.phpnu[ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'xls' => 'application/vnd.ms-excel', 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', 'csv' => 'text/csv', 'html' => 'text/html', 'pdf' => 'application/pdf', ]; public function __construct(string $folder, string $filename, ?string $filetype = null) { if ((is_dir($folder) === false) || (is_readable($folder) === false)) { throw new Exception('Folder is not accessible'); } $filepath = "{$folder}/{$filename}"; $this->filepath = (string) realpath($filepath); $this->filename = basename($filepath); clearstatcache(); if ((is_file($this->filepath) === false) || (is_readable($this->filepath) === false)) { throw new Exception('File not found, or not a regular file, or cannot be read'); } $filetype ??= pathinfo($filename, PATHINFO_EXTENSION); if (array_key_exists(strtolower($filetype), self::CONTENT_TYPES) === false) { throw new Exception('Invalid filetype: file cannot be downloaded'); } $this->filetype = strtolower($filetype); } public function download(): void { $this->headers(); readfile($this->filepath); } public function headers(): void { // I cannot tell what this ob_clean is paired with. // I have never seen a problem with it, but someone has - issue 3739. // Perhaps it should be removed altogether, // but making it conditional seems harmless, and safer. if ((int) ob_get_length() > 0) { ob_clean(); } $this->contentType(); $this->contentDisposition(); $this->cacheHeaders(); $this->fileSize(); flush(); } protected function contentType(): void { header('Content-Type: ' . self::CONTENT_TYPES[$this->filetype]); } protected function contentDisposition(): void { header('Content-Disposition: attachment;filename="' . $this->filename . '"'); } protected function cacheHeaders(): void { header('Cache-Control: max-age=0'); // If you're serving to IE 9, then the following may be needed header('Cache-Control: max-age=1'); // If you're serving to IE over SSL, then the following may be needed header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); // Date in the past header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); // always modified header('Cache-Control: cache, must-revalidate'); // HTTP/1.1 header('Pragma: public'); // HTTP/1.0 } protected function fileSize(): void { header('Content-Length: ' . filesize($this->filepath)); } } PK!XZZ/libraries/vendor/PhpSpreadsheet/Helper/Html.phpnu[ 'f0f8ff', 'antiquewhite' => 'faebd7', 'antiquewhite1' => 'ffefdb', 'antiquewhite2' => 'eedfcc', 'antiquewhite3' => 'cdc0b0', 'antiquewhite4' => '8b8378', 'aqua' => '00ffff', 'aquamarine1' => '7fffd4', 'aquamarine2' => '76eec6', 'aquamarine4' => '458b74', 'azure1' => 'f0ffff', 'azure2' => 'e0eeee', 'azure3' => 'c1cdcd', 'azure4' => '838b8b', 'beige' => 'f5f5dc', 'bisque1' => 'ffe4c4', 'bisque2' => 'eed5b7', 'bisque3' => 'cdb79e', 'bisque4' => '8b7d6b', 'black' => '000000', 'blanchedalmond' => 'ffebcd', 'blue' => '0000ff', 'blue1' => '0000ff', 'blue2' => '0000ee', 'blue4' => '00008b', 'blueviolet' => '8a2be2', 'brown' => 'a52a2a', 'brown1' => 'ff4040', 'brown2' => 'ee3b3b', 'brown3' => 'cd3333', 'brown4' => '8b2323', 'burlywood' => 'deb887', 'burlywood1' => 'ffd39b', 'burlywood2' => 'eec591', 'burlywood3' => 'cdaa7d', 'burlywood4' => '8b7355', 'cadetblue' => '5f9ea0', 'cadetblue1' => '98f5ff', 'cadetblue2' => '8ee5ee', 'cadetblue3' => '7ac5cd', 'cadetblue4' => '53868b', 'chartreuse1' => '7fff00', 'chartreuse2' => '76ee00', 'chartreuse3' => '66cd00', 'chartreuse4' => '458b00', 'chocolate' => 'd2691e', 'chocolate1' => 'ff7f24', 'chocolate2' => 'ee7621', 'chocolate3' => 'cd661d', 'coral' => 'ff7f50', 'coral1' => 'ff7256', 'coral2' => 'ee6a50', 'coral3' => 'cd5b45', 'coral4' => '8b3e2f', 'cornflowerblue' => '6495ed', 'cornsilk1' => 'fff8dc', 'cornsilk2' => 'eee8cd', 'cornsilk3' => 'cdc8b1', 'cornsilk4' => '8b8878', 'cyan1' => '00ffff', 'cyan2' => '00eeee', 'cyan3' => '00cdcd', 'cyan4' => '008b8b', 'darkgoldenrod' => 'b8860b', 'darkgoldenrod1' => 'ffb90f', 'darkgoldenrod2' => 'eead0e', 'darkgoldenrod3' => 'cd950c', 'darkgoldenrod4' => '8b6508', 'darkgreen' => '006400', 'darkkhaki' => 'bdb76b', 'darkolivegreen' => '556b2f', 'darkolivegreen1' => 'caff70', 'darkolivegreen2' => 'bcee68', 'darkolivegreen3' => 'a2cd5a', 'darkolivegreen4' => '6e8b3d', 'darkorange' => 'ff8c00', 'darkorange1' => 'ff7f00', 'darkorange2' => 'ee7600', 'darkorange3' => 'cd6600', 'darkorange4' => '8b4500', 'darkorchid' => '9932cc', 'darkorchid1' => 'bf3eff', 'darkorchid2' => 'b23aee', 'darkorchid3' => '9a32cd', 'darkorchid4' => '68228b', 'darksalmon' => 'e9967a', 'darkseagreen' => '8fbc8f', 'darkseagreen1' => 'c1ffc1', 'darkseagreen2' => 'b4eeb4', 'darkseagreen3' => '9bcd9b', 'darkseagreen4' => '698b69', 'darkslateblue' => '483d8b', 'darkslategray' => '2f4f4f', 'darkslategray1' => '97ffff', 'darkslategray2' => '8deeee', 'darkslategray3' => '79cdcd', 'darkslategray4' => '528b8b', 'darkturquoise' => '00ced1', 'darkviolet' => '9400d3', 'deeppink1' => 'ff1493', 'deeppink2' => 'ee1289', 'deeppink3' => 'cd1076', 'deeppink4' => '8b0a50', 'deepskyblue1' => '00bfff', 'deepskyblue2' => '00b2ee', 'deepskyblue3' => '009acd', 'deepskyblue4' => '00688b', 'dimgray' => '696969', 'dodgerblue1' => '1e90ff', 'dodgerblue2' => '1c86ee', 'dodgerblue3' => '1874cd', 'dodgerblue4' => '104e8b', 'firebrick' => 'b22222', 'firebrick1' => 'ff3030', 'firebrick2' => 'ee2c2c', 'firebrick3' => 'cd2626', 'firebrick4' => '8b1a1a', 'floralwhite' => 'fffaf0', 'forestgreen' => '228b22', 'fuchsia' => 'ff00ff', 'gainsboro' => 'dcdcdc', 'ghostwhite' => 'f8f8ff', 'gold1' => 'ffd700', 'gold2' => 'eec900', 'gold3' => 'cdad00', 'gold4' => '8b7500', 'goldenrod' => 'daa520', 'goldenrod1' => 'ffc125', 'goldenrod2' => 'eeb422', 'goldenrod3' => 'cd9b1d', 'goldenrod4' => '8b6914', 'gray' => 'bebebe', 'gray1' => '030303', 'gray10' => '1a1a1a', 'gray11' => '1c1c1c', 'gray12' => '1f1f1f', 'gray13' => '212121', 'gray14' => '242424', 'gray15' => '262626', 'gray16' => '292929', 'gray17' => '2b2b2b', 'gray18' => '2e2e2e', 'gray19' => '303030', 'gray2' => '050505', 'gray20' => '333333', 'gray21' => '363636', 'gray22' => '383838', 'gray23' => '3b3b3b', 'gray24' => '3d3d3d', 'gray25' => '404040', 'gray26' => '424242', 'gray27' => '454545', 'gray28' => '474747', 'gray29' => '4a4a4a', 'gray3' => '080808', 'gray30' => '4d4d4d', 'gray31' => '4f4f4f', 'gray32' => '525252', 'gray33' => '545454', 'gray34' => '575757', 'gray35' => '595959', 'gray36' => '5c5c5c', 'gray37' => '5e5e5e', 'gray38' => '616161', 'gray39' => '636363', 'gray4' => '0a0a0a', 'gray40' => '666666', 'gray41' => '696969', 'gray42' => '6b6b6b', 'gray43' => '6e6e6e', 'gray44' => '707070', 'gray45' => '737373', 'gray46' => '757575', 'gray47' => '787878', 'gray48' => '7a7a7a', 'gray49' => '7d7d7d', 'gray5' => '0d0d0d', 'gray50' => '7f7f7f', 'gray51' => '828282', 'gray52' => '858585', 'gray53' => '878787', 'gray54' => '8a8a8a', 'gray55' => '8c8c8c', 'gray56' => '8f8f8f', 'gray57' => '919191', 'gray58' => '949494', 'gray59' => '969696', 'gray6' => '0f0f0f', 'gray60' => '999999', 'gray61' => '9c9c9c', 'gray62' => '9e9e9e', 'gray63' => 'a1a1a1', 'gray64' => 'a3a3a3', 'gray65' => 'a6a6a6', 'gray66' => 'a8a8a8', 'gray67' => 'ababab', 'gray68' => 'adadad', 'gray69' => 'b0b0b0', 'gray7' => '121212', 'gray70' => 'b3b3b3', 'gray71' => 'b5b5b5', 'gray72' => 'b8b8b8', 'gray73' => 'bababa', 'gray74' => 'bdbdbd', 'gray75' => 'bfbfbf', 'gray76' => 'c2c2c2', 'gray77' => 'c4c4c4', 'gray78' => 'c7c7c7', 'gray79' => 'c9c9c9', 'gray8' => '141414', 'gray80' => 'cccccc', 'gray81' => 'cfcfcf', 'gray82' => 'd1d1d1', 'gray83' => 'd4d4d4', 'gray84' => 'd6d6d6', 'gray85' => 'd9d9d9', 'gray86' => 'dbdbdb', 'gray87' => 'dedede', 'gray88' => 'e0e0e0', 'gray89' => 'e3e3e3', 'gray9' => '171717', 'gray90' => 'e5e5e5', 'gray91' => 'e8e8e8', 'gray92' => 'ebebeb', 'gray93' => 'ededed', 'gray94' => 'f0f0f0', 'gray95' => 'f2f2f2', 'gray97' => 'f7f7f7', 'gray98' => 'fafafa', 'gray99' => 'fcfcfc', 'green' => '00ff00', 'green1' => '00ff00', 'green2' => '00ee00', 'green3' => '00cd00', 'green4' => '008b00', 'greenyellow' => 'adff2f', 'honeydew1' => 'f0fff0', 'honeydew2' => 'e0eee0', 'honeydew3' => 'c1cdc1', 'honeydew4' => '838b83', 'hotpink' => 'ff69b4', 'hotpink1' => 'ff6eb4', 'hotpink2' => 'ee6aa7', 'hotpink3' => 'cd6090', 'hotpink4' => '8b3a62', 'indianred' => 'cd5c5c', 'indianred1' => 'ff6a6a', 'indianred2' => 'ee6363', 'indianred3' => 'cd5555', 'indianred4' => '8b3a3a', 'ivory1' => 'fffff0', 'ivory2' => 'eeeee0', 'ivory3' => 'cdcdc1', 'ivory4' => '8b8b83', 'khaki' => 'f0e68c', 'khaki1' => 'fff68f', 'khaki2' => 'eee685', 'khaki3' => 'cdc673', 'khaki4' => '8b864e', 'lavender' => 'e6e6fa', 'lavenderblush1' => 'fff0f5', 'lavenderblush2' => 'eee0e5', 'lavenderblush3' => 'cdc1c5', 'lavenderblush4' => '8b8386', 'lawngreen' => '7cfc00', 'lemonchiffon1' => 'fffacd', 'lemonchiffon2' => 'eee9bf', 'lemonchiffon3' => 'cdc9a5', 'lemonchiffon4' => '8b8970', 'light' => 'eedd82', 'lightblue' => 'add8e6', 'lightblue1' => 'bfefff', 'lightblue2' => 'b2dfee', 'lightblue3' => '9ac0cd', 'lightblue4' => '68838b', 'lightcoral' => 'f08080', 'lightcyan1' => 'e0ffff', 'lightcyan2' => 'd1eeee', 'lightcyan3' => 'b4cdcd', 'lightcyan4' => '7a8b8b', 'lightgoldenrod1' => 'ffec8b', 'lightgoldenrod2' => 'eedc82', 'lightgoldenrod3' => 'cdbe70', 'lightgoldenrod4' => '8b814c', 'lightgoldenrodyellow' => 'fafad2', 'lightgray' => 'd3d3d3', 'lightpink' => 'ffb6c1', 'lightpink1' => 'ffaeb9', 'lightpink2' => 'eea2ad', 'lightpink3' => 'cd8c95', 'lightpink4' => '8b5f65', 'lightsalmon1' => 'ffa07a', 'lightsalmon2' => 'ee9572', 'lightsalmon3' => 'cd8162', 'lightsalmon4' => '8b5742', 'lightseagreen' => '20b2aa', 'lightskyblue' => '87cefa', 'lightskyblue1' => 'b0e2ff', 'lightskyblue2' => 'a4d3ee', 'lightskyblue3' => '8db6cd', 'lightskyblue4' => '607b8b', 'lightslateblue' => '8470ff', 'lightslategray' => '778899', 'lightsteelblue' => 'b0c4de', 'lightsteelblue1' => 'cae1ff', 'lightsteelblue2' => 'bcd2ee', 'lightsteelblue3' => 'a2b5cd', 'lightsteelblue4' => '6e7b8b', 'lightyellow1' => 'ffffe0', 'lightyellow2' => 'eeeed1', 'lightyellow3' => 'cdcdb4', 'lightyellow4' => '8b8b7a', 'lime' => '00ff00', 'limegreen' => '32cd32', 'linen' => 'faf0e6', 'magenta' => 'ff00ff', 'magenta2' => 'ee00ee', 'magenta3' => 'cd00cd', 'magenta4' => '8b008b', 'maroon' => 'b03060', 'maroon1' => 'ff34b3', 'maroon2' => 'ee30a7', 'maroon3' => 'cd2990', 'maroon4' => '8b1c62', 'medium' => '66cdaa', 'mediumaquamarine' => '66cdaa', 'mediumblue' => '0000cd', 'mediumorchid' => 'ba55d3', 'mediumorchid1' => 'e066ff', 'mediumorchid2' => 'd15fee', 'mediumorchid3' => 'b452cd', 'mediumorchid4' => '7a378b', 'mediumpurple' => '9370db', 'mediumpurple1' => 'ab82ff', 'mediumpurple2' => '9f79ee', 'mediumpurple3' => '8968cd', 'mediumpurple4' => '5d478b', 'mediumseagreen' => '3cb371', 'mediumslateblue' => '7b68ee', 'mediumspringgreen' => '00fa9a', 'mediumturquoise' => '48d1cc', 'mediumvioletred' => 'c71585', 'midnightblue' => '191970', 'mintcream' => 'f5fffa', 'mistyrose1' => 'ffe4e1', 'mistyrose2' => 'eed5d2', 'mistyrose3' => 'cdb7b5', 'mistyrose4' => '8b7d7b', 'moccasin' => 'ffe4b5', 'navajowhite1' => 'ffdead', 'navajowhite2' => 'eecfa1', 'navajowhite3' => 'cdb38b', 'navajowhite4' => '8b795e', 'navy' => '000080', 'navyblue' => '000080', 'oldlace' => 'fdf5e6', 'olive' => '808000', 'olivedrab' => '6b8e23', 'olivedrab1' => 'c0ff3e', 'olivedrab2' => 'b3ee3a', 'olivedrab4' => '698b22', 'orange' => 'ffa500', 'orange1' => 'ffa500', 'orange2' => 'ee9a00', 'orange3' => 'cd8500', 'orange4' => '8b5a00', 'orangered1' => 'ff4500', 'orangered2' => 'ee4000', 'orangered3' => 'cd3700', 'orangered4' => '8b2500', 'orchid' => 'da70d6', 'orchid1' => 'ff83fa', 'orchid2' => 'ee7ae9', 'orchid3' => 'cd69c9', 'orchid4' => '8b4789', 'pale' => 'db7093', 'palegoldenrod' => 'eee8aa', 'palegreen' => '98fb98', 'palegreen1' => '9aff9a', 'palegreen2' => '90ee90', 'palegreen3' => '7ccd7c', 'palegreen4' => '548b54', 'paleturquoise' => 'afeeee', 'paleturquoise1' => 'bbffff', 'paleturquoise2' => 'aeeeee', 'paleturquoise3' => '96cdcd', 'paleturquoise4' => '668b8b', 'palevioletred' => 'db7093', 'palevioletred1' => 'ff82ab', 'palevioletred2' => 'ee799f', 'palevioletred3' => 'cd6889', 'palevioletred4' => '8b475d', 'papayawhip' => 'ffefd5', 'peachpuff1' => 'ffdab9', 'peachpuff2' => 'eecbad', 'peachpuff3' => 'cdaf95', 'peachpuff4' => '8b7765', 'pink' => 'ffc0cb', 'pink1' => 'ffb5c5', 'pink2' => 'eea9b8', 'pink3' => 'cd919e', 'pink4' => '8b636c', 'plum' => 'dda0dd', 'plum1' => 'ffbbff', 'plum2' => 'eeaeee', 'plum3' => 'cd96cd', 'plum4' => '8b668b', 'powderblue' => 'b0e0e6', 'purple' => 'a020f0', 'rebeccapurple' => '663399', 'purple1' => '9b30ff', 'purple2' => '912cee', 'purple3' => '7d26cd', 'purple4' => '551a8b', 'red' => 'ff0000', 'red1' => 'ff0000', 'red2' => 'ee0000', 'red3' => 'cd0000', 'red4' => '8b0000', 'rosybrown' => 'bc8f8f', 'rosybrown1' => 'ffc1c1', 'rosybrown2' => 'eeb4b4', 'rosybrown3' => 'cd9b9b', 'rosybrown4' => '8b6969', 'royalblue' => '4169e1', 'royalblue1' => '4876ff', 'royalblue2' => '436eee', 'royalblue3' => '3a5fcd', 'royalblue4' => '27408b', 'saddlebrown' => '8b4513', 'salmon' => 'fa8072', 'salmon1' => 'ff8c69', 'salmon2' => 'ee8262', 'salmon3' => 'cd7054', 'salmon4' => '8b4c39', 'sandybrown' => 'f4a460', 'seagreen1' => '54ff9f', 'seagreen2' => '4eee94', 'seagreen3' => '43cd80', 'seagreen4' => '2e8b57', 'seashell1' => 'fff5ee', 'seashell2' => 'eee5de', 'seashell3' => 'cdc5bf', 'seashell4' => '8b8682', 'sienna' => 'a0522d', 'sienna1' => 'ff8247', 'sienna2' => 'ee7942', 'sienna3' => 'cd6839', 'sienna4' => '8b4726', 'silver' => 'c0c0c0', 'skyblue' => '87ceeb', 'skyblue1' => '87ceff', 'skyblue2' => '7ec0ee', 'skyblue3' => '6ca6cd', 'skyblue4' => '4a708b', 'slateblue' => '6a5acd', 'slateblue1' => '836fff', 'slateblue2' => '7a67ee', 'slateblue3' => '6959cd', 'slateblue4' => '473c8b', 'slategray' => '708090', 'slategray1' => 'c6e2ff', 'slategray2' => 'b9d3ee', 'slategray3' => '9fb6cd', 'slategray4' => '6c7b8b', 'snow1' => 'fffafa', 'snow2' => 'eee9e9', 'snow3' => 'cdc9c9', 'snow4' => '8b8989', 'springgreen1' => '00ff7f', 'springgreen2' => '00ee76', 'springgreen3' => '00cd66', 'springgreen4' => '008b45', 'steelblue' => '4682b4', 'steelblue1' => '63b8ff', 'steelblue2' => '5cacee', 'steelblue3' => '4f94cd', 'steelblue4' => '36648b', 'tan' => 'd2b48c', 'tan1' => 'ffa54f', 'tan2' => 'ee9a49', 'tan3' => 'cd853f', 'tan4' => '8b5a2b', 'teal' => '008080', 'thistle' => 'd8bfd8', 'thistle1' => 'ffe1ff', 'thistle2' => 'eed2ee', 'thistle3' => 'cdb5cd', 'thistle4' => '8b7b8b', 'tomato1' => 'ff6347', 'tomato2' => 'ee5c42', 'tomato3' => 'cd4f39', 'tomato4' => '8b3626', 'turquoise' => '40e0d0', 'turquoise1' => '00f5ff', 'turquoise2' => '00e5ee', 'turquoise3' => '00c5cd', 'turquoise4' => '00868b', 'violet' => 'ee82ee', 'violetred' => 'd02090', 'violetred1' => 'ff3e96', 'violetred2' => 'ee3a8c', 'violetred3' => 'cd3278', 'violetred4' => '8b2252', 'wheat' => 'f5deb3', 'wheat1' => 'ffe7ba', 'wheat2' => 'eed8ae', 'wheat3' => 'cdba96', 'wheat4' => '8b7e66', 'white' => 'ffffff', 'whitesmoke' => 'f5f5f5', 'yellow' => 'ffff00', 'yellow1' => 'ffff00', 'yellow2' => 'eeee00', 'yellow3' => 'cdcd00', 'yellow4' => '8b8b00', 'yellowgreen' => '9acd32', ]; private ?string $face = null; private ?string $size = null; private ?string $color = null; private bool $bold = false; private bool $italic = false; private bool $underline = false; private bool $superscript = false; private bool $subscript = false; private bool $strikethrough = false; /** @var callable[] */ protected array $startTagCallbacks; /** @var callable[] */ protected array $endTagCallbacks; private array $stack = []; public string $stringData = ''; private RichText $richTextObject; private bool $preserveWhiteSpace = false; public function __construct() { if (!isset($this->startTagCallbacks)) { $this->startTagCallbacks = [ 'font' => \Closure::fromCallable([$this, 'startFontTag']), 'b' => \Closure::fromCallable([$this, 'startBoldTag']), 'strong' => \Closure::fromCallable([$this, 'startBoldTag']), 'i' => \Closure::fromCallable([$this, 'startItalicTag']), 'em' => \Closure::fromCallable([$this, 'startItalicTag']), 'u' => \Closure::fromCallable([$this, 'startUnderlineTag']), 'ins' => \Closure::fromCallable([$this, 'startUnderlineTag']), 'del' => \Closure::fromCallable([$this, 'startStrikethruTag']), 's' => \Closure::fromCallable([$this, 'startStrikethruTag']), 'sup' => \Closure::fromCallable([$this, 'startSuperscriptTag']), 'sub' => \Closure::fromCallable([$this, 'startSubscriptTag']), ]; } if (!isset($this->endTagCallbacks)) { $this->endTagCallbacks = [ 'font' => \Closure::fromCallable([$this, 'endFontTag']), 'b' => \Closure::fromCallable([$this, 'endBoldTag']), 'strong' => \Closure::fromCallable([$this, 'endBoldTag']), 'i' => \Closure::fromCallable([$this, 'endItalicTag']), 'em' => \Closure::fromCallable([$this, 'endItalicTag']), 'u' => \Closure::fromCallable([$this, 'endUnderlineTag']), 'ins' => \Closure::fromCallable([$this, 'endUnderlineTag']), 'del' => \Closure::fromCallable([$this, 'endStrikethruTag']), 's' => \Closure::fromCallable([$this, 'endStrikethruTag']), 'sup' => \Closure::fromCallable([$this, 'endSuperscriptTag']), 'sub' => \Closure::fromCallable([$this, 'endSubscriptTag']), 'br' => \Closure::fromCallable([$this, 'breakTag']), 'p' => \Closure::fromCallable([$this, 'breakTag']), 'h1' => \Closure::fromCallable([$this, 'breakTag']), 'h2' => \Closure::fromCallable([$this, 'breakTag']), 'h3' => \Closure::fromCallable([$this, 'breakTag']), 'h4' => \Closure::fromCallable([$this, 'breakTag']), 'h5' => \Closure::fromCallable([$this, 'breakTag']), 'h6' => \Closure::fromCallable([$this, 'breakTag']), ]; } } private function initialise(): void { $this->face = $this->size = $this->color = null; $this->bold = $this->italic = $this->underline = $this->superscript = $this->subscript = $this->strikethrough = false; $this->stack = []; $this->stringData = ''; } /** * Parse HTML formatting and return the resulting RichText. */ public function toRichTextObject(string $html, bool $preserveWhiteSpace = false): RichText { $this->initialise(); // Create a new DOM object $dom = new DOMDocument(); // Load the HTML file into the DOM object // Note the use of error suppression, because typically this will be an html fragment, so not fully valid markup $prefix = ''; @$dom->loadHTML($prefix . $html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); // Discard excess white space $dom->preserveWhiteSpace = false; $this->richTextObject = new RichText(); $this->preserveWhiteSpace = $preserveWhiteSpace; $this->parseElements($dom); $this->preserveWhiteSpace = false; // Clean any further spurious whitespace $this->cleanWhitespace(); return $this->richTextObject; } private function cleanWhitespace(): void { foreach ($this->richTextObject->getRichTextElements() as $key => $element) { $text = $element->getText(); // Trim any leading spaces on the first run if ($key == 0) { $text = ltrim($text); } // Trim any spaces immediately after a line break $text = (string) preg_replace('/\n */mu', "\n", $text); $element->setText($text); } } private function buildTextRun(): void { $text = $this->stringData; if (trim($text) === '') { return; } $richtextRun = $this->richTextObject->createTextRun($this->stringData); $font = $richtextRun->getFont(); if ($font !== null) { if ($this->face) { $font->setName($this->face); } if ($this->size) { $font->setSize($this->size); } if ($this->color) { $font->setColor(new Color('ff' . $this->color)); } if ($this->bold) { $font->setBold(true); } if ($this->italic) { $font->setItalic(true); } if ($this->underline) { $font->setUnderline(Font::UNDERLINE_SINGLE); } if ($this->superscript) { $font->setSuperscript(true); } if ($this->subscript) { $font->setSubscript(true); } if ($this->strikethrough) { $font->setStrikethrough(true); } } $this->stringData = ''; } private function rgbToColour(string $rgbValue): string { preg_match_all('/\d+/', $rgbValue, $values); foreach ($values[0] as &$value) { $value = str_pad(dechex((int) $value), 2, '0', STR_PAD_LEFT); } return implode('', $values[0]); } public static function colourNameLookup(string $colorName): string { return static::COLOUR_MAP[$colorName] ?? ''; } protected function startFontTag(DOMElement $tag): void { $attrs = $tag->attributes; /** @var DOMAttr $attribute */ foreach ($attrs as $attribute) { $attributeName = strtolower($attribute->name); $attributeName = preg_replace('/^html:/', '', $attributeName) ?? $attributeName; // in case from Xml spreadsheet $attributeValue = $attribute->value; if ($attributeName === 'color') { if (preg_match('/rgb\s*\(/', $attributeValue)) { $this->$attributeName = $this->rgbToColour($attributeValue); } elseif (str_starts_with(trim($attributeValue), '#')) { $this->$attributeName = ltrim($attributeValue, '#'); } else { $this->$attributeName = static::colourNameLookup($attributeValue); } } elseif ($attributeName === 'face' || $attributeName === 'size') { $this->$attributeName = $attributeValue; } } } protected function endFontTag(): void { $this->face = $this->size = $this->color = null; } protected function startBoldTag(): void { $this->bold = true; } protected function endBoldTag(): void { $this->bold = false; } protected function startItalicTag(): void { $this->italic = true; } protected function endItalicTag(): void { $this->italic = false; } protected function startUnderlineTag(): void { $this->underline = true; } protected function endUnderlineTag(): void { $this->underline = false; } protected function startSubscriptTag(): void { $this->subscript = true; } protected function endSubscriptTag(): void { $this->subscript = false; } protected function startSuperscriptTag(): void { $this->superscript = true; } protected function endSuperscriptTag(): void { $this->superscript = false; } protected function startStrikethruTag(): void { $this->strikethrough = true; } protected function endStrikethruTag(): void { $this->strikethrough = false; } public function breakTag(): void { $this->stringData .= "\n"; } private function parseTextNode(DOMText $textNode): void { if ($this->preserveWhiteSpace) { $domText = $textNode->nodeValue ?? ''; } else { $domText = (string) preg_replace( '/\s+/u', ' ', str_replace(["\r", "\n"], ' ', $textNode->nodeValue ?? '') ); } $this->stringData .= $domText; $this->buildTextRun(); } public function addStartTagCallback(string $tag, callable $callback): void { $this->startTagCallbacks[$tag] = $callback; } public function addEndTagCallback(string $tag, callable $callback): void { $this->endTagCallbacks[$tag] = $callback; } /** @param callable[] $callbacks */ private function handleCallback(DOMElement $element, string $callbackTag, array $callbacks): void { if (isset($callbacks[$callbackTag])) { $elementHandler = $callbacks[$callbackTag]; call_user_func($elementHandler, $element, $this); } } private function parseElementNode(DOMElement $element): void { $callbackTag = strtolower($element->nodeName); $this->stack[] = $callbackTag; $this->handleCallback($element, $callbackTag, $this->startTagCallbacks); $this->parseElements($element); array_pop($this->stack); $this->handleCallback($element, $callbackTag, $this->endTagCallbacks); } private function parseElements(DOMNode $element): void { foreach ($element->childNodes as $child) { if ($child instanceof DOMText) { $this->parseTextNode($child); } elseif ($child instanceof DOMElement) { $this->parseElementNode($child); } } } } PK!WY;libraries/vendor/PhpSpreadsheet/Collection/CellsFactory.phpnu[parent = $parent; $this->cache = $cache; $this->cachePrefix = $this->getUniqueID(); } /** * Return the parent worksheet for this cell collection. */ public function getParent(): ?Worksheet { return $this->parent; } /** * Whether the collection holds a cell for the given coordinate. * * @param string $cellCoordinate Coordinate of the cell to check */ public function has(string $cellCoordinate): bool { return ($cellCoordinate === $this->currentCoordinate) || isset($this->index[$cellCoordinate]); } public function has2(string $cellCoordinate): bool { return isset($this->index[$cellCoordinate]); } /** * Add or update a cell in the collection. * * @param Cell $cell Cell to update */ public function update(Cell $cell): Cell { return $this->add($cell->getCoordinate(), $cell); } /** * Delete a cell in cache identified by coordinate. * * @param string $cellCoordinate Coordinate of the cell to delete */ public function delete(string $cellCoordinate): void { if ($cellCoordinate === $this->currentCoordinate && $this->currentCell !== null) { $this->currentCell->detach(); $this->currentCoordinate = null; $this->currentCell = null; $this->currentCellIsDirty = false; } unset($this->index[$cellCoordinate]); // Delete the entry from cache $this->cache->delete($this->cachePrefix . $cellCoordinate); } /** * Get a list of all cell coordinates currently held in the collection. * * @return string[] */ public function getCoordinates(): array { return array_keys($this->index); } /** * Get a sorted list of all cell coordinates currently held in the collection by row and column. * * @return string[] */ public function getSortedCoordinates(): array { asort($this->index); return array_keys($this->index); } /** * Get a sorted list of all cell coordinates currently held in the collection by index (16384*row+column). * * @return int[] */ public function getSortedCoordinatesInt(): array { asort($this->index); return array_values($this->index); } /** * Return the cell coordinate of the currently active cell object. */ public function getCurrentCoordinate(): ?string { return $this->currentCoordinate; } /** * Return the column coordinate of the currently active cell object. */ public function getCurrentColumn(): string { $column = 0; $row = ''; sscanf($this->currentCoordinate ?? '', '%[A-Z]%d', $column, $row); return (string) $column; } /** * Return the row coordinate of the currently active cell object. */ public function getCurrentRow(): int { $column = 0; $row = ''; sscanf($this->currentCoordinate ?? '', '%[A-Z]%d', $column, $row); return (int) $row; } /** * Get highest worksheet column and highest row that have cell records. * * @return array Highest column name and highest row number */ public function getHighestRowAndColumn(): array { // Lookup highest column and highest row $maxRow = $maxColumn = 1; foreach ($this->index as $coordinate) { $row = (int) floor(($coordinate - 1) / self::MAX_COLUMN_ID) + 1; $maxRow = ($maxRow > $row) ? $maxRow : $row; $column = ($coordinate % self::MAX_COLUMN_ID) ?: self::MAX_COLUMN_ID; $maxColumn = ($maxColumn > $column) ? $maxColumn : $column; } return [ 'row' => $maxRow, 'column' => Coordinate::stringFromColumnIndex($maxColumn), ]; } /** * Get highest worksheet column. * * @param null|int|string $row Return the highest column for the specified row, * or the highest column of any row if no row number is passed * * @return string Highest column name */ public function getHighestColumn($row = null): string { if ($row === null) { return $this->getHighestRowAndColumn()['column']; } $row = (int) $row; if ($row <= 0) { throw new PhpSpreadsheetException('Row number must be a positive integer'); } $maxColumn = 1; $toRow = $row * self::MAX_COLUMN_ID; $fromRow = --$row * self::MAX_COLUMN_ID; foreach ($this->index as $coordinate) { if ($coordinate < $fromRow || $coordinate >= $toRow) { continue; } $column = ($coordinate % self::MAX_COLUMN_ID) ?: self::MAX_COLUMN_ID; $maxColumn = $maxColumn > $column ? $maxColumn : $column; } return Coordinate::stringFromColumnIndex($maxColumn); } /** * Get highest worksheet row. * * @param null|string $column Return the highest row for the specified column, * or the highest row of any column if no column letter is passed * * @return int Highest row number */ public function getHighestRow(?string $column = null): int { if ($column === null) { return $this->getHighestRowAndColumn()['row']; } $maxRow = 1; $columnIndex = Coordinate::columnIndexFromString($column); foreach ($this->index as $coordinate) { if ($coordinate % self::MAX_COLUMN_ID !== $columnIndex) { continue; } $row = (int) floor($coordinate / self::MAX_COLUMN_ID) + 1; $maxRow = ($maxRow > $row) ? $maxRow : $row; } return $maxRow; } /** * Generate a unique ID for cache referencing. * * @return string Unique Reference */ private function getUniqueID(): string { $cacheType = Settings::getCache(); return ($cacheType instanceof Memory\SimpleCache1 || $cacheType instanceof Memory\SimpleCache3) ? random_bytes(7) . ':' : uniqid('phpspreadsheet.', true) . '.'; } /** * Clone the cell collection. * @return static */ public function cloneCellCollection(Worksheet $worksheet) { $this->storeCurrentCell(); $newCollection = clone $this; $newCollection->parent = $worksheet; $newCollection->cachePrefix = $newCollection->getUniqueID(); foreach ($this->index as $key => $value) { $newCollection->index[$key] = $value; $stored = $newCollection->cache->set( $newCollection->cachePrefix . $key, clone $this->getCache($key) ); if ($stored === false) { $this->destructIfNeeded($newCollection, 'Failed to copy cells in cache'); } } return $newCollection; } /** * Remove a row, deleting all cells in that row. * * @param int|string $row Row number to remove */ public function removeRow($row): void { $this->storeCurrentCell(); $row = (int) $row; if ($row <= 0) { throw new PhpSpreadsheetException('Row number must be a positive integer'); } $toRow = $row * self::MAX_COLUMN_ID; $fromRow = --$row * self::MAX_COLUMN_ID; foreach ($this->index as $coordinate) { if ($coordinate >= $fromRow && $coordinate < $toRow) { $row = (int) floor($coordinate / self::MAX_COLUMN_ID) + 1; $column = Coordinate::stringFromColumnIndex($coordinate % self::MAX_COLUMN_ID); $this->delete("{$column}{$row}"); } } } /** * Remove a column, deleting all cells in that column. * * @param string $column Column ID to remove */ public function removeColumn(string $column): void { $this->storeCurrentCell(); $columnIndex = Coordinate::columnIndexFromString($column); foreach ($this->index as $coordinate) { if ($coordinate % self::MAX_COLUMN_ID === $columnIndex) { $row = (int) floor($coordinate / self::MAX_COLUMN_ID) + 1; $column = Coordinate::stringFromColumnIndex($coordinate % self::MAX_COLUMN_ID); $this->delete("{$column}{$row}"); } } } /** * Store cell data in cache for the current cell object if it's "dirty", * and the 'nullify' the current cell object. */ private function storeCurrentCell(): void { if ($this->currentCellIsDirty && isset($this->currentCoordinate, $this->currentCell)) { $this->currentCell->detach(); $stored = $this->cache->set($this->cachePrefix . $this->currentCoordinate, $this->currentCell); if ($stored === false) { $this->destructIfNeeded($this, "Failed to store cell {$this->currentCoordinate} in cache"); } $this->currentCellIsDirty = false; } $this->currentCoordinate = null; $this->currentCell = null; } private function destructIfNeeded(self $cells, string $message): void { $cells->__destruct(); throw new PhpSpreadsheetException($message); } /** * Add or update a cell identified by its coordinate into the collection. * * @param string $cellCoordinate Coordinate of the cell to update * @param Cell $cell Cell to update */ public function add(string $cellCoordinate, Cell $cell): Cell { if ($cellCoordinate !== $this->currentCoordinate) { $this->storeCurrentCell(); } $column = 0; $row = ''; sscanf($cellCoordinate, '%[A-Z]%d', $column, $row); $this->index[$cellCoordinate] = (--$row * self::MAX_COLUMN_ID) + Coordinate::columnIndexFromString((string) $column); $this->currentCoordinate = $cellCoordinate; $this->currentCell = $cell; $this->currentCellIsDirty = true; return $cell; } /** * Get cell at a specific coordinate. * * @param string $cellCoordinate Coordinate of the cell * * @return null|Cell Cell that was found, or null if not found */ public function get(string $cellCoordinate): ?Cell { if ($cellCoordinate === $this->currentCoordinate) { return $this->currentCell; } $this->storeCurrentCell(); // Return null if requested entry doesn't exist in collection if ($this->has($cellCoordinate) === false) { return null; } $cell = $this->getcache($cellCoordinate); // Set current entry to the requested entry $this->currentCoordinate = $cellCoordinate; $this->currentCell = $cell; // Re-attach this as the cell's parent $this->currentCell->attach($this); // Return requested entry return $this->currentCell; } /** * Clear the cell collection and disconnect from our parent. */ public function unsetWorksheetCells(): void { if ($this->currentCell !== null) { $this->currentCell->detach(); $this->currentCell = null; $this->currentCoordinate = null; } // Flush the cache $this->__destruct(); $this->index = []; // detach ourself from the worksheet, so that it can then delete this object successfully $this->parent = null; } /** * Destroy this cell collection. */ public function __destruct() { $this->cache->deleteMultiple($this->getAllCacheKeys()); $this->parent = null; } /** * Returns all known cache keys. * * @return iterable */ private function getAllCacheKeys(): iterable { foreach ($this->index as $coordinate => $value) { yield $this->cachePrefix . $coordinate; } } private function getCache(string $cellCoordinate): Cell { $cell = $this->cache->get($this->cachePrefix . $cellCoordinate); if (!($cell instanceof Cell)) { throw new PhpSpreadsheetException("Cell entry {$cellCoordinate} no longer exists in cache. This probably means that the cache was cleared by someone else."); } return $cell; } } PK!p6Blibraries/vendor/PhpSpreadsheet/Collection/Memory/SimpleCache3.phpnu[cache = []; return true; } public function delete(string $key): bool { unset($this->cache[$key]); return true; } public function deleteMultiple(iterable $keys): bool { foreach ($keys as $key) { $this->delete($key); } return true; } /** * @param mixed $default * @return mixed */ public function get(string $key, $default = null) { if ($this->has($key)) { return $this->cache[$key]; } return $default; } /** * @param mixed $default */ public function getMultiple(iterable $keys, $default = null): iterable { $results = []; foreach ($keys as $key) { $results[$key] = $this->get($key, $default); } return $results; } public function has(string $key): bool { return array_key_exists($key, $this->cache); } /** * @param null|int|\DateInterval $ttl * @param mixed $value */ public function set(string $key, $value, $ttl = null): bool { $this->cache[$key] = $value; return true; } /** * @param null|int|\DateInterval $ttl */ public function setMultiple(iterable $values, $ttl = null): bool { foreach ($values as $key => $value) { $this->set($key, $value); } return true; } } PK!;libraries/vendor/PhpSpreadsheet/Collection/Memory/index.phpnu[cache = []; return true; } public function delete($key): bool { unset($this->cache[$key]); return true; } public function deleteMultiple($keys): bool { foreach ($keys as $key) { $this->delete($key); } return true; } /** * @return mixed */ public function get($key, $default = null) { if ($this->has($key)) { return $this->cache[$key]; } return $default; } public function getMultiple($keys, $default = null): iterable { $results = []; foreach ($keys as $key) { $results[$key] = $this->get($key, $default); } return $results; } public function has($key): bool { return array_key_exists($key, $this->cache); } public function set($key, $value, $ttl = null): bool { $this->cache[$key] = $value; return true; } public function setMultiple($values, $ttl = null): bool { foreach ($values as $key => $value) { $this->set($key, $value); } return true; } } PK!4libraries/vendor/PhpSpreadsheet/Collection/index.phpnu[lockRevision || $this->lockStructure || $this->lockWindows; } public function getLockRevision(): bool { return $this->lockRevision; } public function setLockRevision(?bool $locked): self { if ($locked !== null) { $this->lockRevision = $locked; } return $this; } public function getLockStructure(): bool { return $this->lockStructure; } public function setLockStructure(?bool $locked): self { if ($locked !== null) { $this->lockStructure = $locked; } return $this; } public function getLockWindows(): bool { return $this->lockWindows; } public function setLockWindows(?bool $locked): self { if ($locked !== null) { $this->lockWindows = $locked; } return $this; } public function getRevisionsPassword(): string { return $this->revisionsPassword; } /** * Set RevisionsPassword. * * @param bool $alreadyHashed If the password has already been hashed, set this to true * * @return $this */ public function setRevisionsPassword(?string $password, bool $alreadyHashed = false) { if ($password !== null) { if (!$alreadyHashed) { $password = PasswordHasher::hashPassword($password); } $this->revisionsPassword = $password; } return $this; } public function getWorkbookPassword(): string { return $this->workbookPassword; } /** * Set WorkbookPassword. * * @param bool $alreadyHashed If the password has already been hashed, set this to true * * @return $this */ public function setWorkbookPassword(?string $password, bool $alreadyHashed = false) { if ($password !== null) { if (!$alreadyHashed) { $password = PasswordHasher::hashPassword($password); } $this->workbookPassword = $password; } return $this; } } PK!2libraries/vendor/PhpSpreadsheet/Document/index.phpnu[lastModifiedBy = $this->creator; $this->created = self::intOrFloatTimestamp(null); $this->modified = $this->created; } /** * Get Creator. */ public function getCreator(): string { return $this->creator; } /** * Set Creator. * * @return $this */ public function setCreator(string $creator): self { $this->creator = $creator; return $this; } /** * Get Last Modified By. */ public function getLastModifiedBy(): string { return $this->lastModifiedBy; } /** * Set Last Modified By. * * @return $this */ public function setLastModifiedBy(string $modifiedBy): self { $this->lastModifiedBy = $modifiedBy; return $this; } /** * @param null|bool|float|int|string $timestamp * @return float|int */ private static function intOrFloatTimestamp($timestamp) { if ($timestamp === null || is_bool($timestamp)) { $timestamp = (float) (new DateTime())->format('U'); } elseif (is_string($timestamp)) { if (is_numeric($timestamp)) { $timestamp = (float) $timestamp; } else { $timestamp = (string) preg_replace('/[.][0-9]*$/', '', $timestamp); $timestamp = (string) preg_replace('/^(\d{4})- (\d)/', '$1-0$2', $timestamp); $timestamp = (string) preg_replace('/^(\d{4}-\d{2})- (\d)/', '$1-0$2', $timestamp); $timestamp = (float) (new DateTime($timestamp))->format('U'); } } return IntOrFloat::evaluate($timestamp); } /** * Get Created. * @return float|int */ public function getCreated() { return $this->created; } /** * Set Created. * * @return $this * @param null|float|int|string $timestamp */ public function setCreated($timestamp): self { $this->created = self::intOrFloatTimestamp($timestamp); return $this; } /** * Get Modified. * @return float|int */ public function getModified() { return $this->modified; } /** * Set Modified. * * @return $this * @param null|float|int|string $timestamp */ public function setModified($timestamp): self { $this->modified = self::intOrFloatTimestamp($timestamp); return $this; } /** * Get Title. */ public function getTitle(): string { return $this->title; } /** * Set Title. * * @return $this */ public function setTitle(string $title): self { $this->title = $title; return $this; } /** * Get Description. */ public function getDescription(): string { return $this->description; } /** * Set Description. * * @return $this */ public function setDescription(string $description): self { $this->description = $description; return $this; } /** * Get Subject. */ public function getSubject(): string { return $this->subject; } /** * Set Subject. * * @return $this */ public function setSubject(string $subject): self { $this->subject = $subject; return $this; } /** * Get Keywords. */ public function getKeywords(): string { return $this->keywords; } /** * Set Keywords. * * @return $this */ public function setKeywords(string $keywords): self { $this->keywords = $keywords; return $this; } /** * Get Category. */ public function getCategory(): string { return $this->category; } /** * Set Category. * * @return $this */ public function setCategory(string $category): self { $this->category = $category; return $this; } /** * Get Company. */ public function getCompany(): string { return $this->company; } /** * Set Company. * * @return $this */ public function setCompany(string $company): self { $this->company = $company; return $this; } /** * Get Manager. */ public function getManager(): string { return $this->manager; } /** * Set Manager. * * @return $this */ public function setManager(string $manager): self { $this->manager = $manager; return $this; } /** * Get a List of Custom Property Names. * * @return string[] */ public function getCustomProperties(): array { return array_keys($this->customProperties); } /** * Check if a Custom Property is defined. */ public function isCustomPropertySet(string $propertyName): bool { return array_key_exists($propertyName, $this->customProperties); } /** * Get a Custom Property Value. * @return bool|float|int|string|null */ public function getCustomPropertyValue(string $propertyName) { if (isset($this->customProperties[$propertyName])) { return $this->customProperties[$propertyName]['value']; } return null; } /** * Get a Custom Property Type. */ public function getCustomPropertyType(string $propertyName): ?string { return $this->customProperties[$propertyName]['type'] ?? null; } /** * @param bool|int|float|string|null $propertyValue */ private function identifyPropertyType($propertyValue): string { if (is_float($propertyValue)) { return self::PROPERTY_TYPE_FLOAT; } if (is_int($propertyValue)) { return self::PROPERTY_TYPE_INTEGER; } if (is_bool($propertyValue)) { return self::PROPERTY_TYPE_BOOLEAN; } return self::PROPERTY_TYPE_STRING; } /** * Set a Custom Property. * * @param ?string $propertyType see `self::VALID_PROPERTY_TYPE_LIST` * * @return $this * @param bool|int|float|string|null $propertyValue */ public function setCustomProperty(string $propertyName, $propertyValue = '', ?string $propertyType = null): self { if (($propertyType === null) || (!in_array($propertyType, self::VALID_PROPERTY_TYPE_LIST))) { $propertyType = $this->identifyPropertyType($propertyValue); } $this->customProperties[$propertyName] = [ 'value' => self::convertProperty($propertyValue, $propertyType), 'type' => $propertyType, ]; return $this; } private const PROPERTY_TYPE_ARRAY = [ 'i' => self::PROPERTY_TYPE_INTEGER, // Integer 'i1' => self::PROPERTY_TYPE_INTEGER, // 1-Byte Signed Integer 'i2' => self::PROPERTY_TYPE_INTEGER, // 2-Byte Signed Integer 'i4' => self::PROPERTY_TYPE_INTEGER, // 4-Byte Signed Integer 'i8' => self::PROPERTY_TYPE_INTEGER, // 8-Byte Signed Integer 'int' => self::PROPERTY_TYPE_INTEGER, // Integer 'ui1' => self::PROPERTY_TYPE_INTEGER, // 1-Byte Unsigned Integer 'ui2' => self::PROPERTY_TYPE_INTEGER, // 2-Byte Unsigned Integer 'ui4' => self::PROPERTY_TYPE_INTEGER, // 4-Byte Unsigned Integer 'ui8' => self::PROPERTY_TYPE_INTEGER, // 8-Byte Unsigned Integer 'uint' => self::PROPERTY_TYPE_INTEGER, // Unsigned Integer 'f' => self::PROPERTY_TYPE_FLOAT, // Real Number 'r4' => self::PROPERTY_TYPE_FLOAT, // 4-Byte Real Number 'r8' => self::PROPERTY_TYPE_FLOAT, // 8-Byte Real Number 'decimal' => self::PROPERTY_TYPE_FLOAT, // Decimal 's' => self::PROPERTY_TYPE_STRING, // String 'empty' => self::PROPERTY_TYPE_STRING, // Empty 'null' => self::PROPERTY_TYPE_STRING, // Null 'lpstr' => self::PROPERTY_TYPE_STRING, // LPSTR 'lpwstr' => self::PROPERTY_TYPE_STRING, // LPWSTR 'bstr' => self::PROPERTY_TYPE_STRING, // Basic String 'd' => self::PROPERTY_TYPE_DATE, // Date and Time 'date' => self::PROPERTY_TYPE_DATE, // Date and Time 'filetime' => self::PROPERTY_TYPE_DATE, // File Time 'b' => self::PROPERTY_TYPE_BOOLEAN, // Boolean 'bool' => self::PROPERTY_TYPE_BOOLEAN, // Boolean ]; private const SPECIAL_TYPES = [ 'empty' => '', 'null' => null, ]; /** * Convert property to form desired by Excel. * @param bool|int|float|string|null $propertyValue * @return bool|float|int|string|null */ public static function convertProperty($propertyValue, string $propertyType) { return self::SPECIAL_TYPES[$propertyType] ?? self::convertProperty2($propertyValue, $propertyType); } /** * Convert property to form desired by Excel. * @param bool|int|float|string|null $propertyValue * @return bool|float|int|string|null */ private static function convertProperty2($propertyValue, string $type) { $propertyType = self::convertPropertyType($type); switch ($propertyType) { case self::PROPERTY_TYPE_INTEGER: $intValue = (int) $propertyValue; return ($type[0] === 'u') ? abs($intValue) : $intValue; case self::PROPERTY_TYPE_FLOAT: return (float) $propertyValue; case self::PROPERTY_TYPE_DATE: return self::intOrFloatTimestamp($propertyValue); case self::PROPERTY_TYPE_BOOLEAN: return is_bool($propertyValue) ? $propertyValue : ($propertyValue === 'true'); default: // includes string return $propertyValue; } } public static function convertPropertyType(string $propertyType): string { return self::PROPERTY_TYPE_ARRAY[$propertyType] ?? self::PROPERTY_TYPE_UNKNOWN; } public function getHyperlinkBase(): string { return $this->hyperlinkBase; } public function setHyperlinkBase(string $hyperlinkBase): self { $this->hyperlinkBase = $hyperlinkBase; return $this; } public function getViewport(): string { return $this->viewport; } public const SUGGESTED_VIEWPORT = 'width=device-width, initial-scale=1'; public function setViewport(string $viewport): self { $this->viewport = $viewport; return $this; } } PK!iK 33-libraries/vendor/PhpSpreadsheet/IOFactory.phpnu[> */ private static array $readers = [ self::READER_XLSX => Reader\Xlsx::class, self::READER_XLS => Reader\Xls::class, self::READER_XML => Reader\Xml::class, self::READER_ODS => Reader\Ods::class, self::READER_SLK => Reader\Slk::class, self::READER_GNUMERIC => Reader\Gnumeric::class, self::READER_HTML => Reader\Html::class, self::READER_CSV => Reader\Csv::class, ]; /** * Create IReader. */ public static function createReader(string $readerType): IReader { /** @var class-string */ $className = $readerType; if (!in_array($readerType, self::$readers, true)) { if (!isset(self::$readers[$readerType])) { throw new Reader\Exception("No reader found for type $readerType"); } // Instantiate reader $className = self::$readers[$readerType]; } return new $className(); } /** * Loads Spreadsheet from file using automatic Reader\IReader resolution. * * @param string $filename The name of the spreadsheet file * @param int $flags the optional second parameter flags may be used to identify specific elements * that should be loaded, but which won't be loaded by default, using these values: * IReader::LOAD_WITH_CHARTS - Include any charts that are defined in the loaded file. * IReader::READ_DATA_ONLY - Read cell values only, not formatting or merge structure. * IReader::IGNORE_EMPTY_CELLS - Don't load empty cells into the model. * @param string[] $readers An array of Readers to use to identify the file type. By default, load() will try * all possible Readers until it finds a match; but this allows you to pass in a * list of Readers so it will only try the subset that you specify here. * Values in this list can be any of the constant values defined in the set * IOFactory::READER_*. */ public static function load(string $filename, int $flags = 0, ?array $readers = null): Spreadsheet { $reader = self::createReaderForFile($filename, $readers); return $reader->load($filename, $flags); } /** * Identify file type using automatic IReader resolution. */ public static function identify(string $filename, ?array $readers = null, bool $fullClassName = false): string { $reader = self::createReaderForFile($filename, $readers); $className = get_class($reader); if ($fullClassName) { return $className; } $classType = explode('\\', $className); return array_pop($classType); } /** * Create Reader\IReader for file using automatic IReader resolution. * * @param string[] $readers An array of Readers to use to identify the file type. By default, load() will try * all possible Readers until it finds a match; but this allows you to pass in a * list of Readers so it will only try the subset that you specify here. * Values in this list can be any of the constant values defined in the set * IOFactory::READER_*. */ public static function createReaderForFile(string $filename, ?array $readers = null): IReader { File::assertFile($filename); $testReaders = self::$readers; if ($readers !== null) { $readers = array_map('strtoupper', $readers); $testReaders = array_filter( self::$readers, fn (string $readerType): bool => in_array(strtoupper($readerType), $readers, true), ARRAY_FILTER_USE_KEY ); } // First, lucky guess by inspecting file extension $guessedReader = self::getReaderTypeFromExtension($filename); if (($guessedReader !== null) && array_key_exists($guessedReader, $testReaders)) { $reader = self::createReader($guessedReader); // Let's see if we are lucky if ($reader->canRead($filename)) { return $reader; } } // If we reach here then "lucky guess" didn't give any result // Try walking through all the options in self::$readers (or the selected subset) foreach ($testReaders as $readerType => $class) { // Ignore our original guess, we know that won't work if ($readerType !== $guessedReader) { $reader = self::createReader($readerType); if ($reader->canRead($filename)) { return $reader; } } } throw new Reader\Exception('Unable to identify a reader for this file'); } /** * Guess a reader type from the file extension, if any. */ private static function getReaderTypeFromExtension(string $filename): ?string { $pathinfo = pathinfo($filename); if (!isset($pathinfo['extension'])) { return null; } switch (strtolower($pathinfo['extension'])) { case 'xlsx': case 'xlsm': case 'xltx': case 'xltm': return 'Xlsx'; case 'xls': case 'xlt': return 'Xls'; case 'ods': case 'ots': return 'Ods'; case 'slk': return 'Slk'; case 'xml': return 'Xml'; case 'gnumeric': return 'Gnumeric'; case 'htm': case 'html': return 'Html'; case 'csv': return null; default: return null; } } /** * Register a reader with its type and class name. * * @param class-string $readerClass */ public static function registerReader(string $readerType, string $readerClass): void { if (!is_a($readerClass, IReader::class, true)) { throw new Reader\Exception('Registered readers must implement ' . IReader::class); } self::$readers[$readerType] = $readerClass; } } PK!)libraries/vendor/PhpSpreadsheet/index.phpnu[text = $text; } /** * Get text. * * @return string Text */ public function getText(): string { return $this->text; } /** * Set text. * * @param string $text Text * * @return $this */ public function setText(string $text): self { $this->text = $text; return $this; } /** * Get font. For this class, the return value is always null. */ public function getFont(): ?Font { return null; } /** * Get hash code. * * @return string Hash code */ public function getHashCode(): string { return md5( $this->text . __CLASS__ ); } } PK!2libraries/vendor/PhpSpreadsheet/RichText/index.phpnu[richTextElements = []; // Rich-Text string attached to cell? if ($cell !== null) { // Add cell text and style if ($cell->getValueString() !== '') { $objRun = new Run($cell->getValueString()); $objRun->setFont(clone $cell->getWorksheet()->getStyle($cell->getCoordinate())->getFont()); $this->addText($objRun); } // Set parent value $cell->setValueExplicit($this, DataType::TYPE_STRING); } } /** * Add text. * * @param ITextElement $text Rich text element * * @return $this */ public function addText(ITextElement $text) { $this->richTextElements[] = $text; return $this; } /** * Create text. * * @param string $text Text */ public function createText(string $text): TextElement { $objText = new TextElement($text); $this->addText($objText); return $objText; } /** * Create text run. * * @param string $text Text */ public function createTextRun(string $text): Run { $objText = new Run($text); $this->addText($objText); return $objText; } /** * Get plain text. */ public function getPlainText(): string { // Return value $returnValue = ''; // Loop through all ITextElements foreach ($this->richTextElements as $text) { $returnValue .= $text->getText(); } return $returnValue; } /** * Convert to string. */ public function __toString(): string { return $this->getPlainText(); } /** * Get Rich Text elements. * * @return ITextElement[] */ public function getRichTextElements(): array { return $this->richTextElements; } /** * Set Rich Text elements. * * @param ITextElement[] $textElements Array of elements * * @return $this */ public function setRichTextElements(array $textElements) { $this->richTextElements = $textElements; return $this; } /** * Get hash code. * * @return string Hash code */ public function getHashCode(): string { $hashElements = ''; foreach ($this->richTextElements as $element) { $hashElements .= $element->getHashCode(); } return md5( $hashElements . __CLASS__ ); } /** * Implement PHP __clone to create a deep clone, not just a shallow copy. */ public function __clone() { $vars = get_object_vars($this); foreach ($vars as $key => $value) { $newValue = is_object($value) ? (clone $value) : $value; if (is_array($value)) { $newValue = []; foreach ($value as $key2 => $value2) { $newValue[$key2] = is_object($value2) ? (clone $value2) : $value2; } } $this->$key = $newValue; } } } PK!0libraries/vendor/PhpSpreadsheet/RichText/Run.phpnu[font = new Font(); } /** * Get font. */ public function getFont(): ?Font { return $this->font; } public function getFontOrThrow(): Font { if ($this->font === null) { throw new SpreadsheetException('unexpected null font'); } return $this->font; } /** * Set font. * * @param ?Font $font Font * * @return $this */ public function setFont(?Font $font = null) { $this->font = $font; return $this; } /** * Get hash code. * * @return string Hash code */ public function getHashCode(): string { return md5( $this->getText() . (($this->font === null) ? '' : $this->font->getHashCode()) . __CLASS__ ); } } PK!acc+libraries/vendor/PhpSpreadsheet/Comment.phpnu[author = 'Author'; $this->text = new RichText(); $this->fillColor = new Color('FFFFFFE1'); $this->alignment = Alignment::HORIZONTAL_GENERAL; $this->backgroundImage = new Drawing(); } /** * Get Author. */ public function getAuthor(): string { return $this->author; } /** * Set Author. */ public function setAuthor(string $author): self { $this->author = $author; return $this; } /** * Get Rich text comment. */ public function getText(): RichText { return $this->text; } /** * Set Rich text comment. */ public function setText(RichText $text): self { $this->text = $text; return $this; } /** * Get comment width (CSS style, i.e. XXpx or YYpt). */ public function getWidth(): string { return $this->width; } /** * Set comment width (CSS style, i.e. XXpx or YYpt). Default unit is pt. */ public function setWidth(string $width): self { $width = new Size($width); if ($width->valid()) { $this->width = (string) $width; } return $this; } /** * Get comment height (CSS style, i.e. XXpx or YYpt). */ public function getHeight(): string { return $this->height; } /** * Set comment height (CSS style, i.e. XXpx or YYpt). Default unit is pt. */ public function setHeight(string $height): self { $height = new Size($height); if ($height->valid()) { $this->height = (string) $height; } return $this; } /** * Get left margin (CSS style, i.e. XXpx or YYpt). */ public function getMarginLeft(): string { return $this->marginLeft; } /** * Set left margin (CSS style, i.e. XXpx or YYpt). Default unit is pt. */ public function setMarginLeft(string $margin): self { $margin = new Size($margin); if ($margin->valid()) { $this->marginLeft = (string) $margin; } return $this; } /** * Get top margin (CSS style, i.e. XXpx or YYpt). */ public function getMarginTop(): string { return $this->marginTop; } /** * Set top margin (CSS style, i.e. XXpx or YYpt). Default unit is pt. */ public function setMarginTop(string $margin): self { $margin = new Size($margin); if ($margin->valid()) { $this->marginTop = (string) $margin; } return $this; } /** * Is the comment visible by default? */ public function getVisible(): bool { return $this->visible; } /** * Set comment default visibility. */ public function setVisible(bool $visibility): self { $this->visible = $visibility; return $this; } /** * Set fill color. */ public function setFillColor(Color $color): self { $this->fillColor = $color; return $this; } /** * Get fill color. */ public function getFillColor(): Color { return $this->fillColor; } public function setAlignment(string $alignment): self { $this->alignment = $alignment; return $this; } public function getAlignment(): string { return $this->alignment; } public function setTextboxDirection(string $textboxDirection): self { $this->textboxDirection = $textboxDirection; return $this; } public function getTextboxDirection(): string { return $this->textboxDirection; } /** * Get hash code. */ public function getHashCode(): string { return md5( $this->author . $this->text->getHashCode() . $this->width . $this->height . $this->marginLeft . $this->marginTop . ($this->visible ? 1 : 0) . $this->fillColor->getHashCode() . $this->alignment . $this->textboxDirection . ($this->hasBackgroundImage() ? $this->backgroundImage->getHashCode() : '') . __CLASS__ ); } /** * Implement PHP __clone to create a deep clone, not just a shallow copy. */ public function __clone() { $vars = get_object_vars($this); foreach ($vars as $key => $value) { if (is_object($value)) { $this->$key = clone $value; } else { $this->$key = $value; } } } /** * Convert to string. */ public function __toString(): string { return $this->text->getPlainText(); } /** * Check is background image exists. */ public function hasBackgroundImage(): bool { $path = $this->backgroundImage->getPath(); if (empty($path)) { return false; } return getimagesize($path) !== false; } /** * Returns background image. */ public function getBackgroundImage(): Drawing { return $this->backgroundImage; } /** * Sets background image. */ public function setBackgroundImage(Drawing $objDrawing): self { if (!array_key_exists($objDrawing->getType(), Drawing::IMAGE_TYPES_CONVERTION_MAP)) { throw new PhpSpreadsheetException('Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.'); } $this->backgroundImage = $objDrawing; return $this; } /** * Sets size of comment as size of background image. */ public function setSizeAsBackgroundImage(): self { if ($this->hasBackgroundImage()) { $this->setWidth(SharedDrawing::pixelsToPoints($this->backgroundImage->getWidth()) . 'pt'); $this->setHeight(SharedDrawing::pixelsToPoints($this->backgroundImage->getHeight()) . 'pt'); } return $this; } } PK!<<4libraries/vendor/PhpSpreadsheet/Chart/DataSeries.phpnu[plotType = $plotType; $this->plotGrouping = $plotGrouping; $this->plotOrder = $plotOrder; $keys = array_keys($plotValues); $this->plotValues = $plotValues; if (!isset($plotLabel[$keys[0]])) { $plotLabel[$keys[0]] = new DataSeriesValues(); } $this->plotLabel = $plotLabel; if (!isset($plotCategory[$keys[0]])) { $plotCategory[$keys[0]] = new DataSeriesValues(); } $this->plotCategory = $plotCategory; $this->smoothLine = (bool) $smoothLine; $this->plotStyle = $plotStyle; if ($plotDirection === null) { $plotDirection = self::DIRECTION_COL; } $this->plotDirection = $plotDirection; } /** * Get Plot Type. */ public function getPlotType(): ?string { return $this->plotType; } /** * Set Plot Type. * * @return $this */ public function setPlotType(string $plotType) { $this->plotType = $plotType; return $this; } /** * Get Plot Grouping Type. */ public function getPlotGrouping(): ?string { return $this->plotGrouping; } /** * Set Plot Grouping Type. * * @return $this */ public function setPlotGrouping(string $groupingType) { $this->plotGrouping = $groupingType; return $this; } /** * Get Plot Direction. */ public function getPlotDirection(): string { return $this->plotDirection; } /** * Set Plot Direction. * * @return $this */ public function setPlotDirection(string $plotDirection) { $this->plotDirection = $plotDirection; return $this; } /** * Get Plot Order. * * @return int[] */ public function getPlotOrder(): array { return $this->plotOrder; } /** * Get Plot Labels. * * @return DataSeriesValues[] */ public function getPlotLabels(): array { return $this->plotLabel; } /** * Get Plot Label by Index. * * @return DataSeriesValues|false */ public function getPlotLabelByIndex(int $index) { $keys = array_keys($this->plotLabel); if (in_array($index, $keys)) { return $this->plotLabel[$index]; } return false; } /** * Get Plot Categories. * * @return DataSeriesValues[] */ public function getPlotCategories(): array { return $this->plotCategory; } /** * Get Plot Category by Index. * * @return DataSeriesValues|false */ public function getPlotCategoryByIndex(int $index) { $keys = array_keys($this->plotCategory); if (in_array($index, $keys)) { return $this->plotCategory[$index]; } elseif (isset($keys[$index])) { return $this->plotCategory[$keys[$index]]; } return false; } /** * Get Plot Style. */ public function getPlotStyle(): ?string { return $this->plotStyle; } /** * Set Plot Style. * * @return $this */ public function setPlotStyle(?string $plotStyle) { $this->plotStyle = $plotStyle; return $this; } /** * Get Plot Values. * * @return DataSeriesValues[] */ public function getPlotValues(): array { return $this->plotValues; } /** * Get Plot Values by Index. * * @return DataSeriesValues|false */ public function getPlotValuesByIndex(int $index) { $keys = array_keys($this->plotValues); if (in_array($index, $keys)) { return $this->plotValues[$index]; } return false; } /** * Get Plot Bubble Sizes. * * @return DataSeriesValues[] */ public function getPlotBubbleSizes(): array { return $this->plotBubbleSizes; } /** * Set Plot Bubble Sizes. * * @param DataSeriesValues[] $plotBubbleSizes */ public function setPlotBubbleSizes(array $plotBubbleSizes): self { $this->plotBubbleSizes = $plotBubbleSizes; return $this; } /** * Get Number of Plot Series. */ public function getPlotSeriesCount(): int { return count($this->plotValues); } /** * Get Smooth Line. */ public function getSmoothLine(): bool { return $this->smoothLine; } /** * Set Smooth Line. * * @return $this */ public function setSmoothLine(bool $smoothLine) { $this->smoothLine = $smoothLine; return $this; } public function refresh(Worksheet $worksheet): void { foreach ($this->plotValues as $plotValues) { $plotValues->refresh($worksheet, true); } foreach ($this->plotLabel as $plotValues) { $plotValues->refresh($worksheet, true); } foreach ($this->plotCategory as $plotValues) { $plotValues->refresh($worksheet, false); } } /** * Implement PHP __clone to create a deep clone, not just a shallow copy. */ public function __clone() { $plotLabels = $this->plotLabel; $this->plotLabel = []; foreach ($plotLabels as $plotLabel) { $this->plotLabel[] = $plotLabel; } $plotCategories = $this->plotCategory; $this->plotCategory = []; foreach ($plotCategories as $plotCategory) { $this->plotCategory[] = clone $plotCategory; } $plotValues = $this->plotValues; $this->plotValues = []; foreach ($plotValues as $plotValue) { $this->plotValues[] = clone $plotValue; } $plotBubbleSizes = $this->plotBubbleSizes; $this->plotBubbleSizes = []; foreach ($plotBubbleSizes as $plotBubbleSize) { $this->plotBubbleSizes[] = clone $plotBubbleSize; } } } PK!<4882libraries/vendor/PhpSpreadsheet/Chart/AxisText.phpnu[font = new Font(); $this->font->setSize(null, true); } public function setRotation(?int $rotation): self { $this->rotation = $rotation; return $this; } public function getRotation(): ?int { return $this->rotation; } public function getFillColorObject(): ChartColor { $fillColor = $this->font->getChartColor(); if ($fillColor === null) { $fillColor = new ChartColor(); $this->font->setChartColorFromObject($fillColor); } return $fillColor; } public function getFont(): Font { return $this->font; } public function setFont(Font $font): self { $this->font = $font; return $this; } /** * Implement PHP __clone to create a deep clone, not just a shallow copy. */ public function __clone() { parent::__clone(); $this->font = clone $this->font; } } PK!-#0libraries/vendor/PhpSpreadsheet/Chart/Legend.phpnu[ self::POSITION_BOTTOM, self::XL_LEGEND_POSITION_CORNER => self::POSITION_TOPRIGHT, self::XL_LEGEND_POSITION_CUSTOM => '??', self::XL_LEGEND_POSITION_LEFT => self::POSITION_LEFT, self::XL_LEGEND_POSITION_RIGHT => self::POSITION_RIGHT, self::XL_LEGEND_POSITION_TOP => self::POSITION_TOP, ]; /** * Legend position. */ private string $position = self::POSITION_RIGHT; /** * Allow overlay of other elements? */ private bool $overlay = true; /** * Legend Layout. */ private ?Layout $layout; private GridLines $borderLines; private ChartColor $fillColor; private ?AxisText $legendText = null; /** * Create a new Legend. */ public function __construct(string $position = self::POSITION_RIGHT, ?Layout $layout = null, bool $overlay = false) { $this->setPosition($position); $this->layout = $layout; $this->setOverlay($overlay); $this->borderLines = new GridLines(); $this->fillColor = new ChartColor(); } public function getFillColor(): ChartColor { return $this->fillColor; } /** * Get legend position as an excel string value. */ public function getPosition(): string { return $this->position; } /** * Get legend position using an excel string value. * * @param string $position see self::POSITION_* */ public function setPosition(string $position): bool { if (!in_array($position, self::POSITION_XLREF)) { return false; } $this->position = $position; return true; } /** * Get legend position as an Excel internal numeric value. * @return int|false */ public function getPositionXL() { return array_search($this->position, self::POSITION_XLREF); } /** * Set legend position using an Excel internal numeric value. * * @param int $positionXL see self::XL_LEGEND_POSITION_* */ public function setPositionXL(int $positionXL): bool { if (!isset(self::POSITION_XLREF[$positionXL])) { return false; } $this->position = self::POSITION_XLREF[$positionXL]; return true; } /** * Get allow overlay of other elements? */ public function getOverlay(): bool { return $this->overlay; } /** * Set allow overlay of other elements? */ public function setOverlay(bool $overlay): void { $this->overlay = $overlay; } /** * Get Layout. */ public function getLayout(): ?Layout { return $this->layout; } public function getLegendText(): ?AxisText { return $this->legendText; } public function setLegendText(?AxisText $legendText): self { $this->legendText = $legendText; return $this; } public function getBorderLines(): GridLines { return $this->borderLines; } public function setBorderLines(GridLines $borderLines): self { $this->borderLines = $borderLines; return $this; } /** * Implement PHP __clone to create a deep clone, not just a shallow copy. */ public function __clone() { $this->layout = ($this->layout === null) ? null : clone $this->layout; $this->legendText = ($this->legendText === null) ? null : clone $this->legendText; $this->borderLines = clone $this->borderLines; $this->fillColor = clone $this->fillColor; } } PK!L,a a 4libraries/vendor/PhpSpreadsheet/Chart/ChartColor.phpnu[setColorPropertiesArray($value); } else { $this->setColorProperties($value, $alpha, $type, $brightness); } } public function getValue(): string { return $this->value; } public function setValue(string $value): self { $this->value = $value; return $this; } public function getType(): string { return $this->type; } public function setType(string $type): self { $this->type = $type; return $this; } public function getAlpha(): ?int { return $this->alpha; } public function setAlpha(?int $alpha): self { $this->alpha = $alpha; return $this; } public function getBrightness(): ?int { return $this->brightness; } public function setBrightness(?int $brightness): self { $this->brightness = $brightness; return $this; } /** * @param null|float|int|string $alpha * @param null|float|int|string $brightness */ public function setColorProperties(?string $color, $alpha = null, ?string $type = null, $brightness = null): self { if (empty($type) && !empty($color)) { if (str_starts_with($color, '*')) { $type = 'schemeClr'; $color = substr($color, 1); } elseif (str_starts_with($color, '/')) { $type = 'prstClr'; $color = substr($color, 1); } elseif (preg_match('/^[0-9A-Fa-f]{6}$/', $color) === 1) { $type = 'srgbClr'; } } if ($color !== null) { $this->setValue("$color"); } if ($type !== null) { $this->setType($type); } if ($alpha === null) { $this->setAlpha(null); } elseif (is_numeric($alpha)) { $this->setAlpha((int) $alpha); } if ($brightness === null) { $this->setBrightness(null); } elseif (is_numeric($brightness)) { $this->setBrightness((int) $brightness); } return $this; } public function setColorPropertiesArray(array $color): self { return $this->setColorProperties( $color['value'] ?? '', $color['alpha'] ?? null, $color['type'] ?? null, $color['brightness'] ?? null ); } public function isUsable(): bool { return $this->type !== '' && $this->value !== ''; } /** * Get Color Property. * @return int|string|null */ public function getColorProperty(string $propertyName) { $retVal = null; if ($propertyName === 'value') { $retVal = $this->value; } elseif ($propertyName === 'type') { $retVal = $this->type; } elseif ($propertyName === 'alpha') { $retVal = $this->alpha; } elseif ($propertyName === 'brightness') { $retVal = $this->brightness; } return $retVal; } public static function alphaToXml(int $alpha): string { return (string) (100 - $alpha) . '000'; } /** * @param float|int|string $alpha */ public static function alphaFromXml($alpha): int { return 100 - ((int) $alpha / 1000); } } PK!f..:libraries/vendor/PhpSpreadsheet/Chart/DataSeriesValues.phpnu[markerFillColor = new ChartColor(); $this->markerBorderColor = new ChartColor(); $this->setDataType($dataType); $this->dataSource = $dataSource; $this->formatCode = $formatCode; $this->pointCount = $pointCount; $this->dataValues = $dataValues; $this->pointMarker = $marker; if ($fillColor !== null) { $this->setFillColor($fillColor); } if (is_numeric($pointSize)) { $this->pointSize = (int) $pointSize; } } /** * Get Series Data Type. */ public function getDataType(): string { return $this->dataType; } /** * Set Series Data Type. * * @param string $dataType Datatype of this data series * Typical values are: * DataSeriesValues::DATASERIES_TYPE_STRING * Normally used for axis point values * DataSeriesValues::DATASERIES_TYPE_NUMBER * Normally used for chart data values * * @return $this */ public function setDataType(string $dataType) { if (!in_array($dataType, self::DATA_TYPE_VALUES)) { throw new Exception('Invalid datatype for chart data series values'); } $this->dataType = $dataType; return $this; } /** * Get Series Data Source (formula). */ public function getDataSource(): ?string { return $this->dataSource; } /** * Set Series Data Source (formula). * * @return $this */ public function setDataSource(?string $dataSource) { $this->dataSource = $dataSource; return $this; } /** * Get Point Marker. */ public function getPointMarker(): ?string { return $this->pointMarker; } /** * Set Point Marker. * * @return $this */ public function setPointMarker(string $marker) { $this->pointMarker = $marker; return $this; } public function getMarkerFillColor(): ChartColor { return $this->markerFillColor; } public function getMarkerBorderColor(): ChartColor { return $this->markerBorderColor; } /** * Get Point Size. */ public function getPointSize(): int { return $this->pointSize; } /** * Set Point Size. * * @return $this */ public function setPointSize(int $size = 3) { $this->pointSize = $size; return $this; } /** * Get Series Format Code. */ public function getFormatCode(): ?string { return $this->formatCode; } /** * Set Series Format Code. * * @return $this */ public function setFormatCode(string $formatCode) { $this->formatCode = $formatCode; return $this; } /** * Get Series Point Count. */ public function getPointCount(): int { return $this->pointCount; } /** * Get fill color object. * * @return null|ChartColor|ChartColor[] */ public function getFillColorObject() { return $this->fillColor; } private function stringToChartColor(string $fillString): ChartColor { $value = $type = ''; if (str_starts_with($fillString, '*')) { $type = 'schemeClr'; $value = substr($fillString, 1); } elseif (str_starts_with($fillString, '/')) { $type = 'prstClr'; $value = substr($fillString, 1); } elseif ($fillString !== '') { $type = 'srgbClr'; $value = $fillString; $this->validateColor($value); } return new ChartColor($value, null, $type); } private function chartColorToString(ChartColor $chartColor): string { $type = (string) $chartColor->getColorProperty('type'); $value = (string) $chartColor->getColorProperty('value'); if ($type === '' || $value === '') { return ''; } if ($type === 'schemeClr') { return "*$value"; } if ($type === 'prstClr') { return "/$value"; } return $value; } /** * Get fill color. * * @return string|string[] HEX color or array with HEX colors */ public function getFillColor() { if ($this->fillColor === null) { return ''; } if (is_array($this->fillColor)) { $array = []; foreach ($this->fillColor as $chartColor) { $array[] = $this->chartColorToString($chartColor); } return $array; } return $this->chartColorToString($this->fillColor); } /** * Set fill color for series. * * @param ChartColor|ChartColor[]|string|string[] $color HEX color or array with HEX colors * * @return $this */ public function setFillColor($color) { if (is_array($color)) { $this->fillColor = []; foreach ($color as $fillString) { if ($fillString instanceof ChartColor) { $this->fillColor[] = $fillString; } else { $this->fillColor[] = $this->stringToChartColor($fillString); } } } elseif ($color instanceof ChartColor) { $this->fillColor = $color; } else { $this->fillColor = $this->stringToChartColor($color); } return $this; } /** * Method for validating hex color. * * @param string $color value for color * * @return bool true if validation was successful */ private function validateColor(string $color): bool { if (!preg_match('/^[a-f0-9]{6}$/i', $color)) { throw new Exception(sprintf('Invalid hex color for chart series (color: "%s")', $color)); } return true; } /** * Get line width for series. * @return float|int|null */ public function getLineWidth() { return $this->lineStyleProperties['width']; } /** * Set line width for the series. * * @return $this * @param null|float|int $width */ public function setLineWidth($width) { $this->lineStyleProperties['width'] = $width; return $this; } /** * Identify if the Data Series is a multi-level or a simple series. */ public function isMultiLevelSeries(): ?bool { if (!empty($this->dataValues)) { return is_array(array_values($this->dataValues)[0]); } return null; } /** * Return the level count of a multi-level Data Series. */ public function multiLevelCount(): int { $levelCount = 0; foreach (($this->dataValues ?? []) as $dataValueSet) { $levelCount = max($levelCount, count($dataValueSet)); } return $levelCount; } /** * Get Series Data Values. */ public function getDataValues(): ?array { return $this->dataValues; } /** * Get the first Series Data value. * @return mixed */ public function getDataValue() { if ($this->dataValues === null) { return null; } $count = count($this->dataValues); if ($count == 0) { return null; } elseif ($count == 1) { return $this->dataValues[0]; } return $this->dataValues; } /** * Set Series Data Values. * * @return $this */ public function setDataValues(array $dataValues) { $this->dataValues = Functions::flattenArray($dataValues); $this->pointCount = count($dataValues); return $this; } public function refresh(Worksheet $worksheet, bool $flatten = true): void { if ($this->dataSource !== null) { $calcEngine = Calculation::getInstance($worksheet->getParent()); $newDataValues = Calculation::unwrapResult( $calcEngine->_calculateFormulaValue( '=' . $this->dataSource, null, $worksheet->getCell('A1') ) ); if ($flatten) { $this->dataValues = Functions::flattenArray($newDataValues); foreach ($this->dataValues as &$dataValue) { if (is_string($dataValue) && !empty($dataValue) && $dataValue[0] == '#') { $dataValue = 0.0; } } unset($dataValue); } else { [, $cellRange] = Worksheet::extractSheetTitle($this->dataSource, true); $dimensions = Coordinate::rangeDimension(str_replace('$', '', $cellRange ?? '')); if (($dimensions[0] == 1) || ($dimensions[1] == 1)) { $this->dataValues = Functions::flattenArray($newDataValues); } else { /** @var array */ $newDataValuesx = $newDataValues; $newArray = array_values(array_shift($newDataValuesx) ?? []); foreach ($newArray as $i => $newDataSet) { $newArray[$i] = [$newDataSet]; } foreach ($newDataValuesx as $newDataSet) { $i = 0; foreach ($newDataSet as $newDataVal) { array_unshift($newArray[$i++], $newDataVal); } } $this->dataValues = $newArray; } } $this->pointCount = count($this->dataValues); } } public function getScatterLines(): bool { return $this->scatterLines; } public function setScatterLines(bool $scatterLines): self { $this->scatterLines = $scatterLines; return $this; } public function getBubble3D(): bool { return $this->bubble3D; } public function setBubble3D(bool $bubble3D): self { $this->bubble3D = $bubble3D; return $this; } /** * Smooth Line. Must be specified for both DataSeries and DataSeriesValues. */ private bool $smoothLine = false; /** * Get Smooth Line. */ public function getSmoothLine(): bool { return $this->smoothLine; } /** * Set Smooth Line. * * @return $this */ public function setSmoothLine(bool $smoothLine) { $this->smoothLine = $smoothLine; return $this; } public function getLabelLayout(): ?Layout { return $this->labelLayout; } public function setLabelLayout(?Layout $labelLayout): self { $this->labelLayout = $labelLayout; return $this; } public function setTrendLines(array $trendLines): self { $this->trendLines = $trendLines; return $this; } public function getTrendLines(): array { return $this->trendLines; } /** * Implement PHP __clone to create a deep clone, not just a shallow copy. */ public function __clone() { parent::__clone(); $this->markerFillColor = clone $this->markerFillColor; $this->markerBorderColor = clone $this->markerBorderColor; if (is_array($this->fillColor)) { $fillColor = $this->fillColor; $this->fillColor = []; foreach ($fillColor as $color) { $this->fillColor[] = clone $color; } } elseif ($this->fillColor instanceof ChartColor) { $this->fillColor = clone $this->fillColor; } $this->labelLayout = ($this->labelLayout === null) ? null : clone $this->labelLayout; $trendLines = $this->trendLines; $this->trendLines = []; foreach ($trendLines as $trendLine) { $this->trendLines[] = clone $trendLine; } } } PK!PP/libraries/vendor/PhpSpreadsheet/Chart/Title.phpnu[|RichText|string */ private $caption; /** * Allow overlay of other elements? */ private bool $overlay = true; /** * Title Layout. */ private ?Layout $layout; private string $cellReference = ''; private ?Font $font = null; /** * Create a new Title. * @param mixed[]|\TablePress\PhpOffice\PhpSpreadsheet\RichText\RichText|string $caption */ public function __construct($caption = '', ?Layout $layout = null, bool $overlay = false) { $this->caption = $caption; $this->layout = $layout; $this->setOverlay($overlay); } /** * Get caption. * @return mixed[]|\TablePress\PhpOffice\PhpSpreadsheet\RichText\RichText|string */ public function getCaption() { return $this->caption; } public function getCaptionText(?Spreadsheet $spreadsheet = null): string { if ($spreadsheet !== null) { $caption = $this->getCalculatedTitle($spreadsheet); if ($caption !== null) { return $caption; } } $caption = $this->caption; if (is_string($caption)) { return $caption; } if ($caption instanceof RichText) { return $caption->getPlainText(); } $retVal = ''; foreach ($caption as $textx) { /** @var RichText|string $text */ $text = $textx; if ($text instanceof RichText) { $retVal .= $text->getPlainText(); } else { $retVal .= $text; } } return $retVal; } /** * Set caption. * * @return $this * @param mixed[]|\TablePress\PhpOffice\PhpSpreadsheet\RichText\RichText|string $caption */ public function setCaption($caption) { $this->caption = $caption; return $this; } /** * Get allow overlay of other elements? */ public function getOverlay(): bool { return $this->overlay; } /** * Set allow overlay of other elements? */ public function setOverlay(bool $overlay): self { $this->overlay = $overlay; return $this; } public function getLayout(): ?Layout { return $this->layout; } public function setCellReference(string $cellReference): self { $this->cellReference = $cellReference; return $this; } public function getCellReference(): string { return $this->cellReference; } public function getCalculatedTitle(?Spreadsheet $spreadsheet): ?string { preg_match(self::TITLE_CELL_REFERENCE, $this->cellReference, $matches); if (count($matches) === 0 || $spreadsheet === null) { return null; } $sheetName = preg_replace("/^'(.*)'$/", '$1', $matches[1]) ?? ''; return ($nullsafeVariable1 = ($nullsafeVariable2 = $spreadsheet->getSheetByName($sheetName)) ? $nullsafeVariable2->getCell($matches[2] . $matches[3]) : null) ? $nullsafeVariable1->getFormattedValue() : null; } public function getFont(): ?Font { return $this->font; } public function setFont(?Font $font): self { $this->font = $font; return $this; } /** * Implement PHP __clone to create a deep clone, not just a shallow copy. */ public function __clone() { $this->layout = ($this->layout === null) ? null : clone $this->layout; $this->font = ($this->font === null) ? null : clone $this->font; if (is_array($this->caption)) { $captions = $this->caption; $this->caption = []; foreach ($captions as $caption) { $this->caption[] = is_object($caption) ? (clone $caption) : $caption; } } else { $this->caption = is_object($this->caption) ? (clone $this->caption) : $this->caption; } } } PK!j?3libraries/vendor/PhpSpreadsheet/Chart/Exception.phpnu[name = $name; $this->title = $title; $this->legend = $legend; $this->xAxisLabel = $xAxisLabel; $this->yAxisLabel = $yAxisLabel; $this->plotArea = $plotArea; $this->plotVisibleOnly = $plotVisibleOnly; $this->setDisplayBlanksAs($displayBlanksAs); $this->xAxis = $xAxis ?? new Axis(); $this->yAxis = $yAxis ?? new Axis(); if ($majorGridlines !== null) { $this->yAxis->setMajorGridlines($majorGridlines); } if ($minorGridlines !== null) { $this->yAxis->setMinorGridlines($minorGridlines); } $this->fillColor = new ChartColor(); $this->borderLines = new GridLines(); } public function __destruct() { $this->worksheet = null; } /** * Get Name. */ public function getName(): string { return $this->name; } public function setName(string $name): self { $this->name = $name; return $this; } /** * Get Worksheet. */ public function getWorksheet(): ?Worksheet { return $this->worksheet; } /** * Set Worksheet. * * @return $this */ public function setWorksheet(?Worksheet $worksheet = null) { $this->worksheet = $worksheet; return $this; } public function getTitle(): ?Title { return $this->title; } /** * Set Title. * * @return $this */ public function setTitle(Title $title) { $this->title = $title; return $this; } public function getLegend(): ?Legend { return $this->legend; } /** * Set Legend. * * @return $this */ public function setLegend(Legend $legend) { $this->legend = $legend; return $this; } public function getXAxisLabel(): ?Title { return $this->xAxisLabel; } /** * Set X-Axis Label. * * @return $this */ public function setXAxisLabel(Title $label) { $this->xAxisLabel = $label; return $this; } public function getYAxisLabel(): ?Title { return $this->yAxisLabel; } /** * Set Y-Axis Label. * * @return $this */ public function setYAxisLabel(Title $label) { $this->yAxisLabel = $label; return $this; } public function getPlotArea(): ?PlotArea { return $this->plotArea; } public function getPlotAreaOrThrow(): PlotArea { $plotArea = $this->getPlotArea(); if ($plotArea !== null) { return $plotArea; } throw new Exception('Chart has no PlotArea'); } /** * Set Plot Area. */ public function setPlotArea(PlotArea $plotArea): self { $this->plotArea = $plotArea; return $this; } /** * Get Plot Visible Only. */ public function getPlotVisibleOnly(): bool { return $this->plotVisibleOnly; } /** * Set Plot Visible Only. * * @return $this */ public function setPlotVisibleOnly(bool $plotVisibleOnly) { $this->plotVisibleOnly = $plotVisibleOnly; return $this; } /** * Get Display Blanks as. */ public function getDisplayBlanksAs(): string { return $this->displayBlanksAs; } /** * Set Display Blanks as. * * @return $this */ public function setDisplayBlanksAs(string $displayBlanksAs) { $displayBlanksAs = strtolower($displayBlanksAs); $this->displayBlanksAs = in_array($displayBlanksAs, DataSeries::VALID_EMPTY_AS, true) ? $displayBlanksAs : DataSeries::DEFAULT_EMPTY_AS; return $this; } public function getChartAxisY(): Axis { return $this->yAxis; } /** * Set yAxis. */ public function setChartAxisY(?Axis $axis): self { $this->yAxis = $axis ?? new Axis(); return $this; } public function getChartAxisX(): Axis { return $this->xAxis; } /** * Set xAxis. */ public function setChartAxisX(?Axis $axis): self { $this->xAxis = $axis ?? new Axis(); return $this; } /** * Set the Top Left position for the chart. * * @return $this */ public function setTopLeftPosition(string $cellAddress, ?int $xOffset = null, ?int $yOffset = null) { $this->topLeftCellRef = $cellAddress; if ($xOffset !== null) { $this->setTopLeftXOffset($xOffset); } if ($yOffset !== null) { $this->setTopLeftYOffset($yOffset); } return $this; } /** * Get the top left position of the chart. * * Returns ['cell' => string cell address, 'xOffset' => int, 'yOffset' => int]. * * @return array{cell: string, xOffset: int, yOffset: int} an associative array containing the cell address, X-Offset and Y-Offset from the top left of that cell */ public function getTopLeftPosition(): array { return [ 'cell' => $this->topLeftCellRef, 'xOffset' => $this->topLeftXOffset, 'yOffset' => $this->topLeftYOffset, ]; } /** * Get the cell address where the top left of the chart is fixed. */ public function getTopLeftCell(): string { return $this->topLeftCellRef; } /** * Set the Top Left cell position for the chart. * * @return $this */ public function setTopLeftCell(string $cellAddress) { $this->topLeftCellRef = $cellAddress; return $this; } /** * Set the offset position within the Top Left cell for the chart. * * @return $this */ public function setTopLeftOffset(?int $xOffset, ?int $yOffset) { if ($xOffset !== null) { $this->setTopLeftXOffset($xOffset); } if ($yOffset !== null) { $this->setTopLeftYOffset($yOffset); } return $this; } /** * Get the offset position within the Top Left cell for the chart. * * @return int[] */ public function getTopLeftOffset(): array { return [ 'X' => $this->topLeftXOffset, 'Y' => $this->topLeftYOffset, ]; } /** * @return $this */ public function setTopLeftXOffset(int $xOffset) { $this->topLeftXOffset = $xOffset; return $this; } public function getTopLeftXOffset(): int { return $this->topLeftXOffset; } /** * @return $this */ public function setTopLeftYOffset(int $yOffset) { $this->topLeftYOffset = $yOffset; return $this; } public function getTopLeftYOffset(): int { return $this->topLeftYOffset; } /** * Set the Bottom Right position of the chart. * * @return $this */ public function setBottomRightPosition(string $cellAddress = '', ?int $xOffset = null, ?int $yOffset = null) { $this->bottomRightCellRef = $cellAddress; if ($xOffset !== null) { $this->setBottomRightXOffset($xOffset); } if ($yOffset !== null) { $this->setBottomRightYOffset($yOffset); } return $this; } /** * Get the bottom right position of the chart. * * @return array an associative array containing the cell address, X-Offset and Y-Offset from the top left of that cell */ public function getBottomRightPosition(): array { return [ 'cell' => $this->bottomRightCellRef, 'xOffset' => $this->bottomRightXOffset, 'yOffset' => $this->bottomRightYOffset, ]; } /** * Set the Bottom Right cell for the chart. * * @return $this */ public function setBottomRightCell(string $cellAddress = '') { $this->bottomRightCellRef = $cellAddress; return $this; } /** * Get the cell address where the bottom right of the chart is fixed. */ public function getBottomRightCell(): string { return $this->bottomRightCellRef; } /** * Set the offset position within the Bottom Right cell for the chart. * * @return $this */ public function setBottomRightOffset(?int $xOffset, ?int $yOffset) { if ($xOffset !== null) { $this->setBottomRightXOffset($xOffset); } if ($yOffset !== null) { $this->setBottomRightYOffset($yOffset); } return $this; } /** * Get the offset position within the Bottom Right cell for the chart. * * @return int[] */ public function getBottomRightOffset(): array { return [ 'X' => $this->bottomRightXOffset, 'Y' => $this->bottomRightYOffset, ]; } /** * @return $this */ public function setBottomRightXOffset(int $xOffset) { $this->bottomRightXOffset = $xOffset; return $this; } public function getBottomRightXOffset(): int { return $this->bottomRightXOffset; } /** * @return $this */ public function setBottomRightYOffset(int $yOffset) { $this->bottomRightYOffset = $yOffset; return $this; } public function getBottomRightYOffset(): int { return $this->bottomRightYOffset; } public function refresh(): void { if ($this->worksheet !== null && $this->plotArea !== null) { $this->plotArea->refresh($this->worksheet); } } /** * Render the chart to given file (or stream). * * @param ?string $outputDestination Name of the file render to * * @return bool true on success */ public function render(?string $outputDestination = null): bool { if ($outputDestination == 'php://output') { $outputDestination = null; } $libraryName = Settings::getChartRenderer(); if ($libraryName === null) { return false; } // Ensure that data series values are up-to-date before we render $this->refresh(); $renderer = new $libraryName($this); return $renderer->render($outputDestination); } public function getRotX(): ?int { return $this->rotX; } public function setRotX(?int $rotX): self { $this->rotX = $rotX; return $this; } public function getRotY(): ?int { return $this->rotY; } public function setRotY(?int $rotY): self { $this->rotY = $rotY; return $this; } public function getRAngAx(): ?int { return $this->rAngAx; } public function setRAngAx(?int $rAngAx): self { $this->rAngAx = $rAngAx; return $this; } public function getPerspective(): ?int { return $this->perspective; } public function setPerspective(?int $perspective): self { $this->perspective = $perspective; return $this; } public function getOneCellAnchor(): bool { return $this->oneCellAnchor; } public function setOneCellAnchor(bool $oneCellAnchor): self { $this->oneCellAnchor = $oneCellAnchor; return $this; } public function getAutoTitleDeleted(): bool { return $this->autoTitleDeleted; } public function setAutoTitleDeleted(bool $autoTitleDeleted): self { $this->autoTitleDeleted = $autoTitleDeleted; return $this; } public function getNoFill(): bool { return $this->noFill; } public function setNoFill(bool $noFill): self { $this->noFill = $noFill; return $this; } public function getNoBorder(): bool { return $this->noBorder; } public function setNoBorder(bool $noBorder): self { $this->noBorder = $noBorder; return $this; } public function getRoundedCorners(): bool { return $this->roundedCorners; } public function setRoundedCorners(?bool $roundedCorners): self { if ($roundedCorners !== null) { $this->roundedCorners = $roundedCorners; } return $this; } public function getBorderLines(): GridLines { return $this->borderLines; } public function setBorderLines(GridLines $borderLines): self { $this->borderLines = $borderLines; return $this; } public function getFillColor(): ChartColor { return $this->fillColor; } public function setRenderedWidth(?float $width): self { $this->renderedWidth = $width; return $this; } public function getRenderedWidth(): ?float { return $this->renderedWidth; } public function setRenderedHeight(?float $height): self { $this->renderedHeight = $height; return $this; } public function getRenderedHeight(): ?float { return $this->renderedHeight; } /** * Implement PHP __clone to create a deep clone, not just a shallow copy. */ public function __clone() { $this->worksheet = null; $this->title = ($this->title === null) ? null : clone $this->title; $this->legend = ($this->legend === null) ? null : clone $this->legend; $this->xAxisLabel = ($this->xAxisLabel === null) ? null : clone $this->xAxisLabel; $this->yAxisLabel = ($this->yAxisLabel === null) ? null : clone $this->yAxisLabel; $this->plotArea = ($this->plotArea === null) ? null : clone $this->plotArea; $this->xAxis = clone $this->xAxis; $this->yAxis = clone $this->yAxis; $this->borderLines = clone $this->borderLines; $this->fillColor = clone $this->fillColor; } } PK![?G\#\#.libraries/vendor/PhpSpreadsheet/Chart/Axis.phpnu[fillColor = new ChartColor(); } /** * Chart Major Gridlines as. */ private ?GridLines $majorGridlines = null; /** * Chart Minor Gridlines as. */ private ?GridLines $minorGridlines = null; /** * Axis Number. * * @var array{format: string, source_linked: int, numeric: ?bool} */ private array $axisNumber = [ 'format' => self::FORMAT_CODE_GENERAL, 'source_linked' => 1, 'numeric' => null, ]; private string $axisType = ''; private ?AxisText $axisText = null; private ?Title $dispUnitsTitle = null; /** * Axis Options. * * @var array */ private array $axisOptions = [ 'minimum' => null, 'maximum' => null, 'major_unit' => null, 'minor_unit' => null, 'orientation' => self::ORIENTATION_NORMAL, 'minor_tick_mark' => self::TICK_MARK_NONE, 'major_tick_mark' => self::TICK_MARK_NONE, 'axis_labels' => self::AXIS_LABELS_NEXT_TO, 'horizontal_crosses' => self::HORIZONTAL_CROSSES_AUTOZERO, 'horizontal_crosses_value' => null, 'textRotation' => null, 'hidden' => null, 'majorTimeUnit' => self::TIME_UNIT_YEARS, 'minorTimeUnit' => self::TIME_UNIT_MONTHS, 'baseTimeUnit' => self::TIME_UNIT_DAYS, 'logBase' => null, 'dispUnitsBuiltIn' => null, ]; public const DISP_UNITS_HUNDREDS = 'hundreds'; public const DISP_UNITS_THOUSANDS = 'thousands'; public const DISP_UNITS_TEN_THOUSANDS = 'tenThousands'; public const DISP_UNITS_HUNDRED_THOUSANDS = 'hundredThousands'; public const DISP_UNITS_MILLIONS = 'millions'; public const DISP_UNITS_TEN_MILLIONS = 'tenMillions'; public const DISP_UNITS_HUNDRED_MILLIONS = 'hundredMillions'; public const DISP_UNITS_BILLIONS = 'billions'; public const DISP_UNITS_TRILLIONS = 'trillions'; public const TRILLION_INDEX = (PHP_INT_SIZE > 4) ? 1000000000000 : '1000000000000'; public const DISP_UNITS_BUILTIN_INT = [ 100 => self::DISP_UNITS_HUNDREDS, 1000 => self::DISP_UNITS_THOUSANDS, 10000 => self::DISP_UNITS_TEN_THOUSANDS, 100000 => self::DISP_UNITS_HUNDRED_THOUSANDS, 1000000 => self::DISP_UNITS_MILLIONS, 10000000 => self::DISP_UNITS_TEN_MILLIONS, 100000000 => self::DISP_UNITS_HUNDRED_MILLIONS, 1000000000 => self::DISP_UNITS_BILLIONS, self::TRILLION_INDEX => self::DISP_UNITS_TRILLIONS, // overflow for 32-bit ]; /** * Fill Properties. */ private ChartColor $fillColor; private const NUMERIC_FORMAT = [ Properties::FORMAT_CODE_NUMBER, Properties::FORMAT_CODE_DATE, Properties::FORMAT_CODE_DATE_ISO8601, ]; private bool $noFill = false; /** * Get Series Data Type. */ public function setAxisNumberProperties(string $format_code, ?bool $numeric = null, int $sourceLinked = 0): void { $format = $format_code; $this->axisNumber['format'] = $format; $this->axisNumber['source_linked'] = $sourceLinked; if (is_bool($numeric)) { $this->axisNumber['numeric'] = $numeric; } elseif (in_array($format, self::NUMERIC_FORMAT, true)) { $this->axisNumber['numeric'] = true; } } /** * Get Axis Number Format Data Type. */ public function getAxisNumberFormat(): string { return $this->axisNumber['format']; } /** * Get Axis Number Source Linked. */ public function getAxisNumberSourceLinked(): string { return (string) $this->axisNumber['source_linked']; } public function getAxisIsNumericFormat(): bool { return $this->axisType === self::AXIS_TYPE_DATE || (bool) $this->axisNumber['numeric']; } /** * @param null|float|int|string $value */ public function setAxisOption(string $key, $value): void { if ($value !== null && $value !== '') { $this->axisOptions[$key] = (string) $value; } } /** * Set Axis Options Properties. * @param null|float|int|string $minimum * @param null|float|int|string $maximum * @param null|float|int|string $majorUnit * @param null|float|int|string $minorUnit * @param null|float|int|string $textRotation * @param null|float|int|string $logBase */ public function setAxisOptionsProperties( string $axisLabels, ?string $horizontalCrossesValue = null, ?string $horizontalCrosses = null, ?string $axisOrientation = null, ?string $majorTmt = null, ?string $minorTmt = null, $minimum = null, $maximum = null, $majorUnit = null, $minorUnit = null, $textRotation = null, ?string $hidden = null, ?string $baseTimeUnit = null, ?string $majorTimeUnit = null, ?string $minorTimeUnit = null, $logBase = null, ?string $dispUnitsBuiltIn = null ): void { $this->axisOptions['axis_labels'] = $axisLabels; $this->setAxisOption('horizontal_crosses_value', $horizontalCrossesValue); $this->setAxisOption('horizontal_crosses', $horizontalCrosses); $this->setAxisOption('orientation', $axisOrientation); $this->setAxisOption('major_tick_mark', $majorTmt); $this->setAxisOption('minor_tick_mark', $minorTmt); $this->setAxisOption('minimum', $minimum); $this->setAxisOption('maximum', $maximum); $this->setAxisOption('major_unit', $majorUnit); $this->setAxisOption('minor_unit', $minorUnit); $this->setAxisOption('textRotation', $textRotation); $this->setAxisOption('hidden', $hidden); $this->setAxisOption('baseTimeUnit', $baseTimeUnit); $this->setAxisOption('majorTimeUnit', $majorTimeUnit); $this->setAxisOption('minorTimeUnit', $minorTimeUnit); $this->setAxisOption('logBase', $logBase); $this->setAxisOption('dispUnitsBuiltIn', $dispUnitsBuiltIn); } /** * Get Axis Options Property. */ public function getAxisOptionsProperty(string $property): ?string { if ($property === 'textRotation') { if ($this->axisText !== null) { if ($this->axisText->getRotation() !== null) { return (string) $this->axisText->getRotation(); } } } return $this->axisOptions[$property]; } /** * Set Axis Orientation Property. */ public function setAxisOrientation(string $orientation): void { $this->axisOptions['orientation'] = (string) $orientation; } public function getAxisType(): string { return $this->axisType; } public function setAxisType(string $type): self { if ($type === self::AXIS_TYPE_CATEGORY || $type === self::AXIS_TYPE_VALUE || $type === self::AXIS_TYPE_DATE) { $this->axisType = $type; } else { $this->axisType = ''; } return $this; } /** * Set Fill Property. */ public function setFillParameters(?string $color, ?int $alpha = null, ?string $AlphaType = ChartColor::EXCEL_COLOR_TYPE_RGB): void { $this->fillColor->setColorProperties($color, $alpha, $AlphaType); } /** * Get Fill Property. */ public function getFillProperty(string $property): string { return (string) $this->fillColor->getColorProperty($property); } public function getFillColorObject(): ChartColor { return $this->fillColor; } private string $crossBetween = ''; // 'between' or 'midCat' might be better public function setCrossBetween(string $crossBetween): self { $this->crossBetween = $crossBetween; return $this; } public function getCrossBetween(): string { return $this->crossBetween; } public function getMajorGridlines(): ?GridLines { return $this->majorGridlines; } public function getMinorGridlines(): ?GridLines { return $this->minorGridlines; } public function setMajorGridlines(?GridLines $gridlines): self { $this->majorGridlines = $gridlines; return $this; } public function setMinorGridlines(?GridLines $gridlines): self { $this->minorGridlines = $gridlines; return $this; } public function getAxisText(): ?AxisText { return $this->axisText; } public function setAxisText(?AxisText $axisText): self { $this->axisText = $axisText; return $this; } public function setNoFill(bool $noFill): self { $this->noFill = $noFill; return $this; } public function getNoFill(): bool { return $this->noFill; } public function setDispUnitsTitle(?Title $dispUnitsTitle): self { $this->dispUnitsTitle = $dispUnitsTitle; return $this; } public function getDispUnitsTitle(): ?Title { return $this->dispUnitsTitle; } /** * Implement PHP __clone to create a deep clone, not just a shallow copy. */ public function __clone() { parent::__clone(); $this->majorGridlines = ($this->majorGridlines === null) ? null : clone $this->majorGridlines; $this->majorGridlines = ($this->minorGridlines === null) ? null : clone $this->minorGridlines; $this->axisText = ($this->axisText === null) ? null : clone $this->axisText; $this->dispUnitsTitle = ($this->dispUnitsTitle === null) ? null : clone $this->dispUnitsTitle; $this->fillColor = clone $this->fillColor; } } PK!\r2libraries/vendor/PhpSpreadsheet/Chart/PlotArea.phpnu[layout = $layout; $this->plotSeries = $plotSeries; } public function getLayout(): ?Layout { return $this->layout; } /** * Get Number of Plot Groups. */ public function getPlotGroupCount(): int { return count($this->plotSeries); } /** * Get Number of Plot Series. * @return float|int */ public function getPlotSeriesCount() { $seriesCount = 0; foreach ($this->plotSeries as $plot) { $seriesCount += $plot->getPlotSeriesCount(); } return $seriesCount; } /** * Get Plot Series. * * @return DataSeries[] */ public function getPlotGroup(): array { return $this->plotSeries; } /** * Get Plot Series by Index. */ public function getPlotGroupByIndex(int $index): DataSeries { return $this->plotSeries[$index]; } /** * Set Plot Series. * * @param DataSeries[] $plotSeries * * @return $this */ public function setPlotSeries(array $plotSeries) { $this->plotSeries = $plotSeries; return $this; } public function refresh(Worksheet $worksheet): void { foreach ($this->plotSeries as $plotSeries) { $plotSeries->refresh($worksheet); } } public function setNoFill(bool $noFill): self { $this->noFill = $noFill; return $this; } public function getNoFill(): bool { return $this->noFill; } public function setGradientFillProperties(array $gradientFillStops, ?float $gradientFillAngle): self { $this->gradientFillStops = $gradientFillStops; $this->gradientFillAngle = $gradientFillAngle; return $this; } /** * Get gradientFillAngle. */ public function getGradientFillAngle(): ?float { return $this->gradientFillAngle; } /** * Get gradientFillStops. */ public function getGradientFillStops(): array { return $this->gradientFillStops; } private ?int $gapWidth = null; private bool $useUpBars = false; private bool $useDownBars = false; public function getGapWidth(): ?int { return $this->gapWidth; } public function setGapWidth(?int $gapWidth): self { $this->gapWidth = $gapWidth; return $this; } public function getUseUpBars(): bool { return $this->useUpBars; } public function setUseUpBars(bool $useUpBars): self { $this->useUpBars = $useUpBars; return $this; } public function getUseDownBars(): bool { return $this->useDownBars; } public function setUseDownBars(bool $useDownBars): self { $this->useDownBars = $useDownBars; return $this; } /** * Implement PHP __clone to create a deep clone, not just a shallow copy. */ public function __clone() { $this->layout = ($this->layout === null) ? null : clone $this->layout; $plotSeries = $this->plotSeries; $this->plotSeries = []; foreach ($plotSeries as $series) { $this->plotSeries[] = clone $series; } } } PK!/libraries/vendor/PhpSpreadsheet/Chart/index.phpnu[graph = null; $this->chart = $chart; self::$markSet = [ 'diamond' => MARK_DIAMOND, 'square' => MARK_SQUARE, 'triangle' => MARK_UTRIANGLE, 'x' => MARK_X, 'star' => MARK_STAR, 'dot' => MARK_FILLEDCIRCLE, 'dash' => MARK_DTRIANGLE, 'circle' => MARK_CIRCLE, 'plus' => MARK_CROSS, ]; } private function getGraphWidth(): float { return $this->chart->getRenderedWidth() ?? self::DEFAULT_WIDTH; } private function getGraphHeight(): float { return $this->chart->getRenderedHeight() ?? self::DEFAULT_HEIGHT; } /** * This method should be overriden in descendants to do real JpGraph library initialization. */ abstract protected static function init(): void; private function formatPointMarker($seriesPlot, $markerID) { $plotMarkKeys = array_keys(self::$markSet); if ($markerID === null) { // Use default plot marker (next marker in the series) self::$plotMark %= count(self::$markSet); $seriesPlot->mark->SetType(self::$markSet[$plotMarkKeys[self::$plotMark++]]); } elseif ($markerID !== 'none') { // Use specified plot marker (if it exists) if (isset(self::$markSet[$markerID])) { $seriesPlot->mark->SetType(self::$markSet[$markerID]); } else { // If the specified plot marker doesn't exist, use default plot marker (next marker in the series) self::$plotMark %= count(self::$markSet); $seriesPlot->mark->SetType(self::$markSet[$plotMarkKeys[self::$plotMark++]]); } } else { // Hide plot marker $seriesPlot->mark->Hide(); } $seriesPlot->mark->SetColor(self::$colourSet[self::$plotColour]); $seriesPlot->mark->SetFillColor(self::$colourSet[self::$plotColour]); $seriesPlot->SetColor(self::$colourSet[self::$plotColour++]); return $seriesPlot; } private function formatDataSetLabels(int $groupID, array $datasetLabels, $rotation = '') { $datasetLabelFormatCode = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getFormatCode() ?? ''; // Retrieve any label formatting code $datasetLabelFormatCode = stripslashes($datasetLabelFormatCode); $testCurrentIndex = 0; foreach ($datasetLabels as $i => $datasetLabel) { if (is_array($datasetLabel)) { if ($rotation == 'bar') { $datasetLabels[$i] = implode(' ', $datasetLabel); } else { $datasetLabel = array_reverse($datasetLabel); $datasetLabels[$i] = implode("\n", $datasetLabel); } } else { // Format labels according to any formatting code if ($datasetLabelFormatCode !== null) { $datasetLabels[$i] = NumberFormat::toFormattedString($datasetLabel, $datasetLabelFormatCode); } } ++$testCurrentIndex; } return $datasetLabels; } private function percentageSumCalculation(int $groupID, $seriesCount) { $sumValues = []; // Adjust our values to a percentage value across all series in the group for ($i = 0; $i < $seriesCount; ++$i) { if ($i == 0) { $sumValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); } else { $nextValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); foreach ($nextValues as $k => $value) { if (isset($sumValues[$k])) { $sumValues[$k] += $value; } else { $sumValues[$k] = $value; } } } } return $sumValues; } private function percentageAdjustValues(array $dataValues, array $sumValues) { foreach ($dataValues as $k => $dataValue) { $dataValues[$k] = $dataValue / $sumValues[$k] * 100; } return $dataValues; } private function getCaption($captionElement) { // Read any caption $caption = ($captionElement !== null) ? $captionElement->getCaption() : null; // Test if we have a title caption to display if ($caption !== null) { // If we do, it could be a plain string or an array if (is_array($caption)) { // Implode an array to a plain string $caption = implode('', $caption); } } return $caption; } private function renderTitle(): void { $title = $this->getCaption($this->chart->getTitle()); if ($title !== null) { $this->graph->title->Set($title); } } private function renderLegend(): void { $legend = $this->chart->getLegend(); if ($legend !== null) { $legendPosition = $legend->getPosition(); switch ($legendPosition) { case 'r': $this->graph->legend->SetPos(0.01, 0.5, 'right', 'center'); // right $this->graph->legend->SetColumns(1); break; case 'l': $this->graph->legend->SetPos(0.01, 0.5, 'left', 'center'); // left $this->graph->legend->SetColumns(1); break; case 't': $this->graph->legend->SetPos(0.5, 0.01, 'center', 'top'); // top break; case 'b': $this->graph->legend->SetPos(0.5, 0.99, 'center', 'bottom'); // bottom break; default: $this->graph->legend->SetPos(0.01, 0.01, 'right', 'top'); // top-right $this->graph->legend->SetColumns(1); break; } } else { $this->graph->legend->Hide(); } } private function renderCartesianPlotArea(string $type = 'textlin'): void { $this->graph = new Graph($this->getGraphWidth(), $this->getGraphHeight()); $this->graph->SetScale($type); $this->renderTitle(); // Rotate for bar rather than column chart $rotation = $this->chart->getPlotArea()->getPlotGroupByIndex(0)->getPlotDirection(); $reverse = $rotation == 'bar'; $xAxisLabel = $this->chart->getXAxisLabel(); if ($xAxisLabel !== null) { $title = $this->getCaption($xAxisLabel); if ($title !== null) { $this->graph->xaxis->SetTitle($title, 'center'); $this->graph->xaxis->title->SetMargin(35); if ($reverse) { $this->graph->xaxis->title->SetAngle(90); $this->graph->xaxis->title->SetMargin(90); } } } $yAxisLabel = $this->chart->getYAxisLabel(); if ($yAxisLabel !== null) { $title = $this->getCaption($yAxisLabel); if ($title !== null) { $this->graph->yaxis->SetTitle($title, 'center'); if ($reverse) { $this->graph->yaxis->title->SetAngle(0); $this->graph->yaxis->title->SetMargin(-55); } } } } private function renderPiePlotArea(): void { $this->graph = new PieGraph($this->getGraphWidth(), $this->getGraphHeight()); $this->renderTitle(); } private function renderRadarPlotArea(): void { $this->graph = new RadarGraph($this->getGraphWidth(), $this->getGraphHeight()); $this->graph->SetScale('lin'); $this->renderTitle(); } /** * @return mixed */ private function getDataLabel(int $groupId, int $index) { $plotLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupId)->getPlotLabelByIndex($index); if (!$plotLabel) { return ''; } return $plotLabel->getDataValue(); } private function renderPlotLine(int $groupID, bool $filled = false, bool $combination = false): void { $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping(); $index = array_keys($this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder())[0]; $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getPointCount(); if ($labelCount > 0) { $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues(); $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels); $this->graph->xaxis->SetTickLabels($datasetLabels); } $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); $seriesPlots = []; if ($grouping == 'percentStacked') { $sumValues = $this->percentageSumCalculation($groupID, $seriesCount); } else { $sumValues = []; } // Loop through each data series in turn for ($i = 0; $i < $seriesCount; ++$i) { $index = array_keys($this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder())[$i]; $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getDataValues(); $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getPointMarker(); if ($grouping == 'percentStacked') { $dataValues = $this->percentageAdjustValues($dataValues, $sumValues); } // Fill in any missing values in the $dataValues array $testCurrentIndex = 0; foreach ($dataValues as $k => $dataValue) { while ($k != $testCurrentIndex) { $dataValues[$testCurrentIndex] = null; ++$testCurrentIndex; } ++$testCurrentIndex; } $seriesPlot = new LinePlot($dataValues); if ($combination) { $seriesPlot->SetBarCenter(); } if ($filled) { $seriesPlot->SetFilled(true); $seriesPlot->SetColor('black'); $seriesPlot->SetFillColor(self::$colourSet[self::$plotColour++]); } else { // Set the appropriate plot marker $this->formatPointMarker($seriesPlot, $marker); } $seriesPlot->SetLegend($this->getDataLabel($groupID, $index)); $seriesPlots[] = $seriesPlot; } if ($grouping == 'standard') { $groupPlot = $seriesPlots; } else { $groupPlot = new AccLinePlot($seriesPlots); } $this->graph->Add($groupPlot); } private function renderPlotBar(int $groupID, ?string $dimensions = '2d'): void { $rotation = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotDirection(); // Rotate for bar rather than column chart if (($groupID == 0) && ($rotation == 'bar')) { $this->graph->Set90AndMargin(); } $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping(); $index = array_keys($this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder())[0]; $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getPointCount(); if ($labelCount > 0) { $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues(); $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $rotation); // Rotate for bar rather than column chart if ($rotation == 'bar') { $datasetLabels = array_reverse($datasetLabels); $this->graph->yaxis->SetPos('max'); $this->graph->yaxis->SetLabelAlign('center', 'top'); $this->graph->yaxis->SetLabelSide(SIDE_RIGHT); } $this->graph->xaxis->SetTickLabels($datasetLabels); } $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); $seriesPlots = []; if ($grouping == 'percentStacked') { $sumValues = $this->percentageSumCalculation($groupID, $seriesCount); } else { $sumValues = []; } // Loop through each data series in turn for ($j = 0; $j < $seriesCount; ++$j) { $index = array_keys($this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder())[$j]; $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getDataValues(); if ($grouping == 'percentStacked') { $dataValues = $this->percentageAdjustValues($dataValues, $sumValues); } // Fill in any missing values in the $dataValues array $testCurrentIndex = 0; foreach ($dataValues as $k => $dataValue) { while ($k != $testCurrentIndex) { $dataValues[$testCurrentIndex] = null; ++$testCurrentIndex; } ++$testCurrentIndex; } // Reverse the $dataValues order for bar rather than column chart if ($rotation == 'bar') { $dataValues = array_reverse($dataValues); } $seriesPlot = new BarPlot($dataValues); $seriesPlot->SetColor('black'); $seriesPlot->SetFillColor(self::$colourSet[self::$plotColour++]); if ($dimensions == '3d') { $seriesPlot->SetShadow(); } $seriesPlot->SetLegend($this->getDataLabel($groupID, $j)); $seriesPlots[] = $seriesPlot; } // Reverse the plot order for bar rather than column chart if (($rotation == 'bar') && ($grouping != 'percentStacked')) { $seriesPlots = array_reverse($seriesPlots); } if ($grouping == 'clustered') { $groupPlot = new GroupBarPlot($seriesPlots); } elseif ($grouping == 'standard') { $groupPlot = new GroupBarPlot($seriesPlots); } else { $groupPlot = new AccBarPlot($seriesPlots); if ($dimensions == '3d') { $groupPlot->SetShadow(); } } $this->graph->Add($groupPlot); } private function renderPlotScatter(int $groupID, bool $bubble): void { $scatterStyle = $bubbleSize = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle(); $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); // Loop through each data series in turn for ($i = 0; $i < $seriesCount; ++$i) { $plotCategoryByIndex = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i); if ($plotCategoryByIndex === false) { $plotCategoryByIndex = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0); } $dataValuesY = $plotCategoryByIndex->getDataValues(); $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); $redoDataValuesY = true; if ($bubble) { if (!$bubbleSize) { $bubbleSize = '10'; } $redoDataValuesY = false; foreach ($dataValuesY as $dataValueY) { if (!is_int($dataValueY) && !is_float($dataValueY)) { $redoDataValuesY = true; break; } } } if ($redoDataValuesY) { foreach ($dataValuesY as $k => $dataValueY) { $dataValuesY[$k] = $k; } } $seriesPlot = new ScatterPlot($dataValuesX, $dataValuesY); if ($scatterStyle == 'lineMarker') { $seriesPlot->SetLinkPoints(); $seriesPlot->link->SetColor(self::$colourSet[self::$plotColour]); } elseif ($scatterStyle == 'smoothMarker') { $spline = new Spline($dataValuesY, $dataValuesX); [$splineDataY, $splineDataX] = $spline->Get(count($dataValuesX) * $this->getGraphWidth() / 20); $lplot = new LinePlot($splineDataX, $splineDataY); $lplot->SetColor(self::$colourSet[self::$plotColour]); $this->graph->Add($lplot); } if ($bubble) { $this->formatPointMarker($seriesPlot, 'dot'); $seriesPlot->mark->SetColor('black'); $seriesPlot->mark->SetSize($bubbleSize); } else { $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker(); $this->formatPointMarker($seriesPlot, $marker); } $seriesPlot->SetLegend($this->getDataLabel($groupID, $i)); $this->graph->Add($seriesPlot); } } private function renderPlotRadar(int $groupID): void { $radarStyle = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle(); $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); // Loop through each data series in turn for ($i = 0; $i < $seriesCount; ++$i) { $dataValuesY = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i)->getDataValues(); $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker(); $dataValues = []; foreach ($dataValuesY as $k => $dataValueY) { $dataValues[$k] = is_array($dataValueY) ? implode(' ', array_reverse($dataValueY)) : $dataValueY; } $tmp = array_shift($dataValues); $dataValues[] = $tmp; $tmp = array_shift($dataValuesX); $dataValuesX[] = $tmp; $this->graph->SetTitles(array_reverse($dataValues)); $seriesPlot = new RadarPlot(array_reverse($dataValuesX)); $seriesPlot->SetColor(self::$colourSet[self::$plotColour++]); if ($radarStyle == 'filled') { $seriesPlot->SetFillColor(self::$colourSet[self::$plotColour]); } $this->formatPointMarker($seriesPlot, $marker); $seriesPlot->SetLegend($this->getDataLabel($groupID, $i)); $this->graph->Add($seriesPlot); } } private function renderPlotContour(int $groupID): void { $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); $dataValues = []; // Loop through each data series in turn for ($i = 0; $i < $seriesCount; ++$i) { $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); $dataValues[$i] = $dataValuesX; } $seriesPlot = new ContourPlot($dataValues); $this->graph->Add($seriesPlot); } private function renderPlotStock(int $groupID): void { $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); $plotOrder = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder(); $dataValues = []; // Loop through each data series in turn and build the plot arrays foreach ($plotOrder as $i => $v) { $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($v); if ($dataValuesX === false) { continue; } $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($v)->getDataValues(); foreach ($dataValuesX as $j => $dataValueX) { $dataValues[$plotOrder[$i]][$j] = $dataValueX; } } if (empty($dataValues)) { return; } $dataValuesPlot = []; // Flatten the plot arrays to a single dimensional array to work with jpgraph $jMax = count($dataValues[0]); for ($j = 0; $j < $jMax; ++$j) { for ($i = 0; $i < $seriesCount; ++$i) { $dataValuesPlot[] = $dataValues[$i][$j] ?? null; } } // Set the x-axis labels $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount(); if ($labelCount > 0) { $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues(); $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels); $this->graph->xaxis->SetTickLabels($datasetLabels); } $seriesPlot = new StockPlot($dataValuesPlot); $seriesPlot->SetWidth(20); $this->graph->Add($seriesPlot); } private function renderAreaChart($groupCount): void { $this->renderCartesianPlotArea(); for ($i = 0; $i < $groupCount; ++$i) { $this->renderPlotLine($i, true, false); } } private function renderLineChart($groupCount): void { $this->renderCartesianPlotArea(); for ($i = 0; $i < $groupCount; ++$i) { $this->renderPlotLine($i, false, false); } } private function renderBarChart($groupCount, ?string $dimensions = '2d'): void { $this->renderCartesianPlotArea(); for ($i = 0; $i < $groupCount; ++$i) { $this->renderPlotBar($i, $dimensions); } } private function renderScatterChart($groupCount): void { $this->renderCartesianPlotArea('linlin'); for ($i = 0; $i < $groupCount; ++$i) { $this->renderPlotScatter($i, false); } } private function renderBubbleChart($groupCount): void { $this->renderCartesianPlotArea('linlin'); for ($i = 0; $i < $groupCount; ++$i) { $this->renderPlotScatter($i, true); } } private function renderPieChart($groupCount, ?string $dimensions = '2d', bool $doughnut = false, bool $multiplePlots = false): void { $this->renderPiePlotArea(); $iLimit = ($multiplePlots) ? $groupCount : 1; for ($groupID = 0; $groupID < $iLimit; ++$groupID) { $exploded = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle(); $datasetLabels = []; if ($groupID == 0) { $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount(); if ($labelCount > 0) { $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues(); $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels); } } $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); // For pie charts, we only display the first series: doughnut charts generally display all series $jLimit = ($multiplePlots) ? $seriesCount : 1; // Loop through each data series in turn for ($j = 0; $j < $jLimit; ++$j) { $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($j)->getDataValues(); // Fill in any missing values in the $dataValues array $testCurrentIndex = 0; foreach ($dataValues as $k => $dataValue) { while ($k != $testCurrentIndex) { $dataValues[$testCurrentIndex] = null; ++$testCurrentIndex; } ++$testCurrentIndex; } if ($dimensions == '3d') { $seriesPlot = new PiePlot3D($dataValues); } else { if ($doughnut) { $seriesPlot = new PiePlotC($dataValues); } else { $seriesPlot = new PiePlot($dataValues); } } if ($multiplePlots) { $seriesPlot->SetSize(($jLimit - $j) / ($jLimit * 4)); } if ($doughnut && method_exists($seriesPlot, 'SetMidColor')) { $seriesPlot->SetMidColor('white'); } $seriesPlot->SetColor(self::$colourSet[self::$plotColour++]); if (count($datasetLabels) > 0) { $seriesPlot->SetLabels(array_fill(0, count($datasetLabels), '')); } if ($dimensions != '3d') { $seriesPlot->SetGuideLines(false); } if ($j == 0) { if ($exploded) { $seriesPlot->ExplodeAll(); } $seriesPlot->SetLegends($datasetLabels); } $this->graph->Add($seriesPlot); } } } private function renderRadarChart($groupCount): void { $this->renderRadarPlotArea(); for ($groupID = 0; $groupID < $groupCount; ++$groupID) { $this->renderPlotRadar($groupID); } } private function renderStockChart($groupCount): void { $this->renderCartesianPlotArea('intint'); for ($groupID = 0; $groupID < $groupCount; ++$groupID) { $this->renderPlotStock($groupID); } } private function renderContourChart($groupCount): void { $this->renderCartesianPlotArea('intint'); for ($i = 0; $i < $groupCount; ++$i) { $this->renderPlotContour($i); } } private function renderCombinationChart($groupCount, $outputDestination): bool { $this->renderCartesianPlotArea(); for ($i = 0; $i < $groupCount; ++$i) { $dimensions = null; $chartType = $this->chart->getPlotArea()->getPlotGroupByIndex($i)->getPlotType(); switch ($chartType) { case 'area3DChart': case 'areaChart': $this->renderPlotLine($i, true, true); break; case 'bar3DChart': $dimensions = '3d'; // no break case 'barChart': $this->renderPlotBar($i, $dimensions); break; case 'line3DChart': case 'lineChart': $this->renderPlotLine($i, false, true); break; case 'scatterChart': $this->renderPlotScatter($i, false); break; case 'bubbleChart': $this->renderPlotScatter($i, true); break; default: $this->graph = null; return false; } } $this->renderLegend(); $this->graph->Stroke($outputDestination); return true; } public function render(?string $outputDestination): bool { self::$plotColour = 0; $groupCount = $this->chart->getPlotArea()->getPlotGroupCount(); $dimensions = null; if ($groupCount == 1) { $chartType = $this->chart->getPlotArea()->getPlotGroupByIndex(0)->getPlotType(); } else { $chartTypes = []; for ($i = 0; $i < $groupCount; ++$i) { $chartTypes[] = $this->chart->getPlotArea()->getPlotGroupByIndex($i)->getPlotType(); } $chartTypes = array_unique($chartTypes); if (count($chartTypes) == 1) { $chartType = array_pop($chartTypes); } elseif (count($chartTypes) == 0) { echo 'Chart is not yet implemented
    '; return false; } else { return $this->renderCombinationChart($groupCount, $outputDestination); } } switch ($chartType) { case 'area3DChart': $dimensions = '3d'; // no break case 'areaChart': $this->renderAreaChart($groupCount); break; case 'bar3DChart': $dimensions = '3d'; // no break case 'barChart': $this->renderBarChart($groupCount, $dimensions); break; case 'line3DChart': $dimensions = '3d'; // no break case 'lineChart': $this->renderLineChart($groupCount); break; case 'pie3DChart': $dimensions = '3d'; // no break case 'pieChart': $this->renderPieChart($groupCount, $dimensions, false, false); break; case 'doughnut3DChart': $dimensions = '3d'; // no break case 'doughnutChart': $this->renderPieChart($groupCount, $dimensions, true, true); break; case 'scatterChart': $this->renderScatterChart($groupCount); break; case 'bubbleChart': $this->renderBubbleChart($groupCount); break; case 'radarChart': $this->renderRadarChart($groupCount); break; case 'surface3DChart': case 'surfaceChart': $this->renderContourChart($groupCount); break; case 'stockChart': $this->renderStockChart($groupCount); break; default: echo $chartType . ' is not yet implemented
    '; return false; } $this->renderLegend(); $this->graph->Stroke($outputDestination); return true; } } PK!43libraries/vendor/PhpSpreadsheet/Chart/TrendLine.phpnu[setTrendLineProperties( $trendLineType, $order, $period, $dispRSqr, $dispEq, $backward, $forward, $intercept, $name ); } public function getTrendLineType(): string { return $this->trendLineType; } public function setTrendLineType(string $trendLineType): self { $this->trendLineType = $trendLineType; return $this; } public function getOrder(): int { return $this->order; } public function setOrder(int $order): self { $this->order = $order; return $this; } public function getPeriod(): int { return $this->period; } public function setPeriod(int $period): self { $this->period = $period; return $this; } public function getDispRSqr(): bool { return $this->dispRSqr; } public function setDispRSqr(bool $dispRSqr): self { $this->dispRSqr = $dispRSqr; return $this; } public function getDispEq(): bool { return $this->dispEq; } public function setDispEq(bool $dispEq): self { $this->dispEq = $dispEq; return $this; } public function getName(): string { return $this->name; } public function setName(string $name): self { $this->name = $name; return $this; } public function getBackward(): float { return $this->backward; } public function setBackward(float $backward): self { $this->backward = $backward; return $this; } public function getForward(): float { return $this->forward; } public function setForward(float $forward): self { $this->forward = $forward; return $this; } public function getIntercept(): float { return $this->intercept; } public function setIntercept(float $intercept): self { $this->intercept = $intercept; return $this; } public function setTrendLineProperties( ?string $trendLineType = null, ?int $order = 0, ?int $period = 0, ?bool $dispRSqr = false, ?bool $dispEq = false, ?float $backward = null, ?float $forward = null, ?float $intercept = null, ?string $name = null ): self { if (!empty($trendLineType)) { $this->setTrendLineType($trendLineType); } if ($order !== null) { $this->setOrder($order); } if ($period !== null) { $this->setPeriod($period); } if ($dispRSqr !== null) { $this->setDispRSqr($dispRSqr); } if ($dispEq !== null) { $this->setDispEq($dispEq); } if ($backward !== null) { $this->setBackward($backward); } if ($forward !== null) { $this->setForward($forward); } if ($intercept !== null) { $this->setIntercept($intercept); } if ($name !== null) { $this->setName($name); } return $this; } } PK!esWdWd4libraries/vendor/PhpSpreadsheet/Chart/Properties.phpnu[ null, ]; protected array $shadowProperties = self::PRESETS_OPTIONS[0]; protected ChartColor $shadowColor; public function __construct() { $this->lineColor = new ChartColor(); $this->glowColor = new ChartColor(); $this->shadowColor = new ChartColor(); $this->shadowColor->setType(ChartColor::EXCEL_COLOR_TYPE_STANDARD); $this->shadowColor->setValue('black'); $this->shadowColor->setAlpha(40); } /** * Get Object State. */ public function getObjectState(): bool { return $this->objectState; } /** * Change Object State to True. * * @return $this */ public function activateObject() { $this->objectState = true; return $this; } public static function pointsToXml(float $width): string { return (string) (int) ($width * self::POINTS_WIDTH_MULTIPLIER); } public static function xmlToPoints(string $width): float { return ((float) $width) / self::POINTS_WIDTH_MULTIPLIER; } public static function angleToXml(float $angle): string { return (string) (int) ($angle * self::ANGLE_MULTIPLIER); } public static function xmlToAngle(string $angle): float { return ((float) $angle) / self::ANGLE_MULTIPLIER; } public static function tenthOfPercentToXml(float $value): string { return (string) (int) ($value * self::PERCENTAGE_MULTIPLIER); } public static function xmlToTenthOfPercent(string $value): float { return ((float) $value) / self::PERCENTAGE_MULTIPLIER; } /** * @param null|float|int|string $alpha */ protected function setColorProperties(?string $color, $alpha, ?string $colorType): array { return [ 'type' => $colorType, 'value' => $color, 'alpha' => ($alpha === null) ? null : (int) $alpha, ]; } protected const PRESETS_OPTIONS = [ //NONE 0 => [ 'presets' => self::SHADOW_PRESETS_NOSHADOW, 'effect' => null, //'color' => [ // 'type' => ChartColor::EXCEL_COLOR_TYPE_STANDARD, // 'value' => 'black', // 'alpha' => 40, //], 'size' => [ 'sx' => null, 'sy' => null, 'kx' => null, 'ky' => null, ], 'blur' => null, 'direction' => null, 'distance' => null, 'algn' => null, 'rotWithShape' => null, ], //OUTER 1 => [ 'effect' => 'outerShdw', 'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER, 'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER, 'direction' => 2700000 / self::ANGLE_MULTIPLIER, 'algn' => 'tl', 'rotWithShape' => '0', ], 2 => [ 'effect' => 'outerShdw', 'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER, 'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER, 'direction' => 5400000 / self::ANGLE_MULTIPLIER, 'algn' => 't', 'rotWithShape' => '0', ], 3 => [ 'effect' => 'outerShdw', 'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER, 'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER, 'direction' => 8100000 / self::ANGLE_MULTIPLIER, 'algn' => 'tr', 'rotWithShape' => '0', ], 4 => [ 'effect' => 'outerShdw', 'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER, 'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER, 'algn' => 'l', 'rotWithShape' => '0', ], 5 => [ 'effect' => 'outerShdw', 'size' => [ 'sx' => 102000 / self::PERCENTAGE_MULTIPLIER, 'sy' => 102000 / self::PERCENTAGE_MULTIPLIER, ], 'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER, 'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER, 'algn' => 'ctr', 'rotWithShape' => '0', ], 6 => [ 'effect' => 'outerShdw', 'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER, 'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER, 'direction' => 10800000 / self::ANGLE_MULTIPLIER, 'algn' => 'r', 'rotWithShape' => '0', ], 7 => [ 'effect' => 'outerShdw', 'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER, 'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER, 'direction' => 18900000 / self::ANGLE_MULTIPLIER, 'algn' => 'bl', 'rotWithShape' => '0', ], 8 => [ 'effect' => 'outerShdw', 'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER, 'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER, 'direction' => 16200000 / self::ANGLE_MULTIPLIER, 'rotWithShape' => '0', ], 9 => [ 'effect' => 'outerShdw', 'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER, 'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER, 'direction' => 13500000 / self::ANGLE_MULTIPLIER, 'algn' => 'br', 'rotWithShape' => '0', ], //INNER 10 => [ 'effect' => 'innerShdw', 'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER, 'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER, 'direction' => 2700000 / self::ANGLE_MULTIPLIER, ], 11 => [ 'effect' => 'innerShdw', 'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER, 'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER, 'direction' => 5400000 / self::ANGLE_MULTIPLIER, ], 12 => [ 'effect' => 'innerShdw', 'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER, 'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER, 'direction' => 8100000 / self::ANGLE_MULTIPLIER, ], 13 => [ 'effect' => 'innerShdw', 'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER, 'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER, ], 14 => [ 'effect' => 'innerShdw', 'blur' => 114300 / self::POINTS_WIDTH_MULTIPLIER, ], 15 => [ 'effect' => 'innerShdw', 'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER, 'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER, 'direction' => 10800000 / self::ANGLE_MULTIPLIER, ], 16 => [ 'effect' => 'innerShdw', 'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER, 'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER, 'direction' => 18900000 / self::ANGLE_MULTIPLIER, ], 17 => [ 'effect' => 'innerShdw', 'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER, 'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER, 'direction' => 16200000 / self::ANGLE_MULTIPLIER, ], 18 => [ 'effect' => 'innerShdw', 'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER, 'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER, 'direction' => 13500000 / self::ANGLE_MULTIPLIER, ], //perspective 19 => [ 'effect' => 'outerShdw', 'blur' => 152400 / self::POINTS_WIDTH_MULTIPLIER, 'distance' => 317500 / self::POINTS_WIDTH_MULTIPLIER, 'size' => [ 'sx' => 90000 / self::PERCENTAGE_MULTIPLIER, 'sy' => -19000 / self::PERCENTAGE_MULTIPLIER, ], 'direction' => 5400000 / self::ANGLE_MULTIPLIER, 'rotWithShape' => '0', ], 20 => [ 'effect' => 'outerShdw', 'blur' => 76200 / self::POINTS_WIDTH_MULTIPLIER, 'direction' => 18900000 / self::ANGLE_MULTIPLIER, 'size' => [ 'sy' => 23000 / self::PERCENTAGE_MULTIPLIER, 'kx' => -1200000 / self::ANGLE_MULTIPLIER, ], 'algn' => 'bl', 'rotWithShape' => '0', ], 21 => [ 'effect' => 'outerShdw', 'blur' => 76200 / self::POINTS_WIDTH_MULTIPLIER, 'direction' => 13500000 / self::ANGLE_MULTIPLIER, 'size' => [ 'sy' => 23000 / self::PERCENTAGE_MULTIPLIER, 'kx' => 1200000 / self::ANGLE_MULTIPLIER, ], 'algn' => 'br', 'rotWithShape' => '0', ], 22 => [ 'effect' => 'outerShdw', 'blur' => 76200 / self::POINTS_WIDTH_MULTIPLIER, 'distance' => 12700 / self::POINTS_WIDTH_MULTIPLIER, 'direction' => 2700000 / self::ANGLE_MULTIPLIER, 'size' => [ 'sy' => -23000 / self::PERCENTAGE_MULTIPLIER, 'kx' => -800400 / self::ANGLE_MULTIPLIER, ], 'algn' => 'bl', 'rotWithShape' => '0', ], 23 => [ 'effect' => 'outerShdw', 'blur' => 76200 / self::POINTS_WIDTH_MULTIPLIER, 'distance' => 12700 / self::POINTS_WIDTH_MULTIPLIER, 'direction' => 8100000 / self::ANGLE_MULTIPLIER, 'size' => [ 'sy' => -23000 / self::PERCENTAGE_MULTIPLIER, 'kx' => 800400 / self::ANGLE_MULTIPLIER, ], 'algn' => 'br', 'rotWithShape' => '0', ], ]; protected function getShadowPresetsMap(int $presetsOption): array { return self::PRESETS_OPTIONS[$presetsOption] ?? self::PRESETS_OPTIONS[0]; } /** * Get value of array element. * @param mixed[]|int|string $elements * @return mixed */ protected function getArrayElementsValue(array $properties, $elements) { $reference = &$properties; if (!is_array($elements)) { return $reference[$elements]; } foreach ($elements as $keys) { $reference = &$reference[$keys]; } return $reference; } /** * Set Glow Properties. */ public function setGlowProperties(float $size, ?string $colorValue = null, ?int $colorAlpha = null, ?string $colorType = null): void { $this ->activateObject() ->setGlowSize($size); $this->glowColor->setColorPropertiesArray( [ 'value' => $colorValue, 'type' => $colorType, 'alpha' => $colorAlpha, ] ); } /** * Get Glow Property. * @param mixed[]|string $property * @return mixed[]|float|int|string|null */ public function getGlowProperty($property) { $retVal = null; if ($property === 'size') { $retVal = $this->glowSize; } elseif ($property === 'color') { $retVal = [ 'value' => $this->glowColor->getColorProperty('value'), 'type' => $this->glowColor->getColorProperty('type'), 'alpha' => $this->glowColor->getColorProperty('alpha'), ]; } elseif (is_array($property) && count($property) >= 2 && $property[0] === 'color') { $retVal = $this->glowColor->getColorProperty($property[1]); } return $retVal; } /** * Get Glow Color Property. * @return int|string|null */ public function getGlowColor(string $propertyName) { return $this->glowColor->getColorProperty($propertyName); } public function getGlowColorObject(): ChartColor { return $this->glowColor; } /** * Get Glow Size. */ public function getGlowSize(): ?float { return $this->glowSize; } /** * Set Glow Size. * * @return $this */ protected function setGlowSize(?float $size) { $this->glowSize = $size; return $this; } /** * Set Soft Edges Size. */ public function setSoftEdges(?float $size): void { if ($size !== null) { $this->activateObject(); $this->softEdges['size'] = $size; } } /** * Get Soft Edges Size. */ public function getSoftEdgesSize(): ?float { return $this->softEdges['size']; } /** * @param mixed $value */ public function setShadowProperty(string $propertyName, $value): self { $this->activateObject(); if ($propertyName === 'color' && is_array($value)) { $this->shadowColor->setColorPropertiesArray($value); } else { $this->shadowProperties[$propertyName] = $value; } return $this; } /** * Set Shadow Properties. * @param null|float|int|string $colorAlpha */ public function setShadowProperties(int $presets, ?string $colorValue = null, ?string $colorType = null, $colorAlpha = null, ?float $blur = null, ?int $angle = null, ?float $distance = null): void { $this->activateObject()->setShadowPresetsProperties((int) $presets); if ($presets === 0) { $this->shadowColor->setType(ChartColor::EXCEL_COLOR_TYPE_STANDARD); $this->shadowColor->setValue('black'); $this->shadowColor->setAlpha(40); } if ($colorValue !== null) { $this->shadowColor->setValue($colorValue); } if ($colorType !== null) { $this->shadowColor->setType($colorType); } if (is_numeric($colorAlpha)) { $this->shadowColor->setAlpha((int) $colorAlpha); } $this ->setShadowBlur($blur) ->setShadowAngle($angle) ->setShadowDistance($distance); } /** * Set Shadow Presets Properties. * * @return $this */ protected function setShadowPresetsProperties(int $presets) { $this->shadowProperties['presets'] = $presets; $this->setShadowPropertiesMapValues($this->getShadowPresetsMap($presets)); return $this; } protected const SHADOW_ARRAY_KEYS = ['size', 'color']; /** * Set Shadow Properties Values. * * @return $this */ protected function setShadowPropertiesMapValues(array $propertiesMap, ?array &$reference = null) { $base_reference = $reference; foreach ($propertiesMap as $property_key => $property_val) { if (is_array($property_val)) { if (in_array($property_key, self::SHADOW_ARRAY_KEYS, true)) { $reference = &$this->shadowProperties[$property_key]; $this->setShadowPropertiesMapValues($property_val, $reference); } } else { if ($base_reference === null) { $this->shadowProperties[$property_key] = $property_val; } else { $reference[$property_key] = $property_val; } } } return $this; } /** * Set Shadow Blur. * * @return $this */ protected function setShadowBlur(?float $blur) { if ($blur !== null) { $this->shadowProperties['blur'] = $blur; } return $this; } /** * Set Shadow Angle. * * @return $this * @param null|float|int|string $angle */ protected function setShadowAngle($angle) { if (is_numeric($angle)) { $this->shadowProperties['direction'] = $angle; } return $this; } /** * Set Shadow Distance. * * @return $this */ protected function setShadowDistance(?float $distance) { if ($distance !== null) { $this->shadowProperties['distance'] = $distance; } return $this; } public function getShadowColorObject(): ChartColor { return $this->shadowColor; } /** * Get Shadow Property. * * @param string|string[] $elements * @return mixed[]|string|null */ public function getShadowProperty($elements) { if ($elements === 'color') { return [ 'value' => $this->shadowColor->getValue(), 'type' => $this->shadowColor->getType(), 'alpha' => $this->shadowColor->getAlpha(), ]; } $retVal = $this->getArrayElementsValue($this->shadowProperties, $elements); if (is_scalar($retVal)) { $retVal = (string) $retVal; } elseif ($retVal !== null && !is_array($retVal)) { // @codeCoverageIgnoreStart throw new Exception('Unexpected value for shadowProperty'); // @codeCoverageIgnoreEnd } return $retVal; } public function getShadowArray(): array { $array = $this->shadowProperties; if ($this->getShadowColorObject()->isUsable()) { $array['color'] = $this->getShadowProperty('color'); } return $array; } protected ChartColor $lineColor; protected array $lineStyleProperties = [ 'width' => null, //'9525', 'compound' => '', //self::LINE_STYLE_COMPOUND_SIMPLE, 'dash' => '', //self::LINE_STYLE_DASH_SOLID, 'cap' => '', //self::LINE_STYLE_CAP_FLAT, 'join' => '', //self::LINE_STYLE_JOIN_BEVEL, 'arrow' => [ 'head' => [ 'type' => '', //self::LINE_STYLE_ARROW_TYPE_NOARROW, 'size' => '', //self::LINE_STYLE_ARROW_SIZE_5, 'w' => '', 'len' => '', ], 'end' => [ 'type' => '', //self::LINE_STYLE_ARROW_TYPE_NOARROW, 'size' => '', //self::LINE_STYLE_ARROW_SIZE_8, 'w' => '', 'len' => '', ], ], ]; public function copyLineStyles(self $otherProperties): void { $this->lineStyleProperties = $otherProperties->lineStyleProperties; $this->lineColor = $otherProperties->lineColor; $this->glowSize = $otherProperties->glowSize; $this->glowColor = $otherProperties->glowColor; $this->softEdges = $otherProperties->softEdges; $this->shadowProperties = $otherProperties->shadowProperties; } public function getLineColor(): ChartColor { return $this->lineColor; } /** * Set Line Color Properties. */ public function setLineColorProperties(?string $value, ?int $alpha = null, ?string $colorType = null): void { $this->activateObject(); $this->lineColor->setColorPropertiesArray( $this->setColorProperties( $value, $alpha, $colorType ) ); } /** * Get Line Color Property. * @return int|string|null */ public function getLineColorProperty(string $propertyName) { return $this->lineColor->getColorProperty($propertyName); } /** * Set Line Style Properties. * @param null|float|int|string $lineWidth */ public function setLineStyleProperties( $lineWidth = null, ?string $compoundType = '', ?string $dashType = '', ?string $capType = '', ?string $joinType = '', ?string $headArrowType = '', int $headArrowSize = 0, ?string $endArrowType = '', int $endArrowSize = 0, ?string $headArrowWidth = '', ?string $headArrowLength = '', ?string $endArrowWidth = '', ?string $endArrowLength = '' ): void { $this->activateObject(); if (is_numeric($lineWidth)) { $this->lineStyleProperties['width'] = $lineWidth; } if ($compoundType !== '') { $this->lineStyleProperties['compound'] = $compoundType; } if ($dashType !== '') { $this->lineStyleProperties['dash'] = $dashType; } if ($capType !== '') { $this->lineStyleProperties['cap'] = $capType; } if ($joinType !== '') { $this->lineStyleProperties['join'] = $joinType; } if ($headArrowType !== '') { $this->lineStyleProperties['arrow']['head']['type'] = $headArrowType; } if (isset(self::ARROW_SIZES[$headArrowSize])) { $this->lineStyleProperties['arrow']['head']['size'] = $headArrowSize; $this->lineStyleProperties['arrow']['head']['w'] = self::ARROW_SIZES[$headArrowSize]['w']; $this->lineStyleProperties['arrow']['head']['len'] = self::ARROW_SIZES[$headArrowSize]['len']; } if ($endArrowType !== '') { $this->lineStyleProperties['arrow']['end']['type'] = $endArrowType; } if (isset(self::ARROW_SIZES[$endArrowSize])) { $this->lineStyleProperties['arrow']['end']['size'] = $endArrowSize; $this->lineStyleProperties['arrow']['end']['w'] = self::ARROW_SIZES[$endArrowSize]['w']; $this->lineStyleProperties['arrow']['end']['len'] = self::ARROW_SIZES[$endArrowSize]['len']; } if ($headArrowWidth !== '') { $this->lineStyleProperties['arrow']['head']['w'] = $headArrowWidth; } if ($headArrowLength !== '') { $this->lineStyleProperties['arrow']['head']['len'] = $headArrowLength; } if ($endArrowWidth !== '') { $this->lineStyleProperties['arrow']['end']['w'] = $endArrowWidth; } if ($endArrowLength !== '') { $this->lineStyleProperties['arrow']['end']['len'] = $endArrowLength; } } public function getLineStyleArray(): array { return $this->lineStyleProperties; } public function setLineStyleArray(array $lineStyleProperties = []): self { $this->activateObject(); $this->lineStyleProperties['width'] = $lineStyleProperties['width'] ?? null; $this->lineStyleProperties['compound'] = $lineStyleProperties['compound'] ?? ''; $this->lineStyleProperties['dash'] = $lineStyleProperties['dash'] ?? ''; $this->lineStyleProperties['cap'] = $lineStyleProperties['cap'] ?? ''; $this->lineStyleProperties['join'] = $lineStyleProperties['join'] ?? ''; $this->lineStyleProperties['arrow']['head']['type'] = $lineStyleProperties['arrow']['head']['type'] ?? ''; $this->lineStyleProperties['arrow']['head']['size'] = $lineStyleProperties['arrow']['head']['size'] ?? ''; $this->lineStyleProperties['arrow']['head']['w'] = $lineStyleProperties['arrow']['head']['w'] ?? ''; $this->lineStyleProperties['arrow']['head']['len'] = $lineStyleProperties['arrow']['head']['len'] ?? ''; $this->lineStyleProperties['arrow']['end']['type'] = $lineStyleProperties['arrow']['end']['type'] ?? ''; $this->lineStyleProperties['arrow']['end']['size'] = $lineStyleProperties['arrow']['end']['size'] ?? ''; $this->lineStyleProperties['arrow']['end']['w'] = $lineStyleProperties['arrow']['end']['w'] ?? ''; $this->lineStyleProperties['arrow']['end']['len'] = $lineStyleProperties['arrow']['end']['len'] ?? ''; return $this; } /** * @param mixed $value */ public function setLineStyleProperty(string $propertyName, $value): self { $this->activateObject(); $this->lineStyleProperties[$propertyName] = $value; return $this; } /** * Get Line Style Property. * @param mixed[]|string $elements */ public function getLineStyleProperty($elements): ?string { $retVal = $this->getArrayElementsValue($this->lineStyleProperties, $elements); if (is_scalar($retVal)) { $retVal = (string) $retVal; } elseif ($retVal !== null) { // @codeCoverageIgnoreStart throw new Exception('Unexpected value for lineStyleProperty'); // @codeCoverageIgnoreEnd } return $retVal; } protected const ARROW_SIZES = [ 1 => ['w' => 'sm', 'len' => 'sm'], 2 => ['w' => 'sm', 'len' => 'med'], 3 => ['w' => 'sm', 'len' => 'lg'], 4 => ['w' => 'med', 'len' => 'sm'], 5 => ['w' => 'med', 'len' => 'med'], 6 => ['w' => 'med', 'len' => 'lg'], 7 => ['w' => 'lg', 'len' => 'sm'], 8 => ['w' => 'lg', 'len' => 'med'], 9 => ['w' => 'lg', 'len' => 'lg'], ]; /** * Get Line Style Arrow Size. */ protected function getLineStyleArrowSize(int $arraySelector, string $arrayKaySelector): string { return self::ARROW_SIZES[$arraySelector][$arrayKaySelector] ?? ''; } /** * Get Line Style Arrow Parameters. */ public function getLineStyleArrowParameters(string $arrowSelector, string $propertySelector): string { return $this->getLineStyleArrowSize($this->lineStyleProperties['arrow'][$arrowSelector]['size'], $propertySelector); } /** * Get Line Style Arrow Width. */ public function getLineStyleArrowWidth(string $arrow): ?string { return $this->getLineStyleProperty(['arrow', $arrow, 'w']); } /** * Get Line Style Arrow Excel Length. */ public function getLineStyleArrowLength(string $arrow): ?string { return $this->getLineStyleProperty(['arrow', $arrow, 'len']); } /** * Implement PHP __clone to create a deep clone, not just a shallow copy. */ public function __clone() { $this->lineColor = clone $this->lineColor; $this->glowColor = clone $this->glowColor; $this->shadowColor = clone $this->shadowColor; } } PK!ROb&b&0libraries/vendor/PhpSpreadsheet/Chart/Layout.phpnu[layoutTarget = $layout['layoutTarget']; } if (isset($layout['xMode'])) { $this->xMode = $layout['xMode']; } if (isset($layout['yMode'])) { $this->yMode = $layout['yMode']; } if (isset($layout['x'])) { $this->xPos = (float) $layout['x']; } if (isset($layout['y'])) { $this->yPos = (float) $layout['y']; } if (isset($layout['w'])) { $this->width = (float) $layout['w']; } if (isset($layout['h'])) { $this->height = (float) $layout['h']; } if (isset($layout['dLblPos'])) { $this->dLblPos = (string) $layout['dLblPos']; } if (isset($layout['numFmtCode'])) { $this->numFmtCode = (string) $layout['numFmtCode']; } $this->initBoolean($layout, 'showLegendKey'); $this->initBoolean($layout, 'showVal'); $this->initBoolean($layout, 'showCatName'); $this->initBoolean($layout, 'showSerName'); $this->initBoolean($layout, 'showPercent'); $this->initBoolean($layout, 'showBubbleSize'); $this->initBoolean($layout, 'showLeaderLines'); $this->initBoolean($layout, 'numFmtLinked'); $this->initColor($layout, 'labelFillColor'); $this->initColor($layout, 'labelBorderColor'); $labelFont = $layout['labelFont'] ?? null; if ($labelFont instanceof Font) { $this->labelFont = $labelFont; } $labelFontColor = $layout['labelFontColor'] ?? null; if ($labelFontColor instanceof ChartColor) { $this->setLabelFontColor($labelFontColor); } $labelEffects = $layout['labelEffects'] ?? null; if ($labelEffects instanceof Properties) { $this->labelEffects = $labelEffects; } } private function initBoolean(array $layout, string $name): void { if (isset($layout[$name])) { $this->$name = (bool) $layout[$name]; } } private function initColor(array $layout, string $name): void { if (isset($layout[$name]) && $layout[$name] instanceof ChartColor) { $this->$name = $layout[$name]; } } /** * Get Layout Target. */ public function getLayoutTarget(): ?string { return $this->layoutTarget; } /** * Set Layout Target. * * @return $this */ public function setLayoutTarget(?string $target) { $this->layoutTarget = $target; return $this; } /** * Get X-Mode. */ public function getXMode(): ?string { return $this->xMode; } /** * Set X-Mode. * * @return $this */ public function setXMode(?string $mode) { $this->xMode = (string) $mode; return $this; } /** * Get Y-Mode. */ public function getYMode(): ?string { return $this->yMode; } /** * Set Y-Mode. * * @return $this */ public function setYMode(?string $mode) { $this->yMode = (string) $mode; return $this; } /** * Get X-Position. * @return float|int|null */ public function getXPosition() { return $this->xPos; } /** * Set X-Position. * * @return $this */ public function setXPosition(float $position) { $this->xPos = $position; return $this; } /** * Get Y-Position. */ public function getYPosition(): ?float { return $this->yPos; } /** * Set Y-Position. * * @return $this */ public function setYPosition(float $position) { $this->yPos = $position; return $this; } /** * Get Width. */ public function getWidth(): ?float { return $this->width; } /** * Set Width. * * @return $this */ public function setWidth(?float $width) { $this->width = $width; return $this; } /** * Get Height. */ public function getHeight(): ?float { return $this->height; } /** * Set Height. * * @return $this */ public function setHeight(?float $height) { $this->height = $height; return $this; } public function getShowLegendKey(): ?bool { return $this->showLegendKey; } /** * Set show legend key * Specifies that legend keys should be shown in data labels. */ public function setShowLegendKey(?bool $showLegendKey): self { $this->showLegendKey = $showLegendKey; return $this; } public function getShowVal(): ?bool { return $this->showVal; } /** * Set show val * Specifies that the value should be shown in data labels. */ public function setShowVal(?bool $showDataLabelValues): self { $this->showVal = $showDataLabelValues; return $this; } public function getShowCatName(): ?bool { return $this->showCatName; } /** * Set show cat name * Specifies that the category name should be shown in data labels. */ public function setShowCatName(?bool $showCategoryName): self { $this->showCatName = $showCategoryName; return $this; } public function getShowSerName(): ?bool { return $this->showSerName; } /** * Set show data series name. * Specifies that the series name should be shown in data labels. */ public function setShowSerName(?bool $showSeriesName): self { $this->showSerName = $showSeriesName; return $this; } public function getShowPercent(): ?bool { return $this->showPercent; } /** * Set show percentage. * Specifies that the percentage should be shown in data labels. */ public function setShowPercent(?bool $showPercentage): self { $this->showPercent = $showPercentage; return $this; } public function getShowBubbleSize(): ?bool { return $this->showBubbleSize; } /** * Set show bubble size. * Specifies that the bubble size should be shown in data labels. */ public function setShowBubbleSize(?bool $showBubbleSize): self { $this->showBubbleSize = $showBubbleSize; return $this; } public function getShowLeaderLines(): ?bool { return $this->showLeaderLines; } /** * Set show leader lines. * Specifies that leader lines should be shown in data labels. */ public function setShowLeaderLines(?bool $showLeaderLines): self { $this->showLeaderLines = $showLeaderLines; return $this; } public function getLabelFillColor(): ?ChartColor { return $this->labelFillColor; } public function setLabelFillColor(?ChartColor $chartColor): self { $this->labelFillColor = $chartColor; return $this; } public function getLabelBorderColor(): ?ChartColor { return $this->labelBorderColor; } public function setLabelBorderColor(?ChartColor $chartColor): self { $this->labelBorderColor = $chartColor; return $this; } public function getLabelFont(): ?Font { return $this->labelFont; } public function setLabelFont(?Font $labelFont): self { $this->labelFont = $labelFont; return $this; } public function getLabelEffects(): ?Properties { return $this->labelEffects; } public function getLabelFontColor(): ?ChartColor { if ($this->labelFont === null) { return null; } return $this->labelFont->getChartColor(); } public function setLabelFontColor(?ChartColor $chartColor): self { if ($this->labelFont === null) { $this->labelFont = new Font(); $this->labelFont->setSize(null, true); } $this->labelFont->setChartColorFromObject($chartColor); return $this; } public function getDLblPos(): string { return $this->dLblPos; } public function setDLblPos(string $dLblPos): self { $this->dLblPos = $dLblPos; return $this; } public function getNumFmtCode(): string { return $this->numFmtCode; } public function setNumFmtCode(string $numFmtCode): self { $this->numFmtCode = $numFmtCode; return $this; } public function getNumFmtLinked(): bool { return $this->numFmtLinked; } public function setNumFmtLinked(bool $numFmtLinked): self { $this->numFmtLinked = $numFmtLinked; return $this; } /** * Implement PHP __clone to create a deep clone, not just a shallow copy. */ public function __clone() { $this->labelFillColor = ($this->labelFillColor === null) ? null : clone $this->labelFillColor; $this->labelBorderColor = ($this->labelBorderColor === null) ? null : clone $this->labelBorderColor; $this->labelFont = ($this->labelFont === null) ? null : clone $this->labelFont; $this->labelEffects = ($this->labelEffects === null) ? null : clone $this->labelEffects; } } PK! 3libraries/vendor/PhpSpreadsheet/Chart/GridLines.phpnu[> */ protected static array $phpSpreadsheetFunctions = [ 'ABS' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Absolute::class, 'evaluate'], 'argumentCount' => '1', ], 'ACCRINT' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\Securities\AccruedInterest::class, 'periodic'], 'argumentCount' => '4-8', ], 'ACCRINTM' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\Securities\AccruedInterest::class, 'atMaturity'], 'argumentCount' => '3-5', ], 'ACOS' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Trig\Cosine::class, 'acos'], 'argumentCount' => '1', ], 'ACOSH' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Trig\Cosine::class, 'acosh'], 'argumentCount' => '1', ], 'ACOT' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Trig\Cotangent::class, 'acot'], 'argumentCount' => '1', ], 'ACOTH' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Trig\Cotangent::class, 'acoth'], 'argumentCount' => '1', ], 'ADDRESS' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, 'functionCall' => [LookupRef\Address::class, 'cell'], 'argumentCount' => '2-5', ], 'AGGREGATE' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '3+', ], 'AMORDEGRC' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\Amortization::class, 'AMORDEGRC'], 'argumentCount' => '6,7', ], 'AMORLINC' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\Amortization::class, 'AMORLINC'], 'argumentCount' => '6,7', ], 'ANCHORARRAY' => [ 'category' => Category::CATEGORY_MICROSOFT_INTERNAL, 'functionCall' => [Internal\ExcelArrayPseudoFunctions::class, 'anchorArray'], 'argumentCount' => '1', 'passCellReference' => true, 'passByReference' => [true], ], 'AND' => [ 'category' => Category::CATEGORY_LOGICAL, 'functionCall' => [Logical\Operations::class, 'logicalAnd'], 'argumentCount' => '1+', ], 'ARABIC' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Arabic::class, 'evaluate'], 'argumentCount' => '1', ], 'AREAS' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '1', ], 'ARRAYTOTEXT' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Text::class, 'fromArray'], 'argumentCount' => '1,2', ], 'ASC' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '1', ], 'ASIN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Trig\Sine::class, 'asin'], 'argumentCount' => '1', ], 'ASINH' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Trig\Sine::class, 'asinh'], 'argumentCount' => '1', ], 'ATAN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Trig\Tangent::class, 'atan'], 'argumentCount' => '1', ], 'ATAN2' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Trig\Tangent::class, 'atan2'], 'argumentCount' => '2', ], 'ATANH' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Trig\Tangent::class, 'atanh'], 'argumentCount' => '1', ], 'AVEDEV' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Averages::class, 'averageDeviations'], 'argumentCount' => '1+', ], 'AVERAGE' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Averages::class, 'average'], 'argumentCount' => '1+', ], 'AVERAGEA' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Averages::class, 'averageA'], 'argumentCount' => '1+', ], 'AVERAGEIF' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Conditional::class, 'AVERAGEIF'], 'argumentCount' => '2,3', ], 'AVERAGEIFS' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Conditional::class, 'AVERAGEIFS'], 'argumentCount' => '3+', ], 'BAHTTEXT' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '1', ], 'BASE' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Base::class, 'evaluate'], 'argumentCount' => '2,3', ], 'BESSELI' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\BesselI::class, 'BESSELI'], 'argumentCount' => '2', ], 'BESSELJ' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\BesselJ::class, 'BESSELJ'], 'argumentCount' => '2', ], 'BESSELK' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\BesselK::class, 'BESSELK'], 'argumentCount' => '2', ], 'BESSELY' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\BesselY::class, 'BESSELY'], 'argumentCount' => '2', ], 'BETADIST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\Beta::class, 'distribution'], 'argumentCount' => '3-5', ], 'BETA.DIST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '4-6', ], 'BETAINV' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\Beta::class, 'inverse'], 'argumentCount' => '3-5', ], 'BETA.INV' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\Beta::class, 'inverse'], 'argumentCount' => '3-5', ], 'BIN2DEC' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ConvertBinary::class, 'toDecimal'], 'argumentCount' => '1', ], 'BIN2HEX' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ConvertBinary::class, 'toHex'], 'argumentCount' => '1,2', ], 'BIN2OCT' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ConvertBinary::class, 'toOctal'], 'argumentCount' => '1,2', ], 'BINOMDIST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\Binomial::class, 'distribution'], 'argumentCount' => '4', ], 'BINOM.DIST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\Binomial::class, 'distribution'], 'argumentCount' => '4', ], 'BINOM.DIST.RANGE' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\Binomial::class, 'range'], 'argumentCount' => '3,4', ], 'BINOM.INV' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\Binomial::class, 'inverse'], 'argumentCount' => '3', ], 'BITAND' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\BitWise::class, 'BITAND'], 'argumentCount' => '2', ], 'BITOR' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\BitWise::class, 'BITOR'], 'argumentCount' => '2', ], 'BITXOR' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\BitWise::class, 'BITXOR'], 'argumentCount' => '2', ], 'BITLSHIFT' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\BitWise::class, 'BITLSHIFT'], 'argumentCount' => '2', ], 'BITRSHIFT' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\BitWise::class, 'BITRSHIFT'], 'argumentCount' => '2', ], 'BYCOL' => [ 'category' => Category::CATEGORY_LOGICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '*', ], 'BYROW' => [ 'category' => Category::CATEGORY_LOGICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '*', ], 'CEILING' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Ceiling::class, 'ceiling'], 'argumentCount' => '1-2', // 2 for Excel, 1-2 for Ods/Gnumeric ], 'CEILING.MATH' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Ceiling::class, 'math'], 'argumentCount' => '1-3', ], 'CEILING.PRECISE' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Ceiling::class, 'precise'], 'argumentCount' => '1,2', ], 'CELL' => [ 'category' => Category::CATEGORY_INFORMATION, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '1,2', ], 'CHAR' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\CharacterConvert::class, 'character'], 'argumentCount' => '1', ], 'CHIDIST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'distributionRightTail'], 'argumentCount' => '2', ], 'CHISQ.DIST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'distributionLeftTail'], 'argumentCount' => '3', ], 'CHISQ.DIST.RT' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'distributionRightTail'], 'argumentCount' => '2', ], 'CHIINV' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'inverseRightTail'], 'argumentCount' => '2', ], 'CHISQ.INV' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'inverseLeftTail'], 'argumentCount' => '2', ], 'CHISQ.INV.RT' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'inverseRightTail'], 'argumentCount' => '2', ], 'CHITEST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'test'], 'argumentCount' => '2', ], 'CHISQ.TEST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'test'], 'argumentCount' => '2', ], 'CHOOSE' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, 'functionCall' => [LookupRef\Selection::class, 'CHOOSE'], 'argumentCount' => '2+', ], 'CHOOSECOLS' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, 'functionCall' => [LookupRef\ChooseRowsEtc::class, 'chooseCols'], 'argumentCount' => '2+', ], 'CHOOSEROWS' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, 'functionCall' => [LookupRef\ChooseRowsEtc::class, 'chooseRows'], 'argumentCount' => '2+', ], 'CLEAN' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Trim::class, 'nonPrintable'], 'argumentCount' => '1', ], 'CODE' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\CharacterConvert::class, 'code'], 'argumentCount' => '1', ], 'COLUMN' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, 'functionCall' => [LookupRef\RowColumnInformation::class, 'COLUMN'], 'argumentCount' => '-1', 'passCellReference' => true, 'passByReference' => [true], ], 'COLUMNS' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, 'functionCall' => [LookupRef\RowColumnInformation::class, 'COLUMNS'], 'argumentCount' => '1', ], 'COMBIN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Combinations::class, 'withoutRepetition'], 'argumentCount' => '2', ], 'COMBINA' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Combinations::class, 'withRepetition'], 'argumentCount' => '2', ], 'COMPLEX' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\Complex::class, 'COMPLEX'], 'argumentCount' => '2,3', ], 'CONCAT' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Concatenate::class, 'CONCATENATE'], 'argumentCount' => '1+', ], 'CONCATENATE' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Concatenate::class, 'actualCONCATENATE'], 'argumentCount' => '1+', ], 'CONFIDENCE' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Confidence::class, 'CONFIDENCE'], 'argumentCount' => '3', ], 'CONFIDENCE.NORM' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Confidence::class, 'CONFIDENCE'], 'argumentCount' => '3', ], 'CONFIDENCE.T' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '3', ], 'CONVERT' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ConvertUOM::class, 'CONVERT'], 'argumentCount' => '3', ], 'CORREL' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Trends::class, 'CORREL'], 'argumentCount' => '2', ], 'COS' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Trig\Cosine::class, 'cos'], 'argumentCount' => '1', ], 'COSH' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Trig\Cosine::class, 'cosh'], 'argumentCount' => '1', ], 'COT' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Trig\Cotangent::class, 'cot'], 'argumentCount' => '1', ], 'COTH' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Trig\Cotangent::class, 'coth'], 'argumentCount' => '1', ], 'COUNT' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Counts::class, 'COUNT'], 'argumentCount' => '1+', ], 'COUNTA' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Counts::class, 'COUNTA'], 'argumentCount' => '1+', ], 'COUNTBLANK' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Counts::class, 'COUNTBLANK'], 'argumentCount' => '1', ], 'COUNTIF' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Conditional::class, 'COUNTIF'], 'argumentCount' => '2', ], 'COUNTIFS' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Conditional::class, 'COUNTIFS'], 'argumentCount' => '2+', ], 'COUPDAYBS' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\Coupons::class, 'COUPDAYBS'], 'argumentCount' => '3,4', ], 'COUPDAYS' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\Coupons::class, 'COUPDAYS'], 'argumentCount' => '3,4', ], 'COUPDAYSNC' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\Coupons::class, 'COUPDAYSNC'], 'argumentCount' => '3,4', ], 'COUPNCD' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\Coupons::class, 'COUPNCD'], 'argumentCount' => '3,4', ], 'COUPNUM' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\Coupons::class, 'COUPNUM'], 'argumentCount' => '3,4', ], 'COUPPCD' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\Coupons::class, 'COUPPCD'], 'argumentCount' => '3,4', ], 'COVAR' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Trends::class, 'COVAR'], 'argumentCount' => '2', ], 'COVARIANCE.P' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Trends::class, 'COVAR'], 'argumentCount' => '2', ], 'COVARIANCE.S' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '2', ], 'CRITBINOM' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\Binomial::class, 'inverse'], 'argumentCount' => '3', ], 'CSC' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Trig\Cosecant::class, 'csc'], 'argumentCount' => '1', ], 'CSCH' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Trig\Cosecant::class, 'csch'], 'argumentCount' => '1', ], 'CUBEKPIMEMBER' => [ 'category' => Category::CATEGORY_CUBE, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '?', ], 'CUBEMEMBER' => [ 'category' => Category::CATEGORY_CUBE, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '?', ], 'CUBEMEMBERPROPERTY' => [ 'category' => Category::CATEGORY_CUBE, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '?', ], 'CUBERANKEDMEMBER' => [ 'category' => Category::CATEGORY_CUBE, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '?', ], 'CUBESET' => [ 'category' => Category::CATEGORY_CUBE, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '?', ], 'CUBESETCOUNT' => [ 'category' => Category::CATEGORY_CUBE, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '?', ], 'CUBEVALUE' => [ 'category' => Category::CATEGORY_CUBE, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '?', ], 'CUMIPMT' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\CashFlow\Constant\Periodic\Cumulative::class, 'interest'], 'argumentCount' => '6', ], 'CUMPRINC' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\CashFlow\Constant\Periodic\Cumulative::class, 'principal'], 'argumentCount' => '6', ], 'DATE' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, 'functionCall' => [DateTimeExcel\Date::class, 'fromYMD'], 'argumentCount' => '3', ], 'DATEDIF' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, 'functionCall' => [DateTimeExcel\Difference::class, 'interval'], 'argumentCount' => '2,3', ], 'DATESTRING' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '?', ], 'DATEVALUE' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, 'functionCall' => [DateTimeExcel\DateValue::class, 'fromString'], 'argumentCount' => '1', ], 'DAVERAGE' => [ 'category' => Category::CATEGORY_DATABASE, 'functionCall' => [Database\DAverage::class, 'evaluate'], 'argumentCount' => '3', ], 'DAY' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, 'functionCall' => [DateTimeExcel\DateParts::class, 'day'], 'argumentCount' => '1', ], 'DAYS' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, 'functionCall' => [DateTimeExcel\Days::class, 'between'], 'argumentCount' => '2', ], 'DAYS360' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, 'functionCall' => [DateTimeExcel\Days360::class, 'between'], 'argumentCount' => '2,3', ], 'DB' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\Depreciation::class, 'DB'], 'argumentCount' => '4,5', ], 'DBCS' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '1', ], 'DCOUNT' => [ 'category' => Category::CATEGORY_DATABASE, 'functionCall' => [Database\DCount::class, 'evaluate'], 'argumentCount' => '3', ], 'DCOUNTA' => [ 'category' => Category::CATEGORY_DATABASE, 'functionCall' => [Database\DCountA::class, 'evaluate'], 'argumentCount' => '3', ], 'DDB' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\Depreciation::class, 'DDB'], 'argumentCount' => '4,5', ], 'DEC2BIN' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ConvertDecimal::class, 'toBinary'], 'argumentCount' => '1,2', ], 'DEC2HEX' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ConvertDecimal::class, 'toHex'], 'argumentCount' => '1,2', ], 'DEC2OCT' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ConvertDecimal::class, 'toOctal'], 'argumentCount' => '1,2', ], 'DECIMAL' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '2', ], 'DEGREES' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Angle::class, 'toDegrees'], 'argumentCount' => '1', ], 'DELTA' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\Compare::class, 'DELTA'], 'argumentCount' => '1,2', ], 'DEVSQ' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Deviations::class, 'sumSquares'], 'argumentCount' => '1+', ], 'DGET' => [ 'category' => Category::CATEGORY_DATABASE, 'functionCall' => [Database\DGet::class, 'evaluate'], 'argumentCount' => '3', ], 'DISC' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\Securities\Rates::class, 'discount'], 'argumentCount' => '4,5', ], 'DMAX' => [ 'category' => Category::CATEGORY_DATABASE, 'functionCall' => [Database\DMax::class, 'evaluate'], 'argumentCount' => '3', ], 'DMIN' => [ 'category' => Category::CATEGORY_DATABASE, 'functionCall' => [Database\DMin::class, 'evaluate'], 'argumentCount' => '3', ], 'DOLLAR' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Format::class, 'DOLLAR'], 'argumentCount' => '1,2', ], 'DOLLARDE' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\Dollar::class, 'decimal'], 'argumentCount' => '2', ], 'DOLLARFR' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\Dollar::class, 'fractional'], 'argumentCount' => '2', ], 'DPRODUCT' => [ 'category' => Category::CATEGORY_DATABASE, 'functionCall' => [Database\DProduct::class, 'evaluate'], 'argumentCount' => '3', ], 'DROP' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, 'functionCall' => [LookupRef\ChooseRowsEtc::class, 'drop'], 'argumentCount' => '2-3', ], 'DSTDEV' => [ 'category' => Category::CATEGORY_DATABASE, 'functionCall' => [Database\DStDev::class, 'evaluate'], 'argumentCount' => '3', ], 'DSTDEVP' => [ 'category' => Category::CATEGORY_DATABASE, 'functionCall' => [Database\DStDevP::class, 'evaluate'], 'argumentCount' => '3', ], 'DSUM' => [ 'category' => Category::CATEGORY_DATABASE, 'functionCall' => [Database\DSum::class, 'evaluate'], 'argumentCount' => '3', ], 'DURATION' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '5,6', ], 'DVAR' => [ 'category' => Category::CATEGORY_DATABASE, 'functionCall' => [Database\DVar::class, 'evaluate'], 'argumentCount' => '3', ], 'DVARP' => [ 'category' => Category::CATEGORY_DATABASE, 'functionCall' => [Database\DVarP::class, 'evaluate'], 'argumentCount' => '3', ], 'ECMA.CEILING' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '1,2', ], 'EDATE' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, 'functionCall' => [DateTimeExcel\Month::class, 'adjust'], 'argumentCount' => '2', ], 'EFFECT' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\InterestRate::class, 'effective'], 'argumentCount' => '2', ], 'ENCODEURL' => [ 'category' => Category::CATEGORY_WEB, 'functionCall' => [Web\Service::class, 'urlEncode'], 'argumentCount' => '1', ], 'EOMONTH' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, 'functionCall' => [DateTimeExcel\Month::class, 'lastDay'], 'argumentCount' => '2', ], 'ERF' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\Erf::class, 'ERF'], 'argumentCount' => '1,2', ], 'ERF.PRECISE' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\Erf::class, 'ERFPRECISE'], 'argumentCount' => '1', ], 'ERFC' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ErfC::class, 'ERFC'], 'argumentCount' => '1', ], 'ERFC.PRECISE' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ErfC::class, 'ERFC'], 'argumentCount' => '1', ], 'ERROR.TYPE' => [ 'category' => Category::CATEGORY_INFORMATION, 'functionCall' => [Information\ExcelError::class, 'type'], 'argumentCount' => '1', ], 'EVEN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Round::class, 'even'], 'argumentCount' => '1', ], 'EXACT' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Text::class, 'exact'], 'argumentCount' => '2', ], 'EXP' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Exp::class, 'evaluate'], 'argumentCount' => '1', ], 'EXPAND' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, 'functionCall' => [LookupRef\ChooseRowsEtc::class, 'expand'], 'argumentCount' => '2-4', ], 'EXPONDIST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\Exponential::class, 'distribution'], 'argumentCount' => '3', ], 'EXPON.DIST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\Exponential::class, 'distribution'], 'argumentCount' => '3', ], 'FACT' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Factorial::class, 'fact'], 'argumentCount' => '1', ], 'FACTDOUBLE' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Factorial::class, 'factDouble'], 'argumentCount' => '1', ], 'FALSE' => [ 'category' => Category::CATEGORY_LOGICAL, 'functionCall' => [Logical\Boolean::class, 'FALSE'], 'argumentCount' => '0', ], 'FDIST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '3', ], 'F.DIST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\F::class, 'distribution'], 'argumentCount' => '4', ], 'F.DIST.RT' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '3', ], 'FILTER' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, 'functionCall' => [LookupRef\Filter::class, 'filter'], 'argumentCount' => '2-3', ], 'FILTERXML' => [ 'category' => Category::CATEGORY_WEB, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '2', ], 'FIND' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Search::class, 'sensitive'], 'argumentCount' => '2,3', ], 'FINDB' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Search::class, 'sensitive'], 'argumentCount' => '2,3', ], 'FINV' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '3', ], 'F.INV' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '3', ], 'F.INV.RT' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '3', ], 'FISHER' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\Fisher::class, 'distribution'], 'argumentCount' => '1', ], 'FISHERINV' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\Fisher::class, 'inverse'], 'argumentCount' => '1', ], 'FIXED' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Format::class, 'FIXEDFORMAT'], 'argumentCount' => '1-3', ], 'FLOOR' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Floor::class, 'floor'], 'argumentCount' => '1-2', // Excel requries 2, Ods/Gnumeric 1-2 ], 'FLOOR.MATH' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Floor::class, 'math'], 'argumentCount' => '1-3', ], 'FLOOR.PRECISE' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Floor::class, 'precise'], 'argumentCount' => '1-2', ], 'FORECAST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Trends::class, 'FORECAST'], 'argumentCount' => '3', ], 'FORECAST.ETS' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '3-6', ], 'FORECAST.ETS.CONFINT' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '3-6', ], 'FORECAST.ETS.SEASONALITY' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '2-4', ], 'FORECAST.ETS.STAT' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '3-6', ], 'FORECAST.LINEAR' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Trends::class, 'FORECAST'], 'argumentCount' => '3', ], 'FORMULATEXT' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, 'functionCall' => [LookupRef\Formula::class, 'text'], 'argumentCount' => '1', 'passCellReference' => true, 'passByReference' => [true], ], 'FREQUENCY' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '2', ], 'FTEST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '2', ], 'F.TEST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '2', ], 'FV' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\CashFlow\Constant\Periodic::class, 'futureValue'], 'argumentCount' => '3-5', ], 'FVSCHEDULE' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\CashFlow\Single::class, 'futureValue'], 'argumentCount' => '2', ], 'GAMMA' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\Gamma::class, 'gamma'], 'argumentCount' => '1', ], 'GAMMADIST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\Gamma::class, 'distribution'], 'argumentCount' => '4', ], 'GAMMA.DIST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\Gamma::class, 'distribution'], 'argumentCount' => '4', ], 'GAMMAINV' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\Gamma::class, 'inverse'], 'argumentCount' => '3', ], 'GAMMA.INV' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\Gamma::class, 'inverse'], 'argumentCount' => '3', ], 'GAMMALN' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\Gamma::class, 'ln'], 'argumentCount' => '1', ], 'GAMMALN.PRECISE' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\Gamma::class, 'ln'], 'argumentCount' => '1', ], 'GAUSS' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'gauss'], 'argumentCount' => '1', ], 'GCD' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Gcd::class, 'evaluate'], 'argumentCount' => '1+', ], 'GEOMEAN' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Averages\Mean::class, 'geometric'], 'argumentCount' => '1+', ], 'GESTEP' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\Compare::class, 'GESTEP'], 'argumentCount' => '1,2', ], 'GETPIVOTDATA' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '2+', ], 'GROUPBY' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '3-7', ], 'GROWTH' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Trends::class, 'GROWTH'], 'argumentCount' => '1-4', ], 'HARMEAN' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Averages\Mean::class, 'harmonic'], 'argumentCount' => '1+', ], 'HEX2BIN' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ConvertHex::class, 'toBinary'], 'argumentCount' => '1,2', ], 'HEX2DEC' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ConvertHex::class, 'toDecimal'], 'argumentCount' => '1', ], 'HEX2OCT' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ConvertHex::class, 'toOctal'], 'argumentCount' => '1,2', ], 'HLOOKUP' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, 'functionCall' => [LookupRef\HLookup::class, 'lookup'], 'argumentCount' => '3,4', ], 'HOUR' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, 'functionCall' => [DateTimeExcel\TimeParts::class, 'hour'], 'argumentCount' => '1', ], 'HSTACK' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '1+', ], 'HYPERLINK' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, 'functionCall' => [LookupRef\Hyperlink::class, 'set'], 'argumentCount' => '1,2', 'passCellReference' => true, ], 'HYPGEOMDIST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\HyperGeometric::class, 'distribution'], 'argumentCount' => '4', ], 'HYPGEOM.DIST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '5', ], 'IF' => [ 'category' => Category::CATEGORY_LOGICAL, 'functionCall' => [Logical\Conditional::class, 'statementIf'], 'argumentCount' => '2-3', ], 'IFERROR' => [ 'category' => Category::CATEGORY_LOGICAL, 'functionCall' => [Logical\Conditional::class, 'IFERROR'], 'argumentCount' => '2', ], 'IFNA' => [ 'category' => Category::CATEGORY_LOGICAL, 'functionCall' => [Logical\Conditional::class, 'IFNA'], 'argumentCount' => '2', ], 'IFS' => [ 'category' => Category::CATEGORY_LOGICAL, 'functionCall' => [Logical\Conditional::class, 'IFS'], 'argumentCount' => '2+', ], 'IMABS' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ComplexFunctions::class, 'IMABS'], 'argumentCount' => '1', ], 'IMAGINARY' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\Complex::class, 'IMAGINARY'], 'argumentCount' => '1', ], 'IMARGUMENT' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ComplexFunctions::class, 'IMARGUMENT'], 'argumentCount' => '1', ], 'IMCONJUGATE' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ComplexFunctions::class, 'IMCONJUGATE'], 'argumentCount' => '1', ], 'IMCOS' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ComplexFunctions::class, 'IMCOS'], 'argumentCount' => '1', ], 'IMCOSH' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ComplexFunctions::class, 'IMCOSH'], 'argumentCount' => '1', ], 'IMCOT' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ComplexFunctions::class, 'IMCOT'], 'argumentCount' => '1', ], 'IMCSC' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ComplexFunctions::class, 'IMCSC'], 'argumentCount' => '1', ], 'IMCSCH' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ComplexFunctions::class, 'IMCSCH'], 'argumentCount' => '1', ], 'IMDIV' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ComplexOperations::class, 'IMDIV'], 'argumentCount' => '2', ], 'IMEXP' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ComplexFunctions::class, 'IMEXP'], 'argumentCount' => '1', ], 'IMLN' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ComplexFunctions::class, 'IMLN'], 'argumentCount' => '1', ], 'IMLOG10' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ComplexFunctions::class, 'IMLOG10'], 'argumentCount' => '1', ], 'IMLOG2' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ComplexFunctions::class, 'IMLOG2'], 'argumentCount' => '1', ], 'IMPOWER' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ComplexFunctions::class, 'IMPOWER'], 'argumentCount' => '2', ], 'IMPRODUCT' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ComplexOperations::class, 'IMPRODUCT'], 'argumentCount' => '1+', ], 'IMREAL' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\Complex::class, 'IMREAL'], 'argumentCount' => '1', ], 'IMSEC' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ComplexFunctions::class, 'IMSEC'], 'argumentCount' => '1', ], 'IMSECH' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ComplexFunctions::class, 'IMSECH'], 'argumentCount' => '1', ], 'IMSIN' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ComplexFunctions::class, 'IMSIN'], 'argumentCount' => '1', ], 'IMSINH' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ComplexFunctions::class, 'IMSINH'], 'argumentCount' => '1', ], 'IMSQRT' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ComplexFunctions::class, 'IMSQRT'], 'argumentCount' => '1', ], 'IMSUB' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ComplexOperations::class, 'IMSUB'], 'argumentCount' => '2', ], 'IMSUM' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ComplexOperations::class, 'IMSUM'], 'argumentCount' => '1+', ], 'IMTAN' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ComplexFunctions::class, 'IMTAN'], 'argumentCount' => '1', ], 'INDEX' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, 'functionCall' => [LookupRef\Matrix::class, 'index'], 'argumentCount' => '2-4', ], 'INDIRECT' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, 'functionCall' => [LookupRef\Indirect::class, 'INDIRECT'], 'argumentCount' => '1,2', 'passCellReference' => true, ], 'INFO' => [ 'category' => Category::CATEGORY_INFORMATION, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '1', ], 'INT' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\IntClass::class, 'evaluate'], 'argumentCount' => '1', ], 'INTERCEPT' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Trends::class, 'INTERCEPT'], 'argumentCount' => '2', ], 'INTRATE' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\Securities\Rates::class, 'interest'], 'argumentCount' => '4,5', ], 'IPMT' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\CashFlow\Constant\Periodic\Interest::class, 'payment'], 'argumentCount' => '4-6', ], 'IRR' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\CashFlow\Variable\Periodic::class, 'rate'], 'argumentCount' => '1,2', ], 'ISBLANK' => [ 'category' => Category::CATEGORY_INFORMATION, 'functionCall' => [Information\Value::class, 'isBlank'], 'argumentCount' => '1', ], 'ISERR' => [ 'category' => Category::CATEGORY_INFORMATION, 'functionCall' => [Information\ErrorValue::class, 'isErr'], 'argumentCount' => '1', ], 'ISERROR' => [ 'category' => Category::CATEGORY_INFORMATION, 'functionCall' => [Information\ErrorValue::class, 'isError'], 'argumentCount' => '1', ], 'ISEVEN' => [ 'category' => Category::CATEGORY_INFORMATION, 'functionCall' => [Information\Value::class, 'isEven'], 'argumentCount' => '1', ], 'ISFORMULA' => [ 'category' => Category::CATEGORY_INFORMATION, 'functionCall' => [Information\Value::class, 'isFormula'], 'argumentCount' => '1', 'passCellReference' => true, 'passByReference' => [true], ], 'ISLOGICAL' => [ 'category' => Category::CATEGORY_INFORMATION, 'functionCall' => [Information\Value::class, 'isLogical'], 'argumentCount' => '1', ], 'ISNA' => [ 'category' => Category::CATEGORY_INFORMATION, 'functionCall' => [Information\ErrorValue::class, 'isNa'], 'argumentCount' => '1', ], 'ISNONTEXT' => [ 'category' => Category::CATEGORY_INFORMATION, 'functionCall' => [Information\Value::class, 'isNonText'], 'argumentCount' => '1', ], 'ISNUMBER' => [ 'category' => Category::CATEGORY_INFORMATION, 'functionCall' => [Information\Value::class, 'isNumber'], 'argumentCount' => '1', ], 'ISO.CEILING' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '1,2', ], 'ISODD' => [ 'category' => Category::CATEGORY_INFORMATION, 'functionCall' => [Information\Value::class, 'isOdd'], 'argumentCount' => '1', ], 'ISOMITTED' => [ 'category' => Category::CATEGORY_INFORMATION, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '*', ], 'ISOWEEKNUM' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, 'functionCall' => [DateTimeExcel\Week::class, 'isoWeekNumber'], 'argumentCount' => '1', ], 'ISPMT' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\CashFlow\Constant\Periodic\Interest::class, 'schedulePayment'], 'argumentCount' => '4', ], 'ISREF' => [ 'category' => Category::CATEGORY_INFORMATION, 'functionCall' => [Information\Value::class, 'isRef'], 'argumentCount' => '1', 'passCellReference' => true, 'passByReference' => [true], ], 'ISTEXT' => [ 'category' => Category::CATEGORY_INFORMATION, 'functionCall' => [Information\Value::class, 'isText'], 'argumentCount' => '1', ], 'ISTHAIDIGIT' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '?', ], 'JIS' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '1', ], 'KURT' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Deviations::class, 'kurtosis'], 'argumentCount' => '1+', ], 'LAMBDA' => [ 'category' => Category::CATEGORY_LOGICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '*', ], 'LARGE' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Size::class, 'large'], 'argumentCount' => '2', ], 'LCM' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Lcm::class, 'evaluate'], 'argumentCount' => '1+', ], 'LEFT' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Extract::class, 'left'], 'argumentCount' => '1,2', ], 'LEFTB' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Extract::class, 'left'], 'argumentCount' => '1,2', ], 'LEN' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Text::class, 'length'], 'argumentCount' => '1', ], 'LENB' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Text::class, 'length'], 'argumentCount' => '1', ], 'LET' => [ 'category' => Category::CATEGORY_LOGICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '*', ], 'LINEST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Trends::class, 'LINEST'], 'argumentCount' => '1-4', ], 'LN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Logarithms::class, 'natural'], 'argumentCount' => '1', ], 'LOG' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Logarithms::class, 'withBase'], 'argumentCount' => '1,2', ], 'LOG10' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Logarithms::class, 'base10'], 'argumentCount' => '1', ], 'LOGEST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Trends::class, 'LOGEST'], 'argumentCount' => '1-4', ], 'LOGINV' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\LogNormal::class, 'inverse'], 'argumentCount' => '3', ], 'LOGNORMDIST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\LogNormal::class, 'cumulative'], 'argumentCount' => '3', ], 'LOGNORM.DIST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\LogNormal::class, 'distribution'], 'argumentCount' => '4', ], 'LOGNORM.INV' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\LogNormal::class, 'inverse'], 'argumentCount' => '3', ], 'LOOKUP' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, 'functionCall' => [LookupRef\Lookup::class, 'lookup'], 'argumentCount' => '2,3', ], 'LOWER' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\CaseConvert::class, 'lower'], 'argumentCount' => '1', ], 'MAKEARRAY' => [ 'category' => Category::CATEGORY_LOGICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '*', ], 'MAP' => [ 'category' => Category::CATEGORY_LOGICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '*', ], 'MATCH' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, 'functionCall' => [LookupRef\ExcelMatch::class, 'MATCH'], 'argumentCount' => '2,3', ], 'MAX' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Maximum::class, 'max'], 'argumentCount' => '1+', ], 'MAXA' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Maximum::class, 'maxA'], 'argumentCount' => '1+', ], 'MAXIFS' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Conditional::class, 'MAXIFS'], 'argumentCount' => '3+', ], 'MDETERM' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\MatrixFunctions::class, 'determinant'], 'argumentCount' => '1', ], 'MDURATION' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '5,6', ], 'MEDIAN' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Averages::class, 'median'], 'argumentCount' => '1+', ], 'MEDIANIF' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '2+', ], 'MID' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Extract::class, 'mid'], 'argumentCount' => '3', ], 'MIDB' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Extract::class, 'mid'], 'argumentCount' => '3', ], 'MIN' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Minimum::class, 'min'], 'argumentCount' => '1+', ], 'MINA' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Minimum::class, 'minA'], 'argumentCount' => '1+', ], 'MINIFS' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Conditional::class, 'MINIFS'], 'argumentCount' => '3+', ], 'MINUTE' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, 'functionCall' => [DateTimeExcel\TimeParts::class, 'minute'], 'argumentCount' => '1', ], 'MINVERSE' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\MatrixFunctions::class, 'inverse'], 'argumentCount' => '1', ], 'MIRR' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\CashFlow\Variable\Periodic::class, 'modifiedRate'], 'argumentCount' => '3', ], 'MMULT' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\MatrixFunctions::class, 'multiply'], 'argumentCount' => '2', ], 'MOD' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Operations::class, 'mod'], 'argumentCount' => '2', ], 'MODE' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Averages::class, 'mode'], 'argumentCount' => '1+', ], 'MODE.MULT' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '1+', ], 'MODE.SNGL' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Averages::class, 'mode'], 'argumentCount' => '1+', ], 'MONTH' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, 'functionCall' => [DateTimeExcel\DateParts::class, 'month'], 'argumentCount' => '1', ], 'MROUND' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Round::class, 'multiple'], 'argumentCount' => '2', ], 'MULTINOMIAL' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Factorial::class, 'multinomial'], 'argumentCount' => '1+', ], 'MUNIT' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\MatrixFunctions::class, 'identity'], 'argumentCount' => '1', ], 'N' => [ 'category' => Category::CATEGORY_INFORMATION, 'functionCall' => [Information\Value::class, 'asNumber'], 'argumentCount' => '1', ], 'NA' => [ 'category' => Category::CATEGORY_INFORMATION, 'functionCall' => [Information\ExcelError::class, 'NA'], 'argumentCount' => '0', ], 'NEGBINOMDIST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\Binomial::class, 'negative'], 'argumentCount' => '3', ], 'NEGBINOM.DIST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '4', ], 'NETWORKDAYS' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, 'functionCall' => [DateTimeExcel\NetworkDays::class, 'count'], 'argumentCount' => '2-3', ], 'NETWORKDAYS.INTL' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '2-4', ], 'NOMINAL' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\InterestRate::class, 'nominal'], 'argumentCount' => '2', ], 'NORMDIST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\Normal::class, 'distribution'], 'argumentCount' => '4', ], 'NORM.DIST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\Normal::class, 'distribution'], 'argumentCount' => '4', ], 'NORMINV' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\Normal::class, 'inverse'], 'argumentCount' => '3', ], 'NORM.INV' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\Normal::class, 'inverse'], 'argumentCount' => '3', ], 'NORMSDIST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'cumulative'], 'argumentCount' => '1', ], 'NORM.S.DIST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'distribution'], 'argumentCount' => '1,2', ], 'NORMSINV' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'inverse'], 'argumentCount' => '1', ], 'NORM.S.INV' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'inverse'], 'argumentCount' => '1', ], 'NOT' => [ 'category' => Category::CATEGORY_LOGICAL, 'functionCall' => [Logical\Operations::class, 'NOT'], 'argumentCount' => '1', ], 'NOW' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, 'functionCall' => [DateTimeExcel\Current::class, 'now'], 'argumentCount' => '0', ], 'NPER' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\CashFlow\Constant\Periodic::class, 'periods'], 'argumentCount' => '3-5', ], 'NPV' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\CashFlow\Variable\Periodic::class, 'presentValue'], 'argumentCount' => '2+', ], 'NUMBERSTRING' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '?', ], 'NUMBERVALUE' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Format::class, 'NUMBERVALUE'], 'argumentCount' => '1+', ], 'OCT2BIN' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ConvertOctal::class, 'toBinary'], 'argumentCount' => '1,2', ], 'OCT2DEC' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ConvertOctal::class, 'toDecimal'], 'argumentCount' => '1', ], 'OCT2HEX' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering\ConvertOctal::class, 'toHex'], 'argumentCount' => '1,2', ], 'ODD' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Round::class, 'odd'], 'argumentCount' => '1', ], 'ODDFPRICE' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '8,9', ], 'ODDFYIELD' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '8,9', ], 'ODDLPRICE' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '7,8', ], 'ODDLYIELD' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '7,8', ], 'OFFSET' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, 'functionCall' => [LookupRef\Offset::class, 'OFFSET'], 'argumentCount' => '3-5', 'passCellReference' => true, 'passByReference' => [true], ], 'OR' => [ 'category' => Category::CATEGORY_LOGICAL, 'functionCall' => [Logical\Operations::class, 'logicalOr'], 'argumentCount' => '1+', ], 'PDURATION' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\CashFlow\Single::class, 'periods'], 'argumentCount' => '3', ], 'PEARSON' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Trends::class, 'CORREL'], 'argumentCount' => '2', ], 'PERCENTILE' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Percentiles::class, 'PERCENTILE'], 'argumentCount' => '2', ], 'PERCENTILE.EXC' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '2', ], 'PERCENTILE.INC' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Percentiles::class, 'PERCENTILE'], 'argumentCount' => '2', ], 'PERCENTRANK' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Percentiles::class, 'PERCENTRANK'], 'argumentCount' => '2,3', ], 'PERCENTRANK.EXC' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '2,3', ], 'PERCENTRANK.INC' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Percentiles::class, 'PERCENTRANK'], 'argumentCount' => '2,3', ], 'PERMUT' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Permutations::class, 'PERMUT'], 'argumentCount' => '2', ], 'PERMUTATIONA' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Permutations::class, 'PERMUTATIONA'], 'argumentCount' => '2', ], 'PHONETIC' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '1', ], 'PHI' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '1', ], 'PI' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => 'pi', 'argumentCount' => '0', ], 'PMT' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\CashFlow\Constant\Periodic\Payments::class, 'annuity'], 'argumentCount' => '3-5', ], 'POISSON' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\Poisson::class, 'distribution'], 'argumentCount' => '3', ], 'POISSON.DIST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\Poisson::class, 'distribution'], 'argumentCount' => '3', ], 'POWER' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Operations::class, 'power'], 'argumentCount' => '2', ], 'PPMT' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\CashFlow\Constant\Periodic\Payments::class, 'interestPayment'], 'argumentCount' => '4-6', ], 'PRICE' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\Securities\Price::class, 'price'], 'argumentCount' => '6,7', ], 'PRICEDISC' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\Securities\Price::class, 'priceDiscounted'], 'argumentCount' => '4,5', ], 'PRICEMAT' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\Securities\Price::class, 'priceAtMaturity'], 'argumentCount' => '5,6', ], 'PROB' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '3,4', ], 'PRODUCT' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Operations::class, 'product'], 'argumentCount' => '1+', ], 'PROPER' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\CaseConvert::class, 'proper'], 'argumentCount' => '1', ], 'PV' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\CashFlow\Constant\Periodic::class, 'presentValue'], 'argumentCount' => '3-5', ], 'QUARTILE' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Percentiles::class, 'QUARTILE'], 'argumentCount' => '2', ], 'QUARTILE.EXC' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '2', ], 'QUARTILE.INC' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Percentiles::class, 'QUARTILE'], 'argumentCount' => '2', ], 'QUOTIENT' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Operations::class, 'quotient'], 'argumentCount' => '2', ], 'RADIANS' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Angle::class, 'toRadians'], 'argumentCount' => '1', ], 'RAND' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Random::class, 'rand'], 'argumentCount' => '0', ], 'RANDARRAY' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Random::class, 'randArray'], 'argumentCount' => '0-5', ], 'RANDBETWEEN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Random::class, 'randBetween'], 'argumentCount' => '2', ], 'RANK' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Percentiles::class, 'RANK'], 'argumentCount' => '2,3', ], 'RANK.AVG' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '2,3', ], 'RANK.EQ' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Percentiles::class, 'RANK'], 'argumentCount' => '2,3', ], 'RATE' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\CashFlow\Constant\Periodic\Interest::class, 'rate'], 'argumentCount' => '3-6', ], 'RECEIVED' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\Securities\Price::class, 'received'], 'argumentCount' => '4-5', ], 'REDUCE' => [ 'category' => Category::CATEGORY_LOGICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '*', ], 'REPLACE' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Replace::class, 'replace'], 'argumentCount' => '4', ], 'REPLACEB' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Replace::class, 'replace'], 'argumentCount' => '4', ], 'REPT' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Concatenate::class, 'builtinREPT'], 'argumentCount' => '2', ], 'RIGHT' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Extract::class, 'right'], 'argumentCount' => '1,2', ], 'RIGHTB' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Extract::class, 'right'], 'argumentCount' => '1,2', ], 'ROMAN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Roman::class, 'evaluate'], 'argumentCount' => '1,2', ], 'ROUND' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Round::class, 'round'], 'argumentCount' => '1,2', ], 'ROUNDBAHTDOWN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '?', ], 'ROUNDBAHTUP' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '?', ], 'ROUNDDOWN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Round::class, 'down'], 'argumentCount' => '1,2', ], 'ROUNDUP' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Round::class, 'up'], 'argumentCount' => '1,2', ], 'ROW' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, 'functionCall' => [LookupRef\RowColumnInformation::class, 'ROW'], 'argumentCount' => '-1', 'passCellReference' => true, 'passByReference' => [true], ], 'ROWS' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, 'functionCall' => [LookupRef\RowColumnInformation::class, 'ROWS'], 'argumentCount' => '1', ], 'RRI' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\CashFlow\Single::class, 'interestRate'], 'argumentCount' => '3', ], 'RSQ' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Trends::class, 'RSQ'], 'argumentCount' => '2', ], 'RTD' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '1+', ], 'SEARCH' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Search::class, 'insensitive'], 'argumentCount' => '2,3', ], 'SCAN' => [ 'category' => Category::CATEGORY_LOGICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '*', ], 'SEARCHB' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Search::class, 'insensitive'], 'argumentCount' => '2,3', ], 'SEC' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Trig\Secant::class, 'sec'], 'argumentCount' => '1', ], 'SECH' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Trig\Secant::class, 'sech'], 'argumentCount' => '1', ], 'SECOND' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, 'functionCall' => [DateTimeExcel\TimeParts::class, 'second'], 'argumentCount' => '1', ], 'SEQUENCE' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\MatrixFunctions::class, 'sequence'], 'argumentCount' => '1-4', ], 'SERIESSUM' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\SeriesSum::class, 'evaluate'], 'argumentCount' => '4', ], 'SHEET' => [ 'category' => Category::CATEGORY_INFORMATION, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '0,1', ], 'SHEETS' => [ 'category' => Category::CATEGORY_INFORMATION, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '0,1', ], 'SIGN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Sign::class, 'evaluate'], 'argumentCount' => '1', ], 'SIN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Trig\Sine::class, 'sin'], 'argumentCount' => '1', ], 'SINGLE' => [ 'category' => Category::CATEGORY_MICROSOFT_INTERNAL, 'functionCall' => [Internal\ExcelArrayPseudoFunctions::class, 'single'], 'argumentCount' => '1', 'passCellReference' => true, 'passByReference' => [true], ], 'SINH' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Trig\Sine::class, 'sinh'], 'argumentCount' => '1', ], 'SKEW' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Deviations::class, 'skew'], 'argumentCount' => '1+', ], 'SKEW.P' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '1+', ], 'SLN' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\Depreciation::class, 'SLN'], 'argumentCount' => '3', ], 'SLOPE' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Trends::class, 'SLOPE'], 'argumentCount' => '2', ], 'SMALL' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Size::class, 'small'], 'argumentCount' => '2', ], 'SORT' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, 'functionCall' => [LookupRef\Sort::class, 'sort'], 'argumentCount' => '1-4', ], 'SORTBY' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, 'functionCall' => [LookupRef\Sort::class, 'sortBy'], 'argumentCount' => '2+', ], 'SQRT' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Sqrt::class, 'sqrt'], 'argumentCount' => '1', ], 'SQRTPI' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Sqrt::class, 'pi'], 'argumentCount' => '1', ], 'STANDARDIZE' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Standardize::class, 'execute'], 'argumentCount' => '3', ], 'STDEV' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\StandardDeviations::class, 'STDEV'], 'argumentCount' => '1+', ], 'STDEV.S' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\StandardDeviations::class, 'STDEV'], 'argumentCount' => '1+', ], 'STDEV.P' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\StandardDeviations::class, 'STDEVP'], 'argumentCount' => '1+', ], 'STDEVA' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\StandardDeviations::class, 'STDEVA'], 'argumentCount' => '1+', ], 'STDEVP' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\StandardDeviations::class, 'STDEVP'], 'argumentCount' => '1+', ], 'STDEVPA' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\StandardDeviations::class, 'STDEVPA'], 'argumentCount' => '1+', ], 'STEYX' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Trends::class, 'STEYX'], 'argumentCount' => '2', ], 'SUBSTITUTE' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Replace::class, 'substitute'], 'argumentCount' => '3,4', ], 'SUBTOTAL' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Subtotal::class, 'evaluate'], 'argumentCount' => '2+', 'passCellReference' => true, ], 'SUM' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Sum::class, 'sumErroringStrings'], 'argumentCount' => '1+', ], 'SUMIF' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [Statistical\Conditional::class, 'SUMIF'], 'argumentCount' => '2,3', ], 'SUMIFS' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [Statistical\Conditional::class, 'SUMIFS'], 'argumentCount' => '3+', ], 'SUMPRODUCT' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Sum::class, 'product'], 'argumentCount' => '1+', ], 'SUMSQ' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\SumSquares::class, 'sumSquare'], 'argumentCount' => '1+', ], 'SUMX2MY2' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\SumSquares::class, 'sumXSquaredMinusYSquared'], 'argumentCount' => '2', ], 'SUMX2PY2' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\SumSquares::class, 'sumXSquaredPlusYSquared'], 'argumentCount' => '2', ], 'SUMXMY2' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\SumSquares::class, 'sumXMinusYSquared'], 'argumentCount' => '2', ], 'SWITCH' => [ 'category' => Category::CATEGORY_LOGICAL, 'functionCall' => [Logical\Conditional::class, 'statementSwitch'], 'argumentCount' => '3+', ], 'SYD' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\Depreciation::class, 'SYD'], 'argumentCount' => '4', ], 'T' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Text::class, 'test'], 'argumentCount' => '1', ], 'TAKE' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, 'functionCall' => [LookupRef\ChooseRowsEtc::class, 'take'], 'argumentCount' => '2-3', ], 'TAN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Trig\Tangent::class, 'tan'], 'argumentCount' => '1', ], 'TANH' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Trig\Tangent::class, 'tanh'], 'argumentCount' => '1', ], 'TBILLEQ' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\TreasuryBill::class, 'bondEquivalentYield'], 'argumentCount' => '3', ], 'TBILLPRICE' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\TreasuryBill::class, 'price'], 'argumentCount' => '3', ], 'TBILLYIELD' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\TreasuryBill::class, 'yield'], 'argumentCount' => '3', ], 'TDIST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\StudentT::class, 'distribution'], 'argumentCount' => '3', ], 'T.DIST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '3', ], 'T.DIST.2T' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '2', ], 'T.DIST.RT' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '2', ], 'TEXT' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Format::class, 'TEXTFORMAT'], 'argumentCount' => '2', ], 'TEXTAFTER' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Extract::class, 'after'], 'argumentCount' => '2-6', ], 'TEXTBEFORE' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Extract::class, 'before'], 'argumentCount' => '2-6', ], 'TEXTJOIN' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Concatenate::class, 'TEXTJOIN'], 'argumentCount' => '3+', ], 'TEXTSPLIT' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Text::class, 'split'], 'argumentCount' => '2-6', ], 'THAIDAYOFWEEK' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '?', ], 'THAIDIGIT' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '?', ], 'THAIMONTHOFYEAR' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '?', ], 'THAINUMSOUND' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '?', ], 'THAINUMSTRING' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '?', ], 'THAISTRINGLENGTH' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '?', ], 'THAIYEAR' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '?', ], 'TIME' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, 'functionCall' => [DateTimeExcel\Time::class, 'fromHMS'], 'argumentCount' => '3', ], 'TIMEVALUE' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, 'functionCall' => [DateTimeExcel\TimeValue::class, 'fromString'], 'argumentCount' => '1', ], 'TINV' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\StudentT::class, 'inverse'], 'argumentCount' => '2', ], 'T.INV' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\StudentT::class, 'inverse'], 'argumentCount' => '2', ], 'T.INV.2T' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '2', ], 'TODAY' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, 'functionCall' => [DateTimeExcel\Current::class, 'today'], 'argumentCount' => '0', ], 'TOCOL' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '1-3', ], 'TOROW' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '1-3', ], 'TRANSPOSE' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, 'functionCall' => [LookupRef\Matrix::class, 'transpose'], 'argumentCount' => '1', ], 'TREND' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Trends::class, 'TREND'], 'argumentCount' => '1-4', ], 'TRIM' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Trim::class, 'spaces'], 'argumentCount' => '1', ], 'TRIMMEAN' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Averages\Mean::class, 'trim'], 'argumentCount' => '2', ], 'TRUE' => [ 'category' => Category::CATEGORY_LOGICAL, 'functionCall' => [Logical\Boolean::class, 'TRUE'], 'argumentCount' => '0', ], 'TRUNC' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig\Trunc::class, 'evaluate'], 'argumentCount' => '1,2', ], 'TTEST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '4', ], 'T.TEST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '4', ], 'TYPE' => [ 'category' => Category::CATEGORY_INFORMATION, 'functionCall' => [Information\Value::class, 'type'], 'argumentCount' => '1', ], 'UNICHAR' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\CharacterConvert::class, 'character'], 'argumentCount' => '1', ], 'UNICODE' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\CharacterConvert::class, 'code'], 'argumentCount' => '1', ], 'UNIQUE' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, 'functionCall' => [LookupRef\Unique::class, 'unique'], 'argumentCount' => '1+', ], 'UPPER' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\CaseConvert::class, 'upper'], 'argumentCount' => '1', ], 'USDOLLAR' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\Dollar::class, 'format'], 'argumentCount' => '2', ], 'VALUE' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Format::class, 'VALUE'], 'argumentCount' => '1', ], 'VALUETOTEXT' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Format::class, 'valueToText'], 'argumentCount' => '1,2', ], 'VAR' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Variances::class, 'VAR'], 'argumentCount' => '1+', ], 'VAR.P' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Variances::class, 'VARP'], 'argumentCount' => '1+', ], 'VAR.S' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Variances::class, 'VAR'], 'argumentCount' => '1+', ], 'VARA' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Variances::class, 'VARA'], 'argumentCount' => '1+', ], 'VARP' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Variances::class, 'VARP'], 'argumentCount' => '1+', ], 'VARPA' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Variances::class, 'VARPA'], 'argumentCount' => '1+', ], 'VDB' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '5-7', ], 'VLOOKUP' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, 'functionCall' => [LookupRef\VLookup::class, 'lookup'], 'argumentCount' => '3,4', ], 'VSTACK' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '1+', ], 'WEBSERVICE' => [ 'category' => Category::CATEGORY_WEB, 'functionCall' => [Web\Service::class, 'webService'], 'argumentCount' => '1', ], 'WEEKDAY' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, 'functionCall' => [DateTimeExcel\Week::class, 'day'], 'argumentCount' => '1,2', ], 'WEEKNUM' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, 'functionCall' => [DateTimeExcel\Week::class, 'number'], 'argumentCount' => '1,2', ], 'WEIBULL' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\Weibull::class, 'distribution'], 'argumentCount' => '4', ], 'WEIBULL.DIST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\Weibull::class, 'distribution'], 'argumentCount' => '4', ], 'WORKDAY' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, 'functionCall' => [DateTimeExcel\WorkDay::class, 'date'], 'argumentCount' => '2-3', ], 'WORKDAY.INTL' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '2-4', ], 'WRAPCOLS' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '2-3', ], 'WRAPROWS' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '2-3', ], 'XIRR' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\CashFlow\Variable\NonPeriodic::class, 'rate'], 'argumentCount' => '2,3', ], 'XLOOKUP' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '3-6', ], 'XNPV' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\CashFlow\Variable\NonPeriodic::class, 'presentValue'], 'argumentCount' => '3', ], 'XMATCH' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '2,3', ], 'XOR' => [ 'category' => Category::CATEGORY_LOGICAL, 'functionCall' => [Logical\Operations::class, 'logicalXor'], 'argumentCount' => '1+', ], 'YEAR' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, 'functionCall' => [DateTimeExcel\DateParts::class, 'year'], 'argumentCount' => '1', ], 'YEARFRAC' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, 'functionCall' => [DateTimeExcel\YearFrac::class, 'fraction'], 'argumentCount' => '2,3', ], 'YIELD' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '6,7', ], 'YIELDDISC' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\Securities\Yields::class, 'yieldDiscounted'], 'argumentCount' => '4,5', ], 'YIELDMAT' => [ 'category' => Category::CATEGORY_FINANCIAL, 'functionCall' => [Financial\Securities\Yields::class, 'yieldAtMaturity'], 'argumentCount' => '5,6', ], 'ZTEST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'zTest'], 'argumentCount' => '2-3', ], 'Z.TEST' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'zTest'], 'argumentCount' => '2-3', ], ]; } PK!'922@libraries/vendor/PhpSpreadsheet/Calculation/BinaryComparison.phpnu[ '' && $operand1[0] == Calculation::FORMULA_STRING_QUOTE) { $operand1 = Calculation::unwrapResult($operand1); } if (is_string($operand2) && $operand2 > '' && $operand2[0] == Calculation::FORMULA_STRING_QUOTE) { $operand2 = Calculation::unwrapResult($operand2); } // Use case insensitive comparaison if not OpenOffice mode if (Functions::getCompatibilityMode() != Functions::COMPATIBILITY_OPENOFFICE) { if (is_string($operand1)) { $operand1 = StringHelper::strToUpper($operand1); } if (is_string($operand2)) { $operand2 = StringHelper::strToUpper($operand2); } } $useLowercaseFirstComparison = is_string($operand1) && is_string($operand2) && Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE; return self::evaluateComparison($operand1, $operand2, $operator, $useLowercaseFirstComparison); } /** * @param mixed $operand1 * @param mixed $operand2 */ private static function evaluateComparison($operand1, $operand2, string $operator, bool $useLowercaseFirstComparison): bool { switch ($operator) { case '=': return self::equal($operand1, $operand2); case '>': return self::greaterThan($operand1, $operand2, $useLowercaseFirstComparison); case '<': return self::lessThan($operand1, $operand2, $useLowercaseFirstComparison); case '>=': return self::greaterThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison); case '<=': return self::lessThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison); case '<>': return self::notEqual($operand1, $operand2); default: throw new Exception('Unsupported binary comparison operator'); } } /** * @param mixed $operand1 * @param mixed $operand2 */ private static function equal($operand1, $operand2): bool { if (is_numeric($operand1) && is_numeric($operand2)) { $result = (abs($operand1 - $operand2) < self::DELTA); } elseif (($operand1 === null && is_numeric($operand2)) || ($operand2 === null && is_numeric($operand1))) { $result = $operand1 == $operand2; } else { $result = self::strcmpAllowNull($operand1, $operand2) == 0; } return $result; } /** * @param mixed $operand1 * @param mixed $operand2 */ private static function greaterThanOrEqual($operand1, $operand2, bool $useLowercaseFirstComparison): bool { if (is_numeric($operand1) && is_numeric($operand2)) { $result = ((abs($operand1 - $operand2) < self::DELTA) || ($operand1 > $operand2)); } elseif (($operand1 === null && is_numeric($operand2)) || ($operand2 === null && is_numeric($operand1))) { $result = $operand1 >= $operand2; } elseif ($useLowercaseFirstComparison) { $result = self::strcmpLowercaseFirst($operand1, $operand2) >= 0; } else { $result = self::strcmpAllowNull($operand1, $operand2) >= 0; } return $result; } /** * @param mixed $operand1 * @param mixed $operand2 */ private static function lessThanOrEqual($operand1, $operand2, bool $useLowercaseFirstComparison): bool { if (is_numeric($operand1) && is_numeric($operand2)) { $result = ((abs($operand1 - $operand2) < self::DELTA) || ($operand1 < $operand2)); } elseif (($operand1 === null && is_numeric($operand2)) || ($operand2 === null && is_numeric($operand1))) { $result = $operand1 <= $operand2; } elseif ($useLowercaseFirstComparison) { $result = self::strcmpLowercaseFirst($operand1, $operand2) <= 0; } else { $result = self::strcmpAllowNull($operand1, $operand2) <= 0; } return $result; } /** * @param mixed $operand1 * @param mixed $operand2 */ private static function greaterThan($operand1, $operand2, bool $useLowercaseFirstComparison): bool { return self::lessThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison) !== true; } /** * @param mixed $operand1 * @param mixed $operand2 */ private static function lessThan($operand1, $operand2, bool $useLowercaseFirstComparison): bool { return self::greaterThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison) !== true; } /** * @param mixed $operand1 * @param mixed $operand2 */ private static function notEqual($operand1, $operand2): bool { return self::equal($operand1, $operand2) !== true; } } PK!7@hPP@libraries/vendor/PhpSpreadsheet/Calculation/Statistical/Size.phpnu[= $count) { return ExcelError::NAN(); } rsort($mArgs); return $mArgs[$entry]; } return ExcelError::VALUE(); } /** * SMALL. * * Returns the nth smallest value in a data set. You can use this function to * select a value based on its relative standing. * * Excel Function: * SMALL(value1[,value2[, ...]],entry) * * @param mixed $args Data values * * @return float|string The result, or a string containing an error */ public static function small(...$args) { $aArgs = Functions::flattenArray($args); $entry = array_pop($aArgs); if ((is_numeric($entry)) && (!is_string($entry))) { $entry = (int) floor($entry); $mArgs = self::filter($aArgs); $count = Counts::COUNT($mArgs); --$entry; if ($count === 0 || $entry < 0 || $entry >= $count) { return ExcelError::NAN(); } sort($mArgs); return $mArgs[$entry]; } return ExcelError::VALUE(); } /** * @param mixed[] $args Data values */ protected static function filter(array $args): array { $mArgs = []; foreach ($args as $arg) { // Is it a numeric value? if ((is_numeric($arg)) && (!is_string($arg))) { $mArgs[] = $arg; } } return $mArgs; } } PK!ObUUGlibraries/vendor/PhpSpreadsheet/Calculation/Statistical/Percentiles.phpnu[getMessage(); } if (($entry < 0) || ($entry > 1)) { return ExcelError::NAN(); } $mArgs = self::percentileFilterValues($aArgs); $mValueCount = count($mArgs); if ($mValueCount > 0) { sort($mArgs); $count = Counts::COUNT($mArgs); $index = $entry * ($count - 1); $indexFloor = floor($index); $iBase = (int) $indexFloor; if ($index == $indexFloor) { return $mArgs[$iBase]; } $iNext = $iBase + 1; $iProportion = $index - $iBase; return $mArgs[$iBase] + (($mArgs[$iNext] - $mArgs[$iBase]) * $iProportion); } return ExcelError::NAN(); } /** * PERCENTRANK. * * Returns the rank of a value in a data set as a percentage of the data set. * Note that the returned rank is simply rounded to the appropriate significant digits, * rather than floored (as MS Excel), so value 3 for a value set of 1, 2, 3, 4 will return * 0.667 rather than 0.666 * * @param mixed $valueSet An array of (float) values, or a reference to, a list of numbers * @param mixed $value The number whose rank you want to find * @param mixed $significance The (integer) number of significant digits for the returned percentage value * * @return float|string (string if result is an error) */ public static function PERCENTRANK($valueSet, $value, $significance = 3) { $valueSet = Functions::flattenArray($valueSet); $value = Functions::flattenSingleValue($value); $significance = ($significance === null) ? 3 : Functions::flattenSingleValue($significance); try { $value = StatisticalValidations::validateFloat($value); $significance = StatisticalValidations::validateInt($significance); } catch (Exception $e) { return $e->getMessage(); } $valueSet = self::rankFilterValues($valueSet); $valueCount = count($valueSet); if ($valueCount == 0) { return ExcelError::NA(); } sort($valueSet, SORT_NUMERIC); $valueAdjustor = $valueCount - 1; if (($value < $valueSet[0]) || ($value > $valueSet[$valueAdjustor])) { return ExcelError::NA(); } $pos = array_search($value, $valueSet); if ($pos === false) { $pos = 0; $testValue = $valueSet[0]; while ($testValue < $value) { $testValue = $valueSet[++$pos]; } --$pos; $pos += (($value - $valueSet[$pos]) / ($testValue - $valueSet[$pos])); } return round(((float) $pos) / $valueAdjustor, $significance); } /** * QUARTILE. * * Returns the quartile of a data set. * * Excel Function: * QUARTILE(value1[,value2[, ...]],entry) * * @param mixed $args Data values * * @return float|string The result, or a string containing an error */ public static function QUARTILE(...$args) { $aArgs = Functions::flattenArray($args); $entry = array_pop($aArgs); try { $entry = StatisticalValidations::validateFloat($entry); } catch (Exception $e) { return $e->getMessage(); } $entry = floor($entry); $entry /= 4; if (($entry < 0) || ($entry > 1)) { return ExcelError::NAN(); } return self::PERCENTILE($aArgs, $entry); } /** * RANK. * * Returns the rank of a number in a list of numbers. * * @param mixed $value The number whose rank you want to find * @param mixed $valueSet An array of float values, or a reference to, a list of numbers * @param mixed $order Order to sort the values in the value set * * @return float|string The result, or a string containing an error (0 = Descending, 1 = Ascending) */ public static function RANK($value, $valueSet, $order = self::RANK_SORT_DESCENDING) { $value = Functions::flattenSingleValue($value); $valueSet = Functions::flattenArray($valueSet); $order = ($order === null) ? self::RANK_SORT_DESCENDING : Functions::flattenSingleValue($order); try { $value = StatisticalValidations::validateFloat($value); $order = StatisticalValidations::validateInt($order); } catch (Exception $e) { return $e->getMessage(); } $valueSet = self::rankFilterValues($valueSet); if ($order === self::RANK_SORT_DESCENDING) { rsort($valueSet, SORT_NUMERIC); } else { sort($valueSet, SORT_NUMERIC); } $pos = array_search($value, $valueSet); if ($pos === false) { return ExcelError::NA(); } return ++$pos; } protected static function percentileFilterValues(array $dataSet): array { return array_filter( $dataSet, fn ($value): bool => is_numeric($value) && !is_string($value) ); } protected static function rankFilterValues(array $dataSet): array { return array_filter( $dataSet, fn ($value): bool => is_numeric($value) ); } } PK!vx44Alibraries/vendor/PhpSpreadsheet/Calculation/Statistical/Range.phpnu[getMessage(); } if (($alpha <= 0) || ($alpha >= 1) || ($stdDev <= 0) || ($size < 1)) { return ExcelError::NAN(); } /** @var float $temp */ $temp = Distributions\StandardNormal::inverse(1 - $alpha / 2); /** @var float */ $result = Functions::scalar($temp * $stdDev / sqrt($size)); return $result; } } PK! Clibraries/vendor/PhpSpreadsheet/Calculation/Statistical/Maximum.phpnu[ $returnValue)) { $returnValue = $arg; } } } if ($returnValue === null) { return 0; } return $returnValue; } /** * MAXA. * * Returns the greatest value in a list of arguments, including numbers, text, and logical values * * Excel Function: * MAXA(value1[,value2[, ...]]) * * @param mixed ...$args Data values * @return float|int|string */ public static function maxA(...$args) { $returnValue = null; // Loop through arguments $aArgs = Functions::flattenArray($args); foreach ($aArgs as $arg) { if (ErrorValue::isError($arg)) { $returnValue = $arg; break; } // Is it a numeric value? if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) { $arg = self::datatypeAdjustmentAllowStrings($arg); if (($returnValue === null) || ($arg > $returnValue)) { $returnValue = $arg; } } } if ($returnValue === null) { return 0; } return $returnValue; } } PK!nljIlibraries/vendor/PhpSpreadsheet/Calculation/Statistical/AggregateBase.phpnu[getMessage(); } if (($value < 0) || ($lambda < 0)) { return ExcelError::NAN(); } if ($cumulative === true) { return 1 - exp(0 - $value * $lambda); } return $lambda * exp(0 - $value * $lambda); } } PK!Plibraries/vendor/PhpSpreadsheet/Calculation/Statistical/Distributions/Normal.phpnu[getMessage(); } if ($stdDev < 0) { return ExcelError::NAN(); } if ($cumulative) { return 0.5 * (1 + Engineering\Erf::erfValue(($value - $mean) / ($stdDev * sqrt(2)))); } return (1 / (self::SQRT2PI * $stdDev)) * exp(0 - (($value - $mean) ** 2 / (2 * ($stdDev * $stdDev)))); } /** * NORMINV. * * Returns the inverse of the normal cumulative distribution for the specified mean and standard deviation. * * @param mixed $probability Float probability for which we want the value * Or can be an array of values * @param mixed $mean Mean Value as a float * Or can be an array of values * @param mixed $stdDev Standard Deviation as a float * Or can be an array of values * * @return array|float|string The result, or a string containing an error * If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function inverse($probability, $mean, $stdDev) { if (is_array($probability) || is_array($mean) || is_array($stdDev)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $probability, $mean, $stdDev); } try { $probability = DistributionValidations::validateProbability($probability); $mean = DistributionValidations::validateFloat($mean); $stdDev = DistributionValidations::validateFloat($stdDev); } catch (Exception $e) { return $e->getMessage(); } if ($stdDev < 0) { return ExcelError::NAN(); } return (self::inverseNcdf($probability) * $stdDev) + $mean; } /* * inverse_ncdf.php * ------------------- * begin : Friday, January 16, 2004 * copyright : (C) 2004 Michael Nickerson * email : nickersonm@yahoo.com * */ private static function inverseNcdf(float $p): float { // Inverse ncdf approximation by Peter J. Acklam, implementation adapted to // PHP by Michael Nickerson, using Dr. Thomas Ziegler's C implementation as // a guide. http://home.online.no/~pjacklam/notes/invnorm/index.html // I have not checked the accuracy of this implementation. Be aware that PHP // will truncate the coeficcients to 14 digits. // You have permission to use and distribute this function freely for // whatever purpose you want, but please show common courtesy and give credit // where credit is due. // Input paramater is $p - probability - where 0 < p < 1. // Coefficients in rational approximations static $a = [ 1 => -3.969683028665376e+01, 2 => 2.209460984245205e+02, 3 => -2.759285104469687e+02, 4 => 1.383577518672690e+02, 5 => -3.066479806614716e+01, 6 => 2.506628277459239e+00, ]; static $b = [ 1 => -5.447609879822406e+01, 2 => 1.615858368580409e+02, 3 => -1.556989798598866e+02, 4 => 6.680131188771972e+01, 5 => -1.328068155288572e+01, ]; static $c = [ 1 => -7.784894002430293e-03, 2 => -3.223964580411365e-01, 3 => -2.400758277161838e+00, 4 => -2.549732539343734e+00, 5 => 4.374664141464968e+00, 6 => 2.938163982698783e+00, ]; static $d = [ 1 => 7.784695709041462e-03, 2 => 3.224671290700398e-01, 3 => 2.445134137142996e+00, 4 => 3.754408661907416e+00, ]; // Define lower and upper region break-points. $p_low = 0.02425; //Use lower region approx. below this $p_high = 1 - $p_low; //Use upper region approx. above this if (0 < $p && $p < $p_low) { // Rational approximation for lower region. $q = sqrt(-2 * log($p)); return ((((($c[1] * $q + $c[2]) * $q + $c[3]) * $q + $c[4]) * $q + $c[5]) * $q + $c[6]) / (((($d[1] * $q + $d[2]) * $q + $d[3]) * $q + $d[4]) * $q + 1); } elseif ($p_high < $p && $p < 1) { // Rational approximation for upper region. $q = sqrt(-2 * log(1 - $p)); return -((((($c[1] * $q + $c[2]) * $q + $c[3]) * $q + $c[4]) * $q + $c[5]) * $q + $c[6]) / (((($d[1] * $q + $d[2]) * $q + $d[3]) * $q + $d[4]) * $q + 1); } // Rational approximation for central region. $q = $p - 0.5; $r = $q * $q; return ((((($a[1] * $r + $a[2]) * $r + $a[3]) * $r + $a[4]) * $r + $a[5]) * $r + $a[6]) * $q / ((((($b[1] * $r + $b[2]) * $r + $b[3]) * $r + $b[4]) * $r + $b[5]) * $r + 1); } } PK!Iyp p Nlibraries/vendor/PhpSpreadsheet/Calculation/Statistical/Distributions/Beta.phpnu[getMessage(); } if ($rMin > $rMax) { $tmp = $rMin; $rMin = $rMax; $rMax = $tmp; } if (($value < $rMin) || ($value > $rMax) || ($alpha <= 0) || ($beta <= 0) || ($rMin == $rMax)) { return ExcelError::NAN(); } $value -= $rMin; $value /= ($rMax - $rMin); return self::incompleteBeta($value, $alpha, $beta); } /** * BETAINV. * * Returns the inverse of the Beta distribution. * * @param mixed $probability Float probability at which you want to evaluate the distribution * Or can be an array of values * @param mixed $alpha Parameter to the distribution as a float * Or can be an array of values * @param mixed $beta Parameter to the distribution as a float * Or can be an array of values * @param mixed $rMin Minimum value as a float * Or can be an array of values * @param mixed $rMax Maximum value as a float * Or can be an array of values * * @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function inverse($probability, $alpha, $beta, $rMin = 0.0, $rMax = 1.0) { if (is_array($probability) || is_array($alpha) || is_array($beta) || is_array($rMin) || is_array($rMax)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $probability, $alpha, $beta, $rMin, $rMax); } $rMin = $rMin ?? 0.0; $rMax = $rMax ?? 1.0; try { $probability = DistributionValidations::validateProbability($probability); $alpha = DistributionValidations::validateFloat($alpha); $beta = DistributionValidations::validateFloat($beta); $rMax = DistributionValidations::validateFloat($rMax); $rMin = DistributionValidations::validateFloat($rMin); } catch (Exception $e) { return $e->getMessage(); } if ($rMin > $rMax) { $tmp = $rMin; $rMin = $rMax; $rMax = $tmp; } if (($alpha <= 0) || ($beta <= 0) || ($rMin == $rMax) || ($probability <= 0.0)) { return ExcelError::NAN(); } return self::calculateInverse($probability, $alpha, $beta, $rMin, $rMax); } /** * @return float|string */ private static function calculateInverse(float $probability, float $alpha, float $beta, float $rMin, float $rMax) { $a = 0; $b = 2; $guess = ($a + $b) / 2; $i = 0; while ((($b - $a) > Functions::PRECISION) && (++$i <= self::MAX_ITERATIONS)) { $guess = ($a + $b) / 2; $result = self::distribution($guess, $alpha, $beta); if (($result === $probability) || ($result === 0.0)) { $b = $a; } elseif ($result > $probability) { $b = $guess; } else { $a = $guess; } } if ($i === self::MAX_ITERATIONS) { return ExcelError::NA(); } return round($rMin + $guess * ($rMax - $rMin), 12); } /** * Incomplete beta function. * * @author Jaco van Kooten * @author Paul Meagher * * The computation is based on formulas from Numerical Recipes, Chapter 6.4 (W.H. Press et al, 1992). * * @param float $x require 0<=x<=1 * @param float $p require p>0 * @param float $q require q>0 * * @return float 0 if x<0, p<=0, q<=0 or p+q>2.55E305 and 1 if x>1 to avoid errors and over/underflow */ public static function incompleteBeta(float $x, float $p, float $q): float { if ($x <= 0.0) { return 0.0; } elseif ($x >= 1.0) { return 1.0; } elseif (($p <= 0.0) || ($q <= 0.0) || (($p + $q) > self::LOG_GAMMA_X_MAX_VALUE)) { return 0.0; } $beta_gam = exp((0 - self::logBeta($p, $q)) + $p * log($x) + $q * log(1.0 - $x)); if ($x < ($p + 1.0) / ($p + $q + 2.0)) { return $beta_gam * self::betaFraction($x, $p, $q) / $p; } return 1.0 - ($beta_gam * self::betaFraction(1 - $x, $q, $p) / $q); } // Function cache for logBeta function private static float $logBetaCacheP = 0.0; private static float $logBetaCacheQ = 0.0; private static float $logBetaCacheResult = 0.0; /** * The natural logarithm of the beta function. * * @param float $p require p>0 * @param float $q require q>0 * * @return float 0 if p<=0, q<=0 or p+q>2.55E305 to avoid errors and over/underflow * * @author Jaco van Kooten */ private static function logBeta(float $p, float $q): float { if ($p != self::$logBetaCacheP || $q != self::$logBetaCacheQ) { self::$logBetaCacheP = $p; self::$logBetaCacheQ = $q; if (($p <= 0.0) || ($q <= 0.0) || (($p + $q) > self::LOG_GAMMA_X_MAX_VALUE)) { self::$logBetaCacheResult = 0.0; } else { self::$logBetaCacheResult = Gamma::logGamma($p) + Gamma::logGamma($q) - Gamma::logGamma($p + $q); } } return self::$logBetaCacheResult; } /** * Evaluates of continued fraction part of incomplete beta function. * Based on an idea from Numerical Recipes (W.H. Press et al, 1992). * * @author Jaco van Kooten */ private static function betaFraction(float $x, float $p, float $q): float { $c = 1.0; $sum_pq = $p + $q; $p_plus = $p + 1.0; $p_minus = $p - 1.0; $h = 1.0 - $sum_pq * $x / $p_plus; if (abs($h) < self::XMININ) { $h = self::XMININ; } $h = 1.0 / $h; $frac = $h; $m = 1; $delta = 0.0; while ($m <= self::MAX_ITERATIONS && abs($delta - 1.0) > Functions::PRECISION) { $m2 = 2 * $m; // even index for d $d = $m * ($q - $m) * $x / (($p_minus + $m2) * ($p + $m2)); $h = 1.0 + $d * $h; if (abs($h) < self::XMININ) { $h = self::XMININ; } $h = 1.0 / $h; $c = 1.0 + $d / $c; if (abs($c) < self::XMININ) { $c = self::XMININ; } $frac *= $h * $c; // odd index for d $d = -($p + $m) * ($sum_pq + $m) * $x / (($p + $m2) * ($p_plus + $m2)); $h = 1.0 + $d * $h; if (abs($h) < self::XMININ) { $h = self::XMININ; } $h = 1.0 / $h; $c = 1.0 + $d / $c; if (abs($c) < self::XMININ) { $c = self::XMININ; } $delta = $h * $c; $frac *= $delta; ++$m; } return $frac; } /* private static function betaValue(float $a, float $b): float { return (Gamma::gammaValue($a) * Gamma::gammaValue($b)) / Gamma::gammaValue($a + $b); } private static function regularizedIncompleteBeta(float $value, float $a, float $b): float { return self::incompleteBeta($value, $a, $b) / self::betaValue($a, $b); } */ } PK!{4NXlibraries/vendor/PhpSpreadsheet/Calculation/Statistical/Distributions/StandardNormal.phpnu[callback = $callback; } /** * @return float|int|string */ public function execute(float $probability) { $xLo = 100; $xHi = 0; $dx = 1; $x = $xNew = 1; $i = 0; while ((abs($dx) > Functions::PRECISION) && ($i++ < self::MAX_ITERATIONS)) { // Apply Newton-Raphson step $result = call_user_func($this->callback, $x); $error = $result - $probability; if ($error == 0.0) { $dx = 0; } elseif ($error < 0.0) { $xLo = $x; } else { $xHi = $x; } // Avoid division by zero if ($result != 0.0) { $dx = $error / $result; $xNew = $x - $dx; } // If the NR fails to converge (which for example may be the // case if the initial guess is too rough) we apply a bisection // step to determine a more narrow interval around the root. if (($xNew < $xLo) || ($xNew > $xHi) || ($result == 0.0)) { $xNew = ($xLo + $xHi) / 2; $dx = $xNew - $x; } $x = $xNew; } if ($i == self::MAX_ITERATIONS) { return ExcelError::NA(); } return $x; } } PK!ɔ%%Slibraries/vendor/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.phpnu[ Functions::PRECISION) && (++$i <= self::MAX_ITERATIONS)) { // Apply Newton-Raphson step $result = self::calculateDistribution($x, $alpha, $beta, true); $error = $result - $probability; if ($error == 0.0) { $dx = 0; } elseif ($error < 0.0) { $xLo = $x; } else { $xHi = $x; } $pdf = self::calculateDistribution($x, $alpha, $beta, false); // Avoid division by zero if ($pdf !== 0.0) { $dx = $error / $pdf; $xNew = $x - $dx; } // If the NR fails to converge (which for example may be the // case if the initial guess is too rough) we apply a bisection // step to determine a more narrow interval around the root. if (($xNew < $xLo) || ($xNew > $xHi) || ($pdf == 0.0)) { $xNew = ($xLo + $xHi) / 2; $dx = $xNew - $x; } $x = $xNew; } if ($i === self::MAX_ITERATIONS) { return ExcelError::NA(); } return $x; } // // Implementation of the incomplete Gamma function // public static function incompleteGamma(float $a, float $x): float { static $max = 32; $summer = 0; for ($n = 0; $n <= $max; ++$n) { $divisor = $a; for ($i = 1; $i <= $n; ++$i) { $divisor *= ($a + $i); } $summer += ($x ** $n / $divisor); } return $x ** $a * exp(0 - $x) * $summer; } // // Implementation of the Gamma function // public static function gammaValue(float $value): float { if ($value == 0.0) { return 0; } static $p0 = 1.000000000190015; static $p = [ 1 => 76.18009172947146, 2 => -86.50532032941677, 3 => 24.01409824083091, 4 => -1.231739572450155, 5 => 1.208650973866179e-3, 6 => -5.395239384953e-6, ]; $y = $x = $value; $tmp = $x + 5.5; $tmp -= ($x + 0.5) * log($tmp); $summer = $p0; for ($j = 1; $j <= 6; ++$j) { $summer += ($p[$j] / ++$y); } return exp(0 - $tmp + log(self::SQRT2PI * $summer / $x)); } private const LG_D1 = -0.5772156649015328605195174; private const LG_D2 = 0.4227843350984671393993777; private const LG_D4 = 1.791759469228055000094023; private const LG_P1 = [ 4.945235359296727046734888, 201.8112620856775083915565, 2290.838373831346393026739, 11319.67205903380828685045, 28557.24635671635335736389, 38484.96228443793359990269, 26377.48787624195437963534, 7225.813979700288197698961, ]; private const LG_P2 = [ 4.974607845568932035012064, 542.4138599891070494101986, 15506.93864978364947665077, 184793.2904445632425417223, 1088204.76946882876749847, 3338152.967987029735917223, 5106661.678927352456275255, 3074109.054850539556250927, ]; private const LG_P4 = [ 14745.02166059939948905062, 2426813.369486704502836312, 121475557.4045093227939592, 2663432449.630976949898078, 29403789566.34553899906876, 170266573776.5398868392998, 492612579337.743088758812, 560625185622.3951465078242, ]; private const LG_Q1 = [ 67.48212550303777196073036, 1113.332393857199323513008, 7738.757056935398733233834, 27639.87074403340708898585, 54993.10206226157329794414, 61611.22180066002127833352, 36351.27591501940507276287, 8785.536302431013170870835, ]; private const LG_Q2 = [ 183.0328399370592604055942, 7765.049321445005871323047, 133190.3827966074194402448, 1136705.821321969608938755, 5267964.117437946917577538, 13467014.54311101692290052, 17827365.30353274213975932, 9533095.591844353613395747, ]; private const LG_Q4 = [ 2690.530175870899333379843, 639388.5654300092398984238, 41355999.30241388052042842, 1120872109.61614794137657, 14886137286.78813811542398, 101680358627.2438228077304, 341747634550.7377132798597, 446315818741.9713286462081, ]; private const LG_C = [ -0.001910444077728, 8.4171387781295e-4, -5.952379913043012e-4, 7.93650793500350248e-4, -0.002777777777777681622553, 0.08333333333333333331554247, 0.0057083835261, ]; // Rough estimate of the fourth root of logGamma_xBig private const LG_FRTBIG = 2.25e76; private const PNT68 = 0.6796875; // Function cache for logGamma private static float $logGammaCacheResult = 0.0; private static float $logGammaCacheX = 0.0; /** * logGamma function. * * Original author was Jaco van Kooten. Ported to PHP by Paul Meagher. * * The natural logarithm of the gamma function.
    * Based on public domain NETLIB (Fortran) code by W. J. Cody and L. Stoltz
    * Applied Mathematics Division
    * Argonne National Laboratory
    * Argonne, IL 60439
    *

    * References: *

      *
    1. W. J. Cody and K. E. Hillstrom, 'Chebyshev Approximations for the Natural * Logarithm of the Gamma Function,' Math. Comp. 21, 1967, pp. 198-203.
    2. *
    3. K. E. Hillstrom, ANL/AMD Program ANLC366S, DGAMMA/DLGAMA, May, 1969.
    4. *
    5. Hart, Et. Al., Computer Approximations, Wiley and sons, New York, 1968.
    6. *
    *

    *

    * From the original documentation: *

    *

    * This routine calculates the LOG(GAMMA) function for a positive real argument X. * Computation is based on an algorithm outlined in references 1 and 2. * The program uses rational functions that theoretically approximate LOG(GAMMA) * to at least 18 significant decimal digits. The approximation for X > 12 is from * reference 3, while approximations for X < 12.0 are similar to those in reference * 1, but are unpublished. The accuracy achieved depends on the arithmetic system, * the compiler, the intrinsic functions, and proper selection of the * machine-dependent constants. *

    *

    * Error returns:
    * The program returns the value XINF for X .LE. 0.0 or when overflow would occur. * The computation is believed to be free of underflow and overflow. *

    * * @version 1.1 * * @author Jaco van Kooten * * @return float MAX_VALUE for x < 0.0 or when overflow would occur, i.e. x > 2.55E305 */ public static function logGamma(float $x): float { if ($x == self::$logGammaCacheX) { return self::$logGammaCacheResult; } $y = $x; if ($y > 0.0 && $y <= self::LOG_GAMMA_X_MAX_VALUE) { if ($y <= self::EPS) { $res = -log($y); } elseif ($y <= 1.5) { $res = self::logGamma1($y); } elseif ($y <= 4.0) { $res = self::logGamma2($y); } elseif ($y <= 12.0) { $res = self::logGamma3($y); } else { $res = self::logGamma4($y); } } else { // -------------------------- // Return for bad arguments // -------------------------- $res = self::MAX_VALUE; } // ------------------------------ // Final adjustments and return // ------------------------------ self::$logGammaCacheX = $x; self::$logGammaCacheResult = $res; return $res; } private static function logGamma1(float $y): float { // --------------------- // EPS .LT. X .LE. 1.5 // --------------------- if ($y < self::PNT68) { $corr = -log($y); $xm1 = $y; } else { $corr = 0.0; $xm1 = $y - 1.0; } $xden = 1.0; $xnum = 0.0; if ($y <= 0.5 || $y >= self::PNT68) { for ($i = 0; $i < 8; ++$i) { $xnum = $xnum * $xm1 + self::LG_P1[$i]; $xden = $xden * $xm1 + self::LG_Q1[$i]; } return $corr + $xm1 * (self::LG_D1 + $xm1 * ($xnum / $xden)); } $xm2 = $y - 1.0; for ($i = 0; $i < 8; ++$i) { $xnum = $xnum * $xm2 + self::LG_P2[$i]; $xden = $xden * $xm2 + self::LG_Q2[$i]; } return $corr + $xm2 * (self::LG_D2 + $xm2 * ($xnum / $xden)); } private static function logGamma2(float $y): float { // --------------------- // 1.5 .LT. X .LE. 4.0 // --------------------- $xm2 = $y - 2.0; $xden = 1.0; $xnum = 0.0; for ($i = 0; $i < 8; ++$i) { $xnum = $xnum * $xm2 + self::LG_P2[$i]; $xden = $xden * $xm2 + self::LG_Q2[$i]; } return $xm2 * (self::LG_D2 + $xm2 * ($xnum / $xden)); } protected static function logGamma3(float $y): float { // ---------------------- // 4.0 .LT. X .LE. 12.0 // ---------------------- $xm4 = $y - 4.0; $xden = -1.0; $xnum = 0.0; for ($i = 0; $i < 8; ++$i) { $xnum = $xnum * $xm4 + self::LG_P4[$i]; $xden = $xden * $xm4 + self::LG_Q4[$i]; } return self::LG_D4 + $xm4 * ($xnum / $xden); } protected static function logGamma4(float $y): float { // --------------------------------- // Evaluate for argument .GE. 12.0 // --------------------------------- $res = 0.0; if ($y <= self::LG_FRTBIG) { $res = self::LG_C[6]; $ysq = $y * $y; for ($i = 0; $i < 6; ++$i) { $res = $res / $ysq + self::LG_C[$i]; } $res /= $y; $corr = log($y); $res = $res + log(self::SQRT2PI) - 0.5 * $corr; $res += $y * ($corr - 1.0); } return $res; } } PK!.%%Tlibraries/vendor/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.phpnu[getMessage(); } if ($degrees < 1) { return ExcelError::NAN(); } if ($value < 0) { if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { return 1; } return ExcelError::NAN(); } return 1 - (Gamma::incompleteGamma($degrees / 2, $value / 2) / Gamma::gammaValue($degrees / 2)); } /** * CHIDIST. * * Returns the one-tailed probability of the chi-squared distribution. * * @param mixed $value Float value for which we want the probability * Or can be an array of values * @param mixed $degrees Integer degrees of freedom * Or can be an array of values * @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false) * Or can be an array of values * * @return array|float|int|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function distributionLeftTail($value, $degrees, $cumulative) { if (is_array($value) || is_array($degrees) || is_array($cumulative)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $degrees, $cumulative); } try { $value = DistributionValidations::validateFloat($value); $degrees = DistributionValidations::validateInt($degrees); $cumulative = DistributionValidations::validateBool($cumulative); } catch (Exception $e) { return $e->getMessage(); } if ($degrees < 1) { return ExcelError::NAN(); } if ($value < 0) { if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { return 1; } return ExcelError::NAN(); } if ($cumulative === true) { $temp = self::distributionRightTail($value, $degrees); return 1 - (is_numeric($temp) ? $temp : 0); } return ($value ** (($degrees / 2) - 1) * exp(-$value / 2)) / ((2 ** ($degrees / 2)) * Gamma::gammaValue($degrees / 2)); } /** * CHIINV. * * Returns the inverse of the right-tailed probability of the chi-squared distribution. * * @param mixed $probability Float probability at which you want to evaluate the distribution * Or can be an array of values * @param mixed $degrees Integer degrees of freedom * Or can be an array of values * * @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function inverseRightTail($probability, $degrees) { if (is_array($probability) || is_array($degrees)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $probability, $degrees); } try { $probability = DistributionValidations::validateProbability($probability); $degrees = DistributionValidations::validateInt($degrees); } catch (Exception $e) { return $e->getMessage(); } if ($degrees < 1) { return ExcelError::NAN(); } $callback = fn ($value): float => 1 - (Gamma::incompleteGamma($degrees / 2, $value / 2) / Gamma::gammaValue($degrees / 2)); $newtonRaphson = new NewtonRaphson($callback); return $newtonRaphson->execute($probability); } /** * CHIINV. * * Returns the inverse of the left-tailed probability of the chi-squared distribution. * * @param mixed $probability Float probability at which you want to evaluate the distribution * Or can be an array of values * @param mixed $degrees Integer degrees of freedom * Or can be an array of values * * @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function inverseLeftTail($probability, $degrees) { if (is_array($probability) || is_array($degrees)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $probability, $degrees); } try { $probability = DistributionValidations::validateProbability($probability); $degrees = DistributionValidations::validateInt($degrees); } catch (Exception $e) { return $e->getMessage(); } if ($degrees < 1) { return ExcelError::NAN(); } return self::inverseLeftTailCalculation($probability, $degrees); } /** * CHITEST. * * Uses the chi-square test to calculate the probability that the differences between two supplied data sets * (of observed and expected frequencies), are likely to be simply due to sampling error, * or if they are likely to be real. * * @param array $actual an array of observed frequencies * @param array $expected an array of expected frequencies * @return float|string */ public static function test($actual, $expected) { $rows = count($actual); $actual = Functions::flattenArray($actual); $expected = Functions::flattenArray($expected); $columns = intdiv(count($actual), $rows); $countActuals = count($actual); $countExpected = count($expected); if ($countActuals !== $countExpected || $countActuals === 1) { return ExcelError::NAN(); } $result = 0.0; for ($i = 0; $i < $countActuals; ++$i) { if ($expected[$i] == 0.0) { return ExcelError::DIV0(); } elseif ($expected[$i] < 0.0) { return ExcelError::NAN(); } $result += (($actual[$i] - $expected[$i]) ** 2) / $expected[$i]; } $degrees = self::degrees($rows, $columns); /** @var float|string */ $result = Functions::scalar(self::distributionRightTail($result, $degrees)); return $result; } protected static function degrees(int $rows, int $columns): int { if ($rows === 1) { return $columns - 1; } elseif ($columns === 1) { return $rows - 1; } return ($columns - 1) * ($rows - 1); } private static function inverseLeftTailCalculation(float $probability, int $degrees): float { // bracket the root $min = 0; $sd = sqrt(2.0 * $degrees); $max = 2 * $sd; $s = -1; while ($s * self::pchisq($max, $degrees) > $probability * $s) { $min = $max; $max += 2 * $sd; } // Find root using bisection $chi2 = 0.5 * ($min + $max); while (($max - $min) > self::EPS * $chi2) { if ($s * self::pchisq($chi2, $degrees) > $probability * $s) { $min = $chi2; } else { $max = $chi2; } $chi2 = 0.5 * ($min + $max); } return $chi2; } private static function pchisq(float $chi2, int $degrees): float { return self::gammp($degrees, 0.5 * $chi2); } private static function gammp(int $n, float $x): float { if ($x < 0.5 * $n + 1) { return self::gser($n, $x); } return 1 - self::gcf($n, $x); } // Return the incomplete gamma function P(n/2,x) evaluated by // series representation. Algorithm from numerical recipe. // Assume that n is a positive integer and x>0, won't check arguments. // Relative error controlled by the eps parameter private static function gser(int $n, float $x): float { /** @var float $gln */ $gln = Gamma::ln($n / 2); $a = 0.5 * $n; $ap = $a; $sum = 1.0 / $a; $del = $sum; for ($i = 1; $i < 101; ++$i) { ++$ap; $del = $del * $x / $ap; $sum += $del; if ($del < $sum * self::EPS) { break; } } return $sum * exp(-$x + $a * log($x) - $gln); } // Return the incomplete gamma function Q(n/2,x) evaluated by // its continued fraction representation. Algorithm from numerical recipe. // Assume that n is a postive integer and x>0, won't check arguments. // Relative error controlled by the eps parameter private static function gcf(int $n, float $x): float { /** @var float $gln */ $gln = Gamma::ln($n / 2); $a = 0.5 * $n; $b = $x + 1 - $a; $fpmin = 1.e-300; $c = 1 / $fpmin; $d = 1 / $b; $h = $d; for ($i = 1; $i < 101; ++$i) { $an = -$i * ($i - $a); $b += 2; $d = $an * $d + $b; if (abs($d) < $fpmin) { $d = $fpmin; } $c = $b + $an / $c; if (abs($c) < $fpmin) { $c = $fpmin; } $d = 1 / $d; $del = $d * $c; $h = $h * $del; if (abs($del - 1) < self::EPS) { break; } } return $h * exp(-$x + $a * log($x) - $gln); } } PK!Olibraries/vendor/PhpSpreadsheet/Calculation/Statistical/Distributions/index.phpnu[getMessage(); } if (($value < 0) || ($value > $trials)) { return ExcelError::NAN(); } if ($cumulative) { return self::calculateCumulativeBinomial($value, $trials, $probability); } /** @var float $comb */ $comb = Combinations::withoutRepetition($trials, $value); return $comb * $probability ** $value * (1 - $probability) ** ($trials - $value); } /** * BINOM.DIST.RANGE. * * Returns returns the Binomial Distribution probability for the number of successes from a specified number * of trials falling into a specified range. * * @param mixed $trials Integer number of trials * Or can be an array of values * @param mixed $probability Probability of success on each trial as a float * Or can be an array of values * @param mixed $successes The integer number of successes in trials * Or can be an array of values * @param mixed $limit Upper limit for successes in trials as null, or an integer * If null, then this will indicate the same as the number of Successes * Or can be an array of values * * @return array|float|int|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function range($trials, $probability, $successes, $limit = null) { if (is_array($trials) || is_array($probability) || is_array($successes) || is_array($limit)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $trials, $probability, $successes, $limit); } $limit = $limit ?? $successes; try { $trials = DistributionValidations::validateInt($trials); $probability = DistributionValidations::validateProbability($probability); $successes = DistributionValidations::validateInt($successes); $limit = DistributionValidations::validateInt($limit); } catch (Exception $e) { return $e->getMessage(); } if (($successes < 0) || ($successes > $trials)) { return ExcelError::NAN(); } if (($limit < 0) || ($limit > $trials) || $limit < $successes) { return ExcelError::NAN(); } $summer = 0; for ($i = $successes; $i <= $limit; ++$i) { /** @var float $comb */ $comb = Combinations::withoutRepetition($trials, $i); $summer += $comb * $probability ** $i * (1 - $probability) ** ($trials - $i); } return $summer; } /** * NEGBINOMDIST. * * Returns the negative binomial distribution. NEGBINOMDIST returns the probability that * there will be number_f failures before the number_s-th success, when the constant * probability of a success is probability_s. This function is similar to the binomial * distribution, except that the number of successes is fixed, and the number of trials is * variable. Like the binomial, trials are assumed to be independent. * * @param mixed $failures Number of Failures as an integer * Or can be an array of values * @param mixed $successes Threshold number of Successes as an integer * Or can be an array of values * @param mixed $probability Probability of success on each trial as a float * Or can be an array of values * * @return array|float|string The result, or a string containing an error * If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions * * TODO Add support for the cumulative flag not present for NEGBINOMDIST, but introduced for NEGBINOM.DIST * The cumulative default should be false to reflect the behaviour of NEGBINOMDIST */ public static function negative($failures, $successes, $probability) { if (is_array($failures) || is_array($successes) || is_array($probability)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $failures, $successes, $probability); } try { $failures = DistributionValidations::validateInt($failures); $successes = DistributionValidations::validateInt($successes); $probability = DistributionValidations::validateProbability($probability); } catch (Exception $e) { return $e->getMessage(); } if (($failures < 0) || ($successes < 1)) { return ExcelError::NAN(); } if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { if (($failures + $successes - 1) <= 0) { return ExcelError::NAN(); } } /** @var float $comb */ $comb = Combinations::withoutRepetition($failures + $successes - 1, $successes - 1); return $comb * ($probability ** $successes) * ((1 - $probability) ** $failures); } /** * BINOM.INV. * * Returns the smallest value for which the cumulative binomial distribution is greater * than or equal to a criterion value * * @param mixed $trials number of Bernoulli trials as an integer * Or can be an array of values * @param mixed $probability probability of a success on each trial as a float * Or can be an array of values * @param mixed $alpha criterion value as a float * Or can be an array of values * * @return array|int|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function inverse($trials, $probability, $alpha) { if (is_array($trials) || is_array($probability) || is_array($alpha)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $trials, $probability, $alpha); } try { $trials = DistributionValidations::validateInt($trials); $probability = DistributionValidations::validateProbability($probability); $alpha = DistributionValidations::validateFloat($alpha); } catch (Exception $e) { return $e->getMessage(); } if ($trials < 0) { return ExcelError::NAN(); } elseif (($alpha < 0.0) || ($alpha > 1.0)) { return ExcelError::NAN(); } $successes = 0; while ($successes <= $trials) { $result = self::calculateCumulativeBinomial($successes, $trials, $probability); if ($result >= $alpha) { break; } ++$successes; } return $successes; } /** * @return float|int */ private static function calculateCumulativeBinomial(int $value, int $trials, float $probability) { $summer = 0; for ($i = 0; $i <= $value; ++$i) { /** @var float $comb */ $comb = Combinations::withoutRepetition($trials, $i); $summer += $comb * $probability ** $i * (1 - $probability) ** ($trials - $i); } return $summer; } } PK!% JRlibraries/vendor/PhpSpreadsheet/Calculation/Statistical/Distributions/StudentT.phpnu[getMessage(); } if (($value < 0) || ($degrees < 1) || ($tails < 1) || ($tails > 2)) { return ExcelError::NAN(); } return self::calculateDistribution($value, $degrees, $tails); } /** * TINV. * * Returns the one-tailed probability of the chi-squared distribution. * * @param mixed $probability Float probability for the function * Or can be an array of values * @param mixed $degrees Integer value for degrees of freedom * Or can be an array of values * * @return array|float|string The result, or a string containing an error * If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function inverse($probability, $degrees) { if (is_array($probability) || is_array($degrees)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $probability, $degrees); } try { $probability = DistributionValidations::validateProbability($probability); $degrees = DistributionValidations::validateInt($degrees); } catch (Exception $e) { return $e->getMessage(); } if ($degrees <= 0) { return ExcelError::NAN(); } $callback = fn ($value) => self::distribution($value, $degrees, 2); $newtonRaphson = new NewtonRaphson($callback); return $newtonRaphson->execute($probability); } private static function calculateDistribution(float $value, int $degrees, int $tails): float { // tdist, which finds the probability that corresponds to a given value // of t with k degrees of freedom. This algorithm is translated from a // pascal function on p81 of "Statistical Computing in Pascal" by D // Cooke, A H Craven & G M Clark (1985: Edward Arnold (Pubs.) Ltd: // London). The above Pascal algorithm is itself a translation of the // fortran algoritm "AS 3" by B E Cooper of the Atlas Computer // Laboratory as reported in (among other places) "Applied Statistics // Algorithms", editied by P Griffiths and I D Hill (1985; Ellis // Horwood Ltd.; W. Sussex, England). $tterm = $degrees; $ttheta = atan2($value, sqrt($tterm)); $tc = cos($ttheta); $ts = sin($ttheta); if (($degrees % 2) === 1) { $ti = 3; $tterm = $tc; } else { $ti = 2; $tterm = 1; } $tsum = $tterm; while ($ti < $degrees) { $tterm *= $tc * $tc * ($ti - 1) / $ti; $tsum += $tterm; $ti += 2; } $tsum *= $ts; if (($degrees % 2) == 1) { $tsum = Functions::M_2DIVPI * ($tsum + $ttheta); } $tValue = 0.5 * (1 + $tsum); if ($tails == 1) { return 1 - abs($tValue); } return 1 - abs((1 - $tValue) - $tValue); } } PK!oQlibraries/vendor/PhpSpreadsheet/Calculation/Statistical/Distributions/Poisson.phpnu[getMessage(); } if (($value < 0) || ($mean < 0)) { return ExcelError::NAN(); } if ($cumulative) { $summer = 0; $floor = floor($value); for ($i = 0; $i <= $floor; ++$i) { /** @var float $fact */ $fact = MathTrig\Factorial::fact($i); $summer += $mean ** $i / $fact; } return exp(0 - $mean) * $summer; } /** @var float $fact */ $fact = MathTrig\Factorial::fact($value); return (exp(0 - $mean) * $mean ** $value) / $fact; } } PK!Aalibraries/vendor/PhpSpreadsheet/Calculation/Statistical/Distributions/DistributionValidations.phpnu[ 1.0) { throw new Exception(ExcelError::NAN()); } return $probability; } } PK!XPaQlibraries/vendor/PhpSpreadsheet/Calculation/Statistical/Distributions/Weibull.phpnu[getMessage(); } if (($value < 0) || ($alpha <= 0) || ($beta <= 0)) { return ExcelError::NAN(); } if ($cumulative) { return 1 - exp(0 - ($value / $beta) ** $alpha); } return ($alpha / $beta ** $alpha) * $value ** ($alpha - 1) * exp(0 - ($value / $beta) ** $alpha); } } PK!s Xlibraries/vendor/PhpSpreadsheet/Calculation/Statistical/Distributions/HyperGeometric.phpnu[getMessage(); } if (($sampleSuccesses < 0) || ($sampleSuccesses > $sampleNumber) || ($sampleSuccesses > $populationSuccesses)) { return ExcelError::NAN(); } if (($sampleNumber <= 0) || ($sampleNumber > $populationNumber)) { return ExcelError::NAN(); } if (($populationSuccesses <= 0) || ($populationSuccesses > $populationNumber)) { return ExcelError::NAN(); } $successesPopulationAndSample = (float) Combinations::withoutRepetition($populationSuccesses, $sampleSuccesses); $numbersPopulationAndSample = (float) Combinations::withoutRepetition($populationNumber, $sampleNumber); $adjustedPopulationAndSample = (float) Combinations::withoutRepetition( $populationNumber - $populationSuccesses, $sampleNumber - $sampleSuccesses ); return $successesPopulationAndSample * $adjustedPopulationAndSample / $numbersPopulationAndSample; } } PK!@Slibraries/vendor/PhpSpreadsheet/Calculation/Statistical/Distributions/LogNormal.phpnu[getMessage(); } if (($value <= 0) || ($stdDev <= 0)) { return ExcelError::NAN(); } return StandardNormal::cumulative((log($value) - $mean) / $stdDev); } /** * LOGNORM.DIST. * * Returns the lognormal distribution of x, where ln(x) is normally distributed * with parameters mean and standard_dev. * * @param mixed $value Float value for which we want the probability * Or can be an array of values * @param mixed $mean Mean value as a float * Or can be an array of values * @param mixed $stdDev Standard Deviation as a float * Or can be an array of values * @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false) * Or can be an array of values * * @return array|float|string The result, or a string containing an error * If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function distribution($value, $mean, $stdDev, $cumulative = false) { if (is_array($value) || is_array($mean) || is_array($stdDev) || is_array($cumulative)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $mean, $stdDev, $cumulative); } try { $value = DistributionValidations::validateFloat($value); $mean = DistributionValidations::validateFloat($mean); $stdDev = DistributionValidations::validateFloat($stdDev); $cumulative = DistributionValidations::validateBool($cumulative); } catch (Exception $e) { return $e->getMessage(); } if (($value <= 0) || ($stdDev <= 0)) { return ExcelError::NAN(); } if ($cumulative === true) { return StandardNormal::distribution((log($value) - $mean) / $stdDev, true); } return (1 / (sqrt(2 * M_PI) * $stdDev * $value)) * exp(0 - ((log($value) - $mean) ** 2 / (2 * $stdDev ** 2))); } /** * LOGINV. * * Returns the inverse of the lognormal cumulative distribution * * @param mixed $probability Float probability for which we want the value * Or can be an array of values * @param mixed $mean Mean Value as a float * Or can be an array of values * @param mixed $stdDev Standard Deviation as a float * Or can be an array of values * * @return array|float|string The result, or a string containing an error * If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions * * @TODO Try implementing P J Acklam's refinement algorithm for greater * accuracy if I can get my head round the mathematics * (as described at) http://home.online.no/~pjacklam/notes/invnorm/ */ public static function inverse($probability, $mean, $stdDev) { if (is_array($probability) || is_array($mean) || is_array($stdDev)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $probability, $mean, $stdDev); } try { $probability = DistributionValidations::validateProbability($probability); $mean = DistributionValidations::validateFloat($mean); $stdDev = DistributionValidations::validateFloat($stdDev); } catch (Exception $e) { return $e->getMessage(); } if ($stdDev <= 0) { return ExcelError::NAN(); } /** @var float $inverse */ $inverse = StandardNormal::inverse($probability); return exp($mean + $stdDev * $inverse); } } PK!<ˮkkOlibraries/vendor/PhpSpreadsheet/Calculation/Statistical/Distributions/Gamma.phpnu[getMessage(); } if ((((int) $value) == ((float) $value)) && $value <= 0.0) { return ExcelError::NAN(); } return self::gammaValue($value); } /** * GAMMADIST. * * Returns the gamma distribution. * * @param mixed $value Float Value at which you want to evaluate the distribution * Or can be an array of values * @param mixed $a Parameter to the distribution as a float * Or can be an array of values * @param mixed $b Parameter to the distribution as a float * Or can be an array of values * @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false) * Or can be an array of values * * @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function distribution($value, $a, $b, $cumulative) { if (is_array($value) || is_array($a) || is_array($b) || is_array($cumulative)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $a, $b, $cumulative); } try { $value = DistributionValidations::validateFloat($value); $a = DistributionValidations::validateFloat($a); $b = DistributionValidations::validateFloat($b); $cumulative = DistributionValidations::validateBool($cumulative); } catch (Exception $e) { return $e->getMessage(); } if (($value < 0) || ($a <= 0) || ($b <= 0)) { return ExcelError::NAN(); } return self::calculateDistribution($value, $a, $b, $cumulative); } /** * GAMMAINV. * * Returns the inverse of the Gamma distribution. * * @param mixed $probability Float probability at which you want to evaluate the distribution * Or can be an array of values * @param mixed $alpha Parameter to the distribution as a float * Or can be an array of values * @param mixed $beta Parameter to the distribution as a float * Or can be an array of values * * @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function inverse($probability, $alpha, $beta) { if (is_array($probability) || is_array($alpha) || is_array($beta)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $probability, $alpha, $beta); } try { $probability = DistributionValidations::validateProbability($probability); $alpha = DistributionValidations::validateFloat($alpha); $beta = DistributionValidations::validateFloat($beta); } catch (Exception $e) { return $e->getMessage(); } if (($alpha <= 0.0) || ($beta <= 0.0)) { return ExcelError::NAN(); } return self::calculateInverse($probability, $alpha, $beta); } /** * GAMMALN. * * Returns the natural logarithm of the gamma function. * * @param mixed $value Float Value at which you want to evaluate the distribution * Or can be an array of values * * @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function ln($value) { if (is_array($value)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value); } try { $value = DistributionValidations::validateFloat($value); } catch (Exception $e) { return $e->getMessage(); } if ($value <= 0) { return ExcelError::NAN(); } return log(self::gammaValue($value)); } } PK!?Plibraries/vendor/PhpSpreadsheet/Calculation/Statistical/Distributions/Fisher.phpnu[getMessage(); } if (($value <= -1) || ($value >= 1)) { return ExcelError::NAN(); } return 0.5 * log((1 + $value) / (1 - $value)); } /** * FISHERINV. * * Returns the inverse of the Fisher transformation. Use this transformation when * analyzing correlations between ranges or arrays of data. If y = FISHER(x), then * FISHERINV(y) = x. * * @param mixed $probability Float probability at which you want to evaluate the distribution * Or can be an array of values * * @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function inverse($probability) { if (is_array($probability)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $probability); } try { $probability = DistributionValidations::validateFloat($probability); } catch (Exception $e) { return $e->getMessage(); } return (exp(2 * $probability) - 1) / (exp(2 * $probability) + 1); } } PK!0V X X Klibraries/vendor/PhpSpreadsheet/Calculation/Statistical/Distributions/F.phpnu[getMessage(); } if ($value < 0 || $u < 1 || $v < 1) { return ExcelError::NAN(); } if ($cumulative) { $adjustedValue = ($u * $value) / ($u * $value + $v); return Beta::incompleteBeta($adjustedValue, $u / 2, $v / 2); } return (Gamma::gammaValue(($v + $u) / 2) / (Gamma::gammaValue($u / 2) * Gamma::gammaValue($v / 2))) * (($u / $v) ** ($u / 2)) * (($value ** (($u - 2) / 2)) / ((1 + ($u / $v) * $value) ** (($u + $v) / 2))); } } PK! Flibraries/vendor/PhpSpreadsheet/Calculation/Statistical/Deviations.phpnu[ $arg) { // Is it a numeric value? if ( (is_bool($arg)) && ((!Functions::isCellValue($k)) || (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE)) ) { $arg = (int) $arg; } if ((is_numeric($arg)) && (!is_string($arg))) { $returnValue += ($arg - $aMean) ** 2; ++$aCount; } } return $aCount === 0 ? ExcelError::VALUE() : $returnValue; } /** * KURT. * * Returns the kurtosis of a data set. Kurtosis characterizes the relative peakedness * or flatness of a distribution compared with the normal distribution. Positive * kurtosis indicates a relatively peaked distribution. Negative kurtosis indicates a * relatively flat distribution. * * @param array ...$args Data Series * @return float|int|string */ public static function kurtosis(...$args) { $aArgs = Functions::flattenArrayIndexed($args); $mean = Averages::average($aArgs); if (!is_numeric($mean)) { return ExcelError::DIV0(); } $stdDev = (float) StandardDeviations::STDEV($aArgs); if ($stdDev > 0) { $count = $summer = 0; foreach ($aArgs as $k => $arg) { if ((is_bool($arg)) && (!Functions::isMatrixValue($k))) { } else { // Is it a numeric value? if ((is_numeric($arg)) && (!is_string($arg))) { $summer += (($arg - $mean) / $stdDev) ** 4; ++$count; } } } if ($count > 3) { return $summer * ($count * ($count + 1) / (($count - 1) * ($count - 2) * ($count - 3))) - (3 * ($count - 1) ** 2 / (($count - 2) * ($count - 3))); } } return ExcelError::DIV0(); } /** * SKEW. * * Returns the skewness of a distribution. Skewness characterizes the degree of asymmetry * of a distribution around its mean. Positive skewness indicates a distribution with an * asymmetric tail extending toward more positive values. Negative skewness indicates a * distribution with an asymmetric tail extending toward more negative values. * * @param array ...$args Data Series * * @return float|int|string The result, or a string containing an error */ public static function skew(...$args) { $aArgs = Functions::flattenArrayIndexed($args); $mean = Averages::average($aArgs); if (!is_numeric($mean)) { return ExcelError::DIV0(); } $stdDev = StandardDeviations::STDEV($aArgs); if ($stdDev === 0.0 || is_string($stdDev)) { return ExcelError::DIV0(); } $count = $summer = 0; // Loop through arguments foreach ($aArgs as $k => $arg) { if ((is_bool($arg)) && (!Functions::isMatrixValue($k))) { } elseif (!is_numeric($arg)) { return ExcelError::VALUE(); } else { // Is it a numeric value? if (!is_string($arg)) { $summer += (($arg - $mean) / $stdDev) ** 3; ++$count; } } } if ($count > 2) { return $summer * ($count / (($count - 1) * ($count - 2))); } return ExcelError::DIV0(); } } PK!-Dlibraries/vendor/PhpSpreadsheet/Calculation/Statistical/Averages.phpnu[ $arg) { $arg = self::testAcceptedBoolean($arg, $k); // Is it a numeric value? // Strings containing numeric values are only counted if they are string literals (not cell values) // and then only in MS Excel and in Open Office, not in Gnumeric if ((is_string($arg)) && (!is_numeric($arg)) && (!Functions::isCellValue($k))) { return ExcelError::VALUE(); } if (self::isAcceptedCountable($arg, $k)) { /** @var float|int|numeric-string $arg */ /** @var float|int|numeric-string $aMean */ $returnValue += abs($arg - $aMean); ++$aCount; } } // Return if ($aCount === 0) { return ExcelError::DIV0(); } return $returnValue / $aCount; } /** * AVERAGE. * * Returns the average (arithmetic mean) of the arguments * * Excel Function: * AVERAGE(value1[,value2[, ...]]) * * @param mixed ...$args Data values * * @return float|int|string (string if result is an error) */ public static function average(...$args) { $returnValue = $aCount = 0; // Loop through arguments foreach (Functions::flattenArrayIndexed($args) as $k => $arg) { $arg = self::testAcceptedBoolean($arg, $k); // Is it a numeric value? // Strings containing numeric values are only counted if they are string literals (not cell values) // and then only in MS Excel and in Open Office, not in Gnumeric if ((is_string($arg)) && (!is_numeric($arg)) && (!Functions::isCellValue($k))) { return ExcelError::VALUE(); } if (self::isAcceptedCountable($arg, $k)) { /** @var float|int|numeric-string $arg */ $returnValue += $arg; ++$aCount; } } // Return if ($aCount > 0) { return $returnValue / $aCount; } return ExcelError::DIV0(); } /** * AVERAGEA. * * Returns the average of its arguments, including numbers, text, and logical values * * Excel Function: * AVERAGEA(value1[,value2[, ...]]) * * @param mixed ...$args Data values * * @return float|int|string (string if result is an error) */ public static function averageA(...$args) { $returnValue = null; $aCount = 0; // Loop through arguments foreach (Functions::flattenArrayIndexed($args) as $k => $arg) { if (is_numeric($arg)) { // do nothing } elseif (is_bool($arg)) { $arg = (int) $arg; } elseif (!Functions::isMatrixValue($k)) { $arg = 0; } else { return ExcelError::VALUE(); } $returnValue += $arg; ++$aCount; } if ($aCount > 0) { return $returnValue / $aCount; } return ExcelError::DIV0(); } /** * MEDIAN. * * Returns the median of the given numbers. The median is the number in the middle of a set of numbers. * * Excel Function: * MEDIAN(value1[,value2[, ...]]) * * @param mixed ...$args Data values * * @return float|string The result, or a string containing an error */ public static function median(...$args) { $aArgs = Functions::flattenArray($args); $returnValue = ExcelError::NAN(); $aArgs = self::filterArguments($aArgs); $valueCount = count($aArgs); if ($valueCount > 0) { sort($aArgs, SORT_NUMERIC); $valueCount = $valueCount / 2; if ($valueCount == floor($valueCount)) { $returnValue = ($aArgs[$valueCount--] + $aArgs[$valueCount]) / 2; } else { $valueCount = (int) floor($valueCount); $returnValue = $aArgs[$valueCount]; } } return $returnValue; } /** * MODE. * * Returns the most frequently occurring, or repetitive, value in an array or range of data * * Excel Function: * MODE(value1[,value2[, ...]]) * * @param mixed ...$args Data values * * @return float|string The result, or a string containing an error */ public static function mode(...$args) { $returnValue = ExcelError::NA(); // Loop through arguments $aArgs = Functions::flattenArray($args); $aArgs = self::filterArguments($aArgs); if (!empty($aArgs)) { return self::modeCalc($aArgs); } return $returnValue; } protected static function filterArguments(array $args): array { return array_filter( $args, function ($value): bool { // Is it a numeric value? return is_numeric($value) && (!is_string($value)); } ); } /** * Special variant of array_count_values that isn't limited to strings and integers, * but can work with floating point numbers as values. * @return float|string */ private static function modeCalc(array $data) { $frequencyArray = []; $index = 0; $maxfreq = 0; $maxfreqkey = ''; $maxfreqdatum = ''; foreach ($data as $datum) { $found = false; ++$index; foreach ($frequencyArray as $key => $value) { if ((string) $value['value'] == (string) $datum) { ++$frequencyArray[$key]['frequency']; $freq = $frequencyArray[$key]['frequency']; if ($freq > $maxfreq) { $maxfreq = $freq; $maxfreqkey = $key; $maxfreqdatum = $datum; } elseif ($freq == $maxfreq) { if ($frequencyArray[$key]['index'] < $frequencyArray[$maxfreqkey]['index']) { $maxfreqkey = $key; $maxfreqdatum = $datum; } } $found = true; break; } } if ($found === false) { $frequencyArray[] = [ 'value' => $datum, 'frequency' => 1, 'index' => $index, ]; } } if ($maxfreq <= 1) { return ExcelError::NA(); } return $maxfreqdatum; } } PK!$FʛHHElibraries/vendor/PhpSpreadsheet/Calculation/Statistical/Variances.phpnu[ 1) { $summerA *= $aCount; $summerB *= $summerB; return ($summerA - $summerB) / ($aCount * ($aCount - 1)); } return $returnValue; } /** * VARA. * * Estimates variance based on a sample, including numbers, text, and logical values * * Excel Function: * VARA(value1[,value2[, ...]]) * * @param mixed ...$args Data values * * @return float|string (string if result is an error) */ public static function VARA(...$args) { $returnValue = ExcelError::DIV0(); $summerA = $summerB = 0.0; // Loop through arguments $aArgs = Functions::flattenArrayIndexed($args); $aCount = 0; foreach ($aArgs as $k => $arg) { if ((is_string($arg)) && (Functions::isValue($k))) { return ExcelError::VALUE(); } elseif ((is_string($arg)) && (!Functions::isMatrixValue($k))) { } else { // Is it a numeric value? if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) { $arg = self::datatypeAdjustmentAllowStrings($arg); $summerA += ($arg * $arg); $summerB += $arg; ++$aCount; } } } if ($aCount > 1) { $summerA *= $aCount; $summerB *= $summerB; return ($summerA - $summerB) / ($aCount * ($aCount - 1)); } return $returnValue; } /** * VARP. * * Calculates variance based on the entire population * * Excel Function: * VARP(value1[,value2[, ...]]) * * @param mixed ...$args Data values * * @return float|string (string if result is an error) */ public static function VARP(...$args) { // Return value $returnValue = ExcelError::DIV0(); $summerA = $summerB = 0.0; // Loop through arguments $aArgs = Functions::flattenArray($args); $aCount = 0; foreach ($aArgs as $arg) { $arg = self::datatypeAdjustmentBooleans($arg); // Is it a numeric value? if ((is_numeric($arg)) && (!is_string($arg))) { $summerA += ($arg * $arg); $summerB += $arg; ++$aCount; } } if ($aCount > 0) { $summerA *= $aCount; $summerB *= $summerB; return ($summerA - $summerB) / ($aCount * $aCount); } return $returnValue; } /** * VARPA. * * Calculates variance based on the entire population, including numbers, text, and logical values * * Excel Function: * VARPA(value1[,value2[, ...]]) * * @param mixed ...$args Data values * * @return float|string (string if result is an error) */ public static function VARPA(...$args) { $returnValue = ExcelError::DIV0(); $summerA = $summerB = 0.0; // Loop through arguments $aArgs = Functions::flattenArrayIndexed($args); $aCount = 0; foreach ($aArgs as $k => $arg) { if ((is_string($arg)) && (Functions::isValue($k))) { return ExcelError::VALUE(); } elseif ((is_string($arg)) && (!Functions::isMatrixValue($k))) { } else { // Is it a numeric value? if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) { $arg = self::datatypeAdjustmentAllowStrings($arg); $summerA += ($arg * $arg); $summerB += $arg; ++$aCount; } } } if ($aCount > 0) { $summerA *= $aCount; $summerB *= $summerB; return ($summerA - $summerB) / ($aCount * $aCount); } return $returnValue; } } PK!<URlibraries/vendor/PhpSpreadsheet/Calculation/Statistical/StatisticalValidations.phpnu[ $value !== null && $value !== '' ); $range = array_merge([[self::CONDITION_COLUMN_NAME]], array_chunk($range, 1)); $condition = array_merge([[self::CONDITION_COLUMN_NAME]], [[$condition]]); return DCount::evaluate($range, null, $condition, false); } /** * COUNTIFS. * * Counts the number of cells that contain numbers within the list of arguments * * Excel Function: * COUNTIFS(criteria_range1, criteria1, [criteria_range2, criteria2]…) * * @param mixed $args Pairs of Ranges and Criteria * @return int|string */ public static function COUNTIFS(...$args) { if (empty($args)) { return 0; } elseif (count($args) === 2) { return self::COUNTIF(...$args); } $database = self::buildDatabase(...$args); $conditions = self::buildConditionSet(...$args); return DCount::evaluate($database, null, $conditions, false); } /** * MAXIFS. * * Returns the maximum value within a range of cells that contain numbers within the list of arguments * * Excel Function: * MAXIFS(max_range, criteria_range1, criteria1, [criteria_range2, criteria2]…) * * @param mixed $args Pairs of Ranges and Criteria * @return float|string|null */ public static function MAXIFS(...$args) { if (empty($args)) { return 0.0; } $conditions = self::buildConditionSetForValueRange(...$args); $database = self::buildDatabaseWithValueRange(...$args); return DMax::evaluate($database, self::VALUE_COLUMN_NAME, $conditions, false); } /** * MINIFS. * * Returns the minimum value within a range of cells that contain numbers within the list of arguments * * Excel Function: * MINIFS(min_range, criteria_range1, criteria1, [criteria_range2, criteria2]…) * * @param mixed $args Pairs of Ranges and Criteria * @return float|string|null */ public static function MINIFS(...$args) { if (empty($args)) { return 0.0; } $conditions = self::buildConditionSetForValueRange(...$args); $database = self::buildDatabaseWithValueRange(...$args); return DMin::evaluate($database, self::VALUE_COLUMN_NAME, $conditions, false); } /** * SUMIF. * * Totals the values of cells that contain numbers within the list of arguments * * Excel Function: * SUMIF(range, criteria, [sum_range]) * * @param mixed $range Data values, expecting array * @param mixed $sumRange Data values, expecting array * @return float|string|null * @param mixed $condition */ public static function SUMIF($range, $condition, $sumRange = []) { if ( !is_array($range) || array_key_exists(0, $range) || !is_array($sumRange) || array_key_exists(0, $sumRange) ) { $refError = ExcelError::REF(); if (in_array($refError, [$range, $sumRange], true)) { return $refError; } throw new CalcException('Must specify range of cells, not any kind of literal'); } $database = self::databaseFromRangeAndValue($range, $sumRange); $condition = [[self::CONDITION_COLUMN_NAME, self::VALUE_COLUMN_NAME], [$condition, null]]; return DSum::evaluate($database, self::VALUE_COLUMN_NAME, $condition); } /** * SUMIFS. * * Counts the number of cells that contain numbers within the list of arguments * * Excel Function: * SUMIFS(average_range, criteria_range1, criteria1, [criteria_range2, criteria2]…) * * @param mixed $args Pairs of Ranges and Criteria * @return float|string|null */ public static function SUMIFS(...$args) { if (empty($args)) { return 0.0; } elseif (count($args) === 3) { return self::SUMIF($args[1], $args[2], $args[0]); } $conditions = self::buildConditionSetForValueRange(...$args); $database = self::buildDatabaseWithValueRange(...$args); return DSum::evaluate($database, self::VALUE_COLUMN_NAME, $conditions); } /** @param array $args */ private static function buildConditionSet(...$args): array { $conditions = self::buildConditions(1, ...$args); return array_map(null, ...$conditions); } /** @param array $args */ private static function buildConditionSetForValueRange(...$args): array { $conditions = self::buildConditions(2, ...$args); if (count($conditions) === 1) { return array_map( fn ($value): array => [$value], $conditions[0] ); } return array_map(null, ...$conditions); } /** @param array $args */ private static function buildConditions(int $startOffset, ...$args): array { $conditions = []; $pairCount = 1; $argumentCount = count($args); for ($argument = $startOffset; $argument < $argumentCount; $argument += 2) { $conditions[] = array_merge([sprintf(self::CONDITIONAL_COLUMN_NAME, $pairCount)], [$args[$argument]]); ++$pairCount; } return $conditions; } /** @param array $args */ private static function buildDatabase(...$args): array { $database = []; return self::buildDataSet(0, $database, ...$args); } /** @param array $args */ private static function buildDatabaseWithValueRange(...$args): array { $database = []; $database[] = array_merge( [self::VALUE_COLUMN_NAME], Functions::flattenArray($args[0]) ); return self::buildDataSet(1, $database, ...$args); } /** @param array $args */ private static function buildDataSet(int $startOffset, array $database, ...$args): array { $pairCount = 1; $argumentCount = count($args); for ($argument = $startOffset; $argument < $argumentCount; $argument += 2) { $database[] = array_merge( [sprintf(self::CONDITIONAL_COLUMN_NAME, $pairCount)], Functions::flattenArray($args[$argument]) ); ++$pairCount; } return array_map(null, ...$database); } private static function databaseFromRangeAndValue(array $range, array $valueRange = []): array { $range = Functions::flattenArray($range); $valueRange = Functions::flattenArray($valueRange); if (empty($valueRange)) { $valueRange = $range; } $database = array_map(null, array_merge([self::CONDITION_COLUMN_NAME], $range), array_merge([self::VALUE_COLUMN_NAME], $valueRange)); return $database; } } PK!#~s11Blibraries/vendor/PhpSpreadsheet/Calculation/Statistical/Trends.phpnu[ $value) { if ((is_bool($value)) || (is_string($value)) || ($value === null)) { unset($array1[$key], $array2[$key]); } } } /** * @param mixed $array1 should be array, but scalar is made into one * @param mixed $array2 should be array, but scalar is made into one * * @param-out array $array1 * @param-out array $array2 */ private static function checkTrendArrays(&$array1, &$array2): void { if (!is_array($array1)) { $array1 = [$array1]; } if (!is_array($array2)) { $array2 = [$array2]; } $array1 = Functions::flattenArray($array1); $array2 = Functions::flattenArray($array2); self::filterTrendValues($array1, $array2); self::filterTrendValues($array2, $array1); // Reset the array indexes $array1 = array_merge($array1); $array2 = array_merge($array2); } protected static function validateTrendArrays(array $yValues, array $xValues): void { $yValueCount = count($yValues); $xValueCount = count($xValues); if (($yValueCount === 0) || ($yValueCount !== $xValueCount)) { throw new Exception(ExcelError::NA()); } elseif ($yValueCount === 1) { throw new Exception(ExcelError::DIV0()); } } /** * CORREL. * * Returns covariance, the average of the products of deviations for each data point pair. * * @param mixed $yValues array of mixed Data Series Y * @param null|mixed $xValues array of mixed Data Series X * @return float|string */ public static function CORREL($yValues, $xValues = null) { if (($xValues === null) || (!is_array($yValues)) || (!is_array($xValues))) { return ExcelError::VALUE(); } try { self::checkTrendArrays($yValues, $xValues); self::validateTrendArrays($yValues, $xValues); } catch (Exception $e) { return $e->getMessage(); } $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); return $bestFitLinear->getCorrelation(); } /** * COVAR. * * Returns covariance, the average of the products of deviations for each data point pair. * * @param mixed[] $yValues array of mixed Data Series Y * @param mixed[] $xValues array of mixed Data Series X * @return float|string */ public static function COVAR(array $yValues, array $xValues) { try { self::checkTrendArrays($yValues, $xValues); self::validateTrendArrays($yValues, $xValues); } catch (Exception $e) { return $e->getMessage(); } $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); return $bestFitLinear->getCovariance(); } /** * FORECAST. * * Calculates, or predicts, a future value by using existing values. * The predicted value is a y-value for a given x-value. * * @param mixed $xValue Float value of X for which we want to find Y * Or can be an array of values * @param mixed[] $yValues array of mixed Data Series Y * @param mixed[] $xValues array of mixed Data Series X * * @return array|bool|float|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function FORECAST($xValue, array $yValues, array $xValues) { if (is_array($xValue)) { return self::evaluateArrayArgumentsSubset([self::class, __FUNCTION__], 1, $xValue, $yValues, $xValues); } try { $xValue = StatisticalValidations::validateFloat($xValue); self::checkTrendArrays($yValues, $xValues); self::validateTrendArrays($yValues, $xValues); } catch (Exception $e) { return $e->getMessage(); } $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); return $bestFitLinear->getValueOfYForX($xValue); } /** * GROWTH. * * Returns values along a predicted exponential Trend * * @param mixed[] $yValues Data Series Y * @param mixed[] $xValues Data Series X * @param mixed[] $newValues Values of X for which we want to find Y * @param mixed $const A logical (boolean) value specifying whether to force the intersect to equal 0 or not * * @return array>> */ public static function GROWTH(array $yValues, array $xValues = [], array $newValues = [], $const = true): array { $yValues = Functions::flattenArray($yValues); $xValues = Functions::flattenArray($xValues); $newValues = Functions::flattenArray($newValues); $const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const); $bestFitExponential = Trend::calculate(Trend::TREND_EXPONENTIAL, $yValues, $xValues, $const); if (empty($newValues)) { $newValues = $bestFitExponential->getXValues(); } $returnArray = []; foreach ($newValues as $xValue) { $returnArray[0][] = [$bestFitExponential->getValueOfYForX($xValue)]; } return $returnArray; } /** * INTERCEPT. * * Calculates the point at which a line will intersect the y-axis by using existing x-values and y-values. * * @param mixed[] $yValues Data Series Y * @param mixed[] $xValues Data Series X * @return float|string */ public static function INTERCEPT(array $yValues, array $xValues) { try { self::checkTrendArrays($yValues, $xValues); self::validateTrendArrays($yValues, $xValues); } catch (Exception $e) { return $e->getMessage(); } $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); return $bestFitLinear->getIntersect(); } /** * LINEST. * * Calculates the statistics for a line by using the "least squares" method to calculate a straight line * that best fits your data, and then returns an array that describes the line. * * @param mixed[] $yValues Data Series Y * @param null|mixed[] $xValues Data Series X * @param mixed $const A logical (boolean) value specifying whether to force the intersect to equal 0 or not * @param mixed $stats A logical (boolean) value specifying whether to return additional regression statistics * * @return array|string The result, or a string containing an error */ public static function LINEST(array $yValues, ?array $xValues = null, $const = true, $stats = false) { $const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const); $stats = ($stats === null) ? false : (bool) Functions::flattenSingleValue($stats); if ($xValues === null) { $xValues = $yValues; } try { self::checkTrendArrays($yValues, $xValues); self::validateTrendArrays($yValues, $xValues); } catch (Exception $e) { return $e->getMessage(); } $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues, $const); if ($stats === true) { return [ [ $bestFitLinear->getSlope(), $bestFitLinear->getIntersect(), ], [ $bestFitLinear->getSlopeSE(), ($const === false) ? ExcelError::NA() : $bestFitLinear->getIntersectSE(), ], [ $bestFitLinear->getGoodnessOfFit(), $bestFitLinear->getStdevOfResiduals(), ], [ $bestFitLinear->getF(), $bestFitLinear->getDFResiduals(), ], [ $bestFitLinear->getSSRegression(), $bestFitLinear->getSSResiduals(), ], ]; } return [ $bestFitLinear->getSlope(), $bestFitLinear->getIntersect(), ]; } /** * LOGEST. * * Calculates an exponential curve that best fits the X and Y data series, * and then returns an array that describes the line. * * @param mixed[] $yValues Data Series Y * @param null|mixed[] $xValues Data Series X * @param mixed $const A logical (boolean) value specifying whether to force the intersect to equal 0 or not * @param mixed $stats A logical (boolean) value specifying whether to return additional regression statistics * * @return array|string The result, or a string containing an error */ public static function LOGEST(array $yValues, ?array $xValues = null, $const = true, $stats = false) { $const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const); $stats = ($stats === null) ? false : (bool) Functions::flattenSingleValue($stats); if ($xValues === null) { $xValues = $yValues; } try { self::checkTrendArrays($yValues, $xValues); self::validateTrendArrays($yValues, $xValues); } catch (Exception $e) { return $e->getMessage(); } foreach ($yValues as $value) { if ($value < 0.0) { return ExcelError::NAN(); } } $bestFitExponential = Trend::calculate(Trend::TREND_EXPONENTIAL, $yValues, $xValues, $const); if ($stats === true) { return [ [ $bestFitExponential->getSlope(), $bestFitExponential->getIntersect(), ], [ $bestFitExponential->getSlopeSE(), ($const === false) ? ExcelError::NA() : $bestFitExponential->getIntersectSE(), ], [ $bestFitExponential->getGoodnessOfFit(), $bestFitExponential->getStdevOfResiduals(), ], [ $bestFitExponential->getF(), $bestFitExponential->getDFResiduals(), ], [ $bestFitExponential->getSSRegression(), $bestFitExponential->getSSResiduals(), ], ]; } return [ $bestFitExponential->getSlope(), $bestFitExponential->getIntersect(), ]; } /** * RSQ. * * Returns the square of the Pearson product moment correlation coefficient through data points * in known_y's and known_x's. * * @param mixed[] $yValues Data Series Y * @param mixed[] $xValues Data Series X * * @return float|string The result, or a string containing an error */ public static function RSQ(array $yValues, array $xValues) { try { self::checkTrendArrays($yValues, $xValues); self::validateTrendArrays($yValues, $xValues); } catch (Exception $e) { return $e->getMessage(); } $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); return $bestFitLinear->getGoodnessOfFit(); } /** * SLOPE. * * Returns the slope of the linear regression line through data points in known_y's and known_x's. * * @param mixed[] $yValues Data Series Y * @param mixed[] $xValues Data Series X * * @return float|string The result, or a string containing an error */ public static function SLOPE(array $yValues, array $xValues) { try { self::checkTrendArrays($yValues, $xValues); self::validateTrendArrays($yValues, $xValues); } catch (Exception $e) { return $e->getMessage(); } $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); return $bestFitLinear->getSlope(); } /** * STEYX. * * Returns the standard error of the predicted y-value for each x in the regression. * * @param mixed[] $yValues Data Series Y * @param mixed[] $xValues Data Series X * @return float|string */ public static function STEYX(array $yValues, array $xValues) { try { self::checkTrendArrays($yValues, $xValues); self::validateTrendArrays($yValues, $xValues); } catch (Exception $e) { return $e->getMessage(); } $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); return $bestFitLinear->getStdevOfResiduals(); } /** * TREND. * * Returns values along a linear Trend * * @param mixed[] $yValues Data Series Y * @param mixed[] $xValues Data Series X * @param mixed[] $newValues Values of X for which we want to find Y * @param mixed $const A logical (boolean) value specifying whether to force the intersect to equal 0 or not * * @return array>> */ public static function TREND(array $yValues, array $xValues = [], array $newValues = [], $const = true): array { $yValues = Functions::flattenArray($yValues); $xValues = Functions::flattenArray($xValues); $newValues = Functions::flattenArray($newValues); $const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const); $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues, $const); if (empty($newValues)) { $newValues = $bestFitLinear->getXValues(); } $returnArray = []; foreach ($newValues as $xValue) { $returnArray[0][] = [$bestFitLinear->getValueOfYForX($xValue)]; } return $returnArray; } } PK!Alibraries/vendor/PhpSpreadsheet/Calculation/Statistical/index.phpnu[ $arg) { $arg = self::testAcceptedBoolean($arg, $k); // Is it a numeric value? // Strings containing numeric values are only counted if they are string literals (not cell values) // and then only in MS Excel and in Open Office, not in Gnumeric if (self::isAcceptedCountable($arg, $k, true)) { ++$returnValue; } } return $returnValue; } /** * COUNTA. * * Counts the number of cells that are not empty within the list of arguments * * Excel Function: * COUNTA(value1[,value2[, ...]]) * * @param mixed ...$args Data values */ public static function COUNTA(...$args): int { $returnValue = 0; // Loop through arguments $aArgs = Functions::flattenArrayIndexed($args); foreach ($aArgs as $k => $arg) { // Nulls are counted if literals, but not if cell values if ($arg !== null || (!Functions::isCellValue($k))) { ++$returnValue; } } return $returnValue; } /** * COUNTBLANK. * * Counts the number of empty cells within the list of arguments * * Excel Function: * COUNTBLANK(value1[,value2[, ...]]) * * @param mixed $range Data values */ public static function COUNTBLANK($range): int { if ($range === null) { return 1; } if (!is_array($range) || array_key_exists(0, $range)) { throw new CalcException('Must specify range of cells, not any kind of literal'); } $returnValue = 0; // Loop through arguments $aArgs = Functions::flattenArray($range); foreach ($aArgs as $arg) { // Is it a blank cell? if (($arg === null) || ((is_string($arg)) && ($arg == ''))) { ++$returnValue; } } return $returnValue; } } PK!Jlibraries/vendor/PhpSpreadsheet/Calculation/Statistical/Averages/index.phpnu[ 0)) { $aCount = Counts::COUNT($aArgs); if (Minimum::min($aArgs) > 0) { return $aMean ** (1 / $aCount); } } return ExcelError::NAN(); } /** * HARMEAN. * * Returns the harmonic mean of a data set. The harmonic mean is the reciprocal of the * arithmetic mean of reciprocals. * * Excel Function: * HARMEAN(value1[,value2[, ...]]) * * @param mixed ...$args Data values * @return float|int|string */ public static function harmonic(...$args) { // Loop through arguments $aArgs = Functions::flattenArray($args); if (Minimum::min($aArgs) < 0) { return ExcelError::NAN(); } $returnValue = 0; $aCount = 0; foreach ($aArgs as $arg) { // Is it a numeric value? if ((is_numeric($arg)) && (!is_string($arg))) { if ($arg <= 0) { return ExcelError::NAN(); } $returnValue += (1 / $arg); ++$aCount; } } // Return if ($aCount > 0) { return 1 / ($returnValue / $aCount); } return ExcelError::NA(); } /** * TRIMMEAN. * * Returns the mean of the interior of a data set. TRIMMEAN calculates the mean * taken by excluding a percentage of data points from the top and bottom tails * of a data set. * * Excel Function: * TRIMEAN(value1[,value2[, ...]], $discard) * * @param mixed $args Data values * @return float|string */ public static function trim(...$args) { $aArgs = Functions::flattenArray($args); // Calculate $percent = array_pop($aArgs); if ((is_numeric($percent)) && (!is_string($percent))) { if (($percent < 0) || ($percent > 1)) { return ExcelError::NAN(); } $mArgs = []; foreach ($aArgs as $arg) { // Is it a numeric value? if ((is_numeric($arg)) && (!is_string($arg))) { $mArgs[] = $arg; } } $discard = floor(Counts::COUNT($mArgs) * $percent / 2); sort($mArgs); for ($i = 0; $i < $discard; ++$i) { array_pop($mArgs); array_shift($mArgs); } return Averages::average($mArgs); } return ExcelError::VALUE(); } } PK!=\Glibraries/vendor/PhpSpreadsheet/Calculation/Statistical/Standardize.phpnu[getMessage(); } if ($stdDev <= 0) { return ExcelError::NAN(); } return ($value - $mean) / $stdDev; } } PK!|K K Hlibraries/vendor/PhpSpreadsheet/Calculation/Statistical/Permutations.phpnu[getMessage(); } if ($numObjs < $numInSet) { return ExcelError::NAN(); } /** @var float|int|string */ $result1 = MathTrig\Factorial::fact($numObjs); if (is_string($result1)) { return $result1; } /** @var float|int|string */ $result2 = MathTrig\Factorial::fact($numObjs - $numInSet); if (is_string($result2)) { return $result2; } $result = round($result1 / $result2); return IntOrFloat::evaluate($result); } /** * PERMUTATIONA. * * Returns the number of permutations for a given number of objects (with repetitions) * that can be selected from the total objects. * * @param mixed $numObjs Integer number of different objects * Or can be an array of values * @param mixed $numInSet Integer number of objects in each permutation * Or can be an array of values * * @return array|float|int|string Number of permutations, or a string containing an error * If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function PERMUTATIONA($numObjs, $numInSet) { if (is_array($numObjs) || is_array($numInSet)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $numObjs, $numInSet); } try { $numObjs = StatisticalValidations::validateInt($numObjs); $numInSet = StatisticalValidations::validateInt($numInSet); } catch (Exception $e) { return $e->getMessage(); } if ($numObjs < 0 || $numInSet < 0) { return ExcelError::NAN(); } $result = $numObjs ** $numInSet; return IntOrFloat::evaluate($result); } } PK!oi!!Alibraries/vendor/PhpSpreadsheet/Calculation/Information/Value.phpnu[getWorksheet()->getParentOrThrow()->getSheetByName($worksheet) === null) { return false; } [$column, $row] = Coordinate::indexesFromString($cellValue ?? ''); if ($column > 16384 || $row > 1048576) { return false; } return true; } $namedRange = $cell->getWorksheet()->getParentOrThrow()->getNamedRange($value); return $namedRange instanceof NamedRange; } /** * IS_EVEN. * * @param mixed $value Value to check * Or can be an array of values * * @return array|bool|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function isEven($value = null) { if (is_array($value)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value); } if ($value === null) { return ExcelError::NAME(); } if (!is_numeric($value)) { return ExcelError::VALUE(); } return ((int) fmod($value + 0, 2)) === 0; } /** * IS_ODD. * * @param mixed $value Value to check * Or can be an array of values * * @return array|bool|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function isOdd($value = null) { if (is_array($value)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value); } if ($value === null) { return ExcelError::NAME(); } if (!is_numeric($value)) { return ExcelError::VALUE(); } return ((int) fmod($value + 0, 2)) !== 0; } /** * IS_NUMBER. * * @param mixed $value Value to check * Or can be an array of values * * @return array|bool If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function isNumber($value = null) { if (is_array($value)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value); } if (is_string($value)) { return false; } return is_numeric($value); } /** * IS_LOGICAL. * * @param mixed $value Value to check * Or can be an array of values * * @return array|bool If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function isLogical($value = null) { if (is_array($value)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value); } return is_bool($value); } /** * IS_TEXT. * * @param mixed $value Value to check * Or can be an array of values * * @return array|bool If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function isText($value = null) { if (is_array($value)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value); } return is_string($value) && !ErrorValue::isError($value); } /** * IS_NONTEXT. * * @param mixed $value Value to check * Or can be an array of values * * @return array|bool If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function isNonText($value = null) { if (is_array($value)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value); } return !self::isText($value); } /** * ISFORMULA. * * @param mixed $cellReference The cell to check * @param ?Cell $cell The current cell (containing this formula) * @return mixed[]|bool|string */ public static function isFormula($cellReference = '', ?Cell $cell = null) { if ($cell === null) { return ExcelError::REF(); } $cellReference = StringHelper::convertToString($cellReference); $fullCellReference = Functions::expandDefinedName($cellReference, $cell); if (str_contains($cellReference, '!')) { $cellReference = Functions::trimSheetFromCellReference($cellReference); $cellReferences = Coordinate::extractAllCellReferencesInRange($cellReference); if (count($cellReferences) > 1) { return self::evaluateArrayArgumentsSubset([self::class, __FUNCTION__], 1, $cellReferences, $cell); } } $fullCellReference = Functions::trimTrailingRange($fullCellReference); $worksheetName = ''; if (1 == preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $fullCellReference, $matches)) { $fullCellReference = $matches[6] . $matches[7]; $worksheetName = str_replace("''", "'", trim($matches[2], "'")); } $worksheet = (!empty($worksheetName)) ? $cell->getWorksheet()->getParentOrThrow()->getSheetByName($worksheetName) : $cell->getWorksheet(); return ($worksheet !== null) ? $worksheet->getCell($fullCellReference)->isFormula() : ExcelError::REF(); } /** * N. * * Returns a value converted to a number * * @param null|mixed $value The value you want converted * * @return number|string N converts values listed in the following table * If value is or refers to N returns * A number That number value * A date The Excel serialized number of that date * TRUE 1 * FALSE 0 * An error value The error value * Anything else 0 */ public static function asNumber($value = null) { while (is_array($value)) { $value = array_shift($value); } if (is_float($value) || is_int($value)) { return $value; } if (is_bool($value)) { return (int) $value; } if (is_string($value) && substr($value, 0, 1) === '#') { return $value; } return 0; } /** * TYPE. * * Returns a number that identifies the type of a value * * @param null|mixed $value The value you want tested * * @return int N converts values listed in the following table * If value is or refers to N returns * A number 1 * Text 2 * Logical Value 4 * An error value 16 * Array or Matrix 64 */ public static function type($value = null): int { $value = Functions::flattenArrayIndexed($value); if (count($value) > 1) { end($value); $a = key($value); // Range of cells is an error if (Functions::isCellValue($a)) { return 16; // Test for Matrix } elseif (Functions::isMatrixValue($a)) { return 64; } } elseif (empty($value)) { // Empty Cell return 1; } $value = Functions::flattenSingleValue($value); if (($value === null) || (is_float($value)) || (is_int($value))) { return 1; } elseif (is_bool($value)) { return 4; } elseif (is_array($value)) { return 64; } elseif (is_string($value)) { // Errors if (($value !== '') && ($value[0] == '#')) { return 16; } return 2; } return 0; } } PK!Alibraries/vendor/PhpSpreadsheet/Calculation/Information/index.phpnu[ */ public const ERROR_CODES = [ 'null' => '#NULL!', // 1 'divisionbyzero' => '#DIV/0!', // 2 'value' => '#VALUE!', // 3 'reference' => '#REF!', // 4 'name' => '#NAME?', // 5 'num' => '#NUM!', // 6 'na' => '#N/A', // 7 'gettingdata' => '#GETTING_DATA', // 8 'spill' => '#SPILL!', // 9 'connect' => '#CONNECT!', //10 'blocked' => '#BLOCKED!', //11 'unknown' => '#UNKNOWN!', //12 'field' => '#FIELD!', //13 'calculation' => '#CALC!', //14 ]; /** * @param mixed $value */ public static function throwError($value): string { return in_array($value, self::ERROR_CODES, true) ? $value : self::ERROR_CODES['value']; } /** * ERROR_TYPE. * * @param mixed $value Value to check * @return mixed[]|int|string */ public static function type($value = '') { if (is_array($value)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value); } $i = 1; foreach (self::ERROR_CODES as $errorCode) { if ($value === $errorCode) { return $i; } ++$i; } return self::NA(); } /** * NULL. * * Returns the error value #NULL! * * @return string #NULL! */ public static function null(): string { return self::ERROR_CODES['null']; } /** * NaN. * * Returns the error value #NUM! * * @return string #NUM! */ public static function NAN(): string { return self::ERROR_CODES['num']; } /** * REF. * * Returns the error value #REF! * * @return string #REF! */ public static function REF(): string { return self::ERROR_CODES['reference']; } /** * NA. * * Excel Function: * =NA() * * Returns the error value #N/A * #N/A is the error value that means "no value is available." * * @return string #N/A! */ public static function NA(): string { return self::ERROR_CODES['na']; } /** * VALUE. * * Returns the error value #VALUE! * * @return string #VALUE! */ public static function VALUE(): string { return self::ERROR_CODES['value']; } /** * NAME. * * Returns the error value #NAME? * * @return string #NAME? */ public static function NAME(): string { return self::ERROR_CODES['name']; } /** * DIV0. * * @return string #DIV/0! */ public static function DIV0(): string { return self::ERROR_CODES['divisionbyzero']; } /** * CALC. * * @return string #CALC! */ public static function CALC(): string { return self::ERROR_CODES['calculation']; } /** * SPILL. * * @return string #SPILL! */ public static function SPILL(): string { return self::ERROR_CODES['spill']; } } PK!9{>]}}Flibraries/vendor/PhpSpreadsheet/Calculation/Information/ErrorValue.phpnu[ $trueValueCount === $count); } /** * LOGICAL_OR. * * Returns boolean TRUE if any argument is TRUE; returns FALSE if all arguments are FALSE. * * Excel Function: * =OR(logical1[,logical2[, ...]]) * * The arguments must evaluate to logical values such as TRUE or FALSE, or the arguments must be arrays * or references that contain logical values. * * Boolean arguments are treated as True or False as appropriate * Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False * If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string * holds the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value * * @param mixed $args Data values * * @return bool|string the logical OR of the arguments */ public static function logicalOr(...$args) { return self::countTrueValues($args, fn (int $trueValueCount): bool => $trueValueCount > 0); } /** * LOGICAL_XOR. * * Returns the Exclusive Or logical operation for one or more supplied conditions. * i.e. the Xor function returns TRUE if an odd number of the supplied conditions evaluate to TRUE, * and FALSE otherwise. * * Excel Function: * =XOR(logical1[,logical2[, ...]]) * * The arguments must evaluate to logical values such as TRUE or FALSE, or the arguments must be arrays * or references that contain logical values. * * Boolean arguments are treated as True or False as appropriate * Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False * If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string * holds the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value * * @param mixed $args Data values * * @return bool|string the logical XOR of the arguments */ public static function logicalXor(...$args) { return self::countTrueValues($args, fn (int $trueValueCount): bool => $trueValueCount % 2 === 1); } /** * NOT. * * Returns the boolean inverse of the argument. * * Excel Function: * =NOT(logical) * * The argument must evaluate to a logical value such as TRUE or FALSE * * Boolean arguments are treated as True or False as appropriate * Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False * If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string * holds the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value * * @param mixed $logical A value or expression that can be evaluated to TRUE or FALSE * Or can be an array of values * * @return array|bool|string the boolean inverse of the argument * If an array of values is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function NOT($logical = false) { if (is_array($logical)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $logical); } if (is_string($logical)) { $logical = mb_strtoupper($logical, 'UTF-8'); if (($logical == 'TRUE') || ($logical == Calculation::getTRUE())) { return false; } elseif (($logical == 'FALSE') || ($logical == Calculation::getFALSE())) { return true; } return ExcelError::VALUE(); } return !$logical; } /** * @return bool|string */ private static function countTrueValues(array $args, callable $func) { $trueValueCount = 0; $count = 0; $aArgs = Functions::flattenArrayIndexed($args); foreach ($aArgs as $k => $arg) { ++$count; // Is it a boolean value? if (is_bool($arg)) { $trueValueCount += $arg; } elseif (is_string($arg)) { $isLiteral = !Functions::isCellValue($k); $arg = mb_strtoupper($arg, 'UTF-8'); if ($isLiteral && ($arg == 'TRUE' || $arg == Calculation::getTRUE())) { ++$trueValueCount; } elseif ($isLiteral && ($arg == 'FALSE' || $arg == Calculation::getFALSE())) { //$trueValueCount += 0; } else { --$count; } } elseif (is_int($arg) || is_float($arg)) { $trueValueCount += (int) ($arg != 0); } else { --$count; } } return ($count === 0) ? ExcelError::VALUE() : $func($trueValueCount, $count); } } PK!mGD D Clibraries/vendor/PhpSpreadsheet/Calculation/Logical/Conditional.phpnu[ 0) { $targetValue = Functions::flattenSingleValue($arguments[0]); $argc = count($arguments) - 1; $switchCount = floor($argc / 2); $hasDefaultClause = $argc % 2 !== 0; $defaultClause = $argc % 2 === 0 ? null : $arguments[$argc]; $switchSatisfied = false; if ($switchCount > 0) { for ($index = 0; $index < $switchCount; ++$index) { if ($targetValue == Functions::flattenSingleValue($arguments[$index * 2 + 1])) { $result = $arguments[$index * 2 + 2]; $switchSatisfied = true; break; } } } if ($switchSatisfied !== true) { $result = $hasDefaultClause ? $defaultClause : ExcelError::NA(); } } return $result; } /** * IFERROR. * * Excel Function: * =IFERROR(testValue,errorpart) * * @param mixed $testValue Value to check, is also the value returned when no error * Or can be an array of values * @param mixed $errorpart Value to return when testValue is an error condition * Note that this can be an array value to be returned * * @return mixed The value of errorpart or testValue determined by error condition * If an array of values is passed as the $testValue argument, then the returned result will also be * an array with the same dimensions */ public static function IFERROR($testValue = '', $errorpart = '') { if (is_array($testValue)) { return self::evaluateArrayArgumentsSubset([self::class, __FUNCTION__], 1, $testValue, $errorpart); } $errorpart = $errorpart ?? ''; $testValue = $testValue ?? 0; // this is how Excel handles empty cell return self::statementIf(ErrorValue::isError($testValue), $errorpart, $testValue); } /** * IFNA. * * Excel Function: * =IFNA(testValue,napart) * * @param mixed $testValue Value to check, is also the value returned when not an NA * Or can be an array of values * @param mixed $napart Value to return when testValue is an NA condition * Note that this can be an array value to be returned * * @return mixed The value of errorpart or testValue determined by error condition * If an array of values is passed as the $testValue argument, then the returned result will also be * an array with the same dimensions */ public static function IFNA($testValue = '', $napart = '') { if (is_array($testValue)) { return self::evaluateArrayArgumentsSubset([self::class, __FUNCTION__], 1, $testValue, $napart); } $napart = $napart ?? ''; $testValue = $testValue ?? 0; // this is how Excel handles empty cell return self::statementIf(ErrorValue::isNa($testValue), $napart, $testValue); } /** * IFS. * * Excel Function: * =IFS(testValue1;returnIfTrue1;testValue2;returnIfTrue2;...;testValue_n;returnIfTrue_n) * * testValue1 ... testValue_n * Conditions to Evaluate * returnIfTrue1 ... returnIfTrue_n * Value returned if corresponding testValue (nth) was true * * @param mixed ...$arguments Statement arguments * Note that this can be an array value to be returned * * @return mixed|string The value of returnIfTrue_n, if testValue_n was true. #N/A if none of testValues was true */ public static function IFS(...$arguments) { $argumentCount = count($arguments); if ($argumentCount % 2 != 0) { return ExcelError::NA(); } // We use instance of Exception as a falseValue in order to prevent string collision with value in cell $falseValueException = new Exception(); for ($i = 0; $i < $argumentCount; $i += 2) { $testValue = ($arguments[$i] === null) ? '' : Functions::flattenSingleValue($arguments[$i]); $returnIfTrue = ($arguments[$i + 1] === null) ? '' : $arguments[$i + 1]; $result = self::statementIf($testValue, $returnIfTrue, $falseValueException); if ($result !== $falseValueException) { return $result; } } return ExcelError::NA(); } } PK!=libraries/vendor/PhpSpreadsheet/Calculation/Logical/index.phpnu[= count($fieldNames)) { return null; } return $field; } $key = array_search($field, array_values($fieldNames), true); return ($key !== false) ? (int) $key : null; } /** * filter. * * Parses the selection criteria, extracts the database rows that match those criteria, and * returns that subset of rows. * * @param mixed[] $database The range of cells that makes up the list or database. * A database is a list of related data in which rows of related * information are records, and columns of data are fields. The * first row of the list contains labels for each column. * @param mixed[] $criteria The range of cells that contains the conditions you specify. * You can use any range for the criteria argument, as long as it * includes at least one column label and at least one cell below * the column label in which you specify a condition for the * column. * * @return mixed[] */ protected static function filter(array $database, array $criteria): array { /** @var array */ $fieldNames = array_shift($database); /** @var array */ $criteriaNames = array_shift($criteria); // Convert the criteria into a set of AND/OR conditions with [:placeholders] $query = self::buildQuery($criteriaNames, $criteria); // Loop through each row of the database return self::executeQuery($database, $query, $criteriaNames, $fieldNames); } protected static function getFilteredColumn(array $database, ?int $field, array $criteria): array { // reduce the database to a set of rows that match all the criteria $database = self::filter($database, $criteria); $defaultReturnColumnValue = ($field === null) ? 1 : null; // extract an array of values for the requested column $columnData = []; /** @var array $row */ foreach ($database as $rowKey => $row) { $keys = array_keys($row); $key = $keys[$field] ?? null; $columnKey = $key ?? 'A'; $columnData[$rowKey][$columnKey] = $row[$key] ?? $defaultReturnColumnValue; } return $columnData; } private static function buildQuery(array $criteriaNames, array $criteria): string { $baseQuery = []; foreach ($criteria as $key => $criterion) { foreach ($criterion as $field => $value) { $criterionName = $criteriaNames[$field]; if ($value !== null) { $condition = self::buildCondition($value, $criterionName); $baseQuery[$key][] = $condition; } } } $rowQuery = array_map( fn ($rowValue): string => (count($rowValue) > 1) ? 'AND(' . implode(',', $rowValue) . ')' : ($rowValue[0] ?? ''), // @phpstan-ignore-line $baseQuery ); return (count($rowQuery) > 1) ? 'OR(' . implode(',', $rowQuery) . ')' : ($rowQuery[0] ?? ''); } /** * @param mixed $criterion */ private static function buildCondition($criterion, string $criterionName): string { $ifCondition = Functions::ifCondition($criterion); // Check for wildcard characters used in the condition $result = preg_match('/(?[^"]*)(?".*[*?].*")/ui', $ifCondition, $matches); if ($result !== 1) { return "[:{$criterionName}]{$ifCondition}"; } $trueFalse = ($matches['operator'] !== '<>'); $wildcard = WildcardMatch::wildcard($matches['operand']); $condition = "WILDCARDMATCH([:{$criterionName}],{$wildcard})"; if ($trueFalse === false) { $condition = "NOT({$condition})"; } return $condition; } private static function executeQuery(array $database, string $query, array $criteria, array $fields): array { foreach ($database as $dataRow => $dataValues) { // Substitute actual values from the database row for our [:placeholders] $conditions = $query; foreach ($criteria as $criterion) { $conditions = self::processCondition($criterion, $fields, $dataValues, $conditions); } // evaluate the criteria against the row data $result = Calculation::getInstance()->_calculateFormulaValue('=' . $conditions); // If the row failed to meet the criteria, remove it from the database if ($result !== true) { unset($database[$dataRow]); } } return $database; } private static function processCondition(string $criterion, array $fields, array $dataValues, string $conditions): string { $key = array_search($criterion, $fields, true); $dataValue = 'NULL'; if (is_bool($dataValues[$key])) { $dataValue = ($dataValues[$key]) ? 'TRUE' : 'FALSE'; } elseif ($dataValues[$key] !== null) { $dataValue = $dataValues[$key]; // escape quotes if we have a string containing quotes if (is_string($dataValue) && str_contains($dataValue, '"')) { $dataValue = str_replace('"', '""', $dataValue); } $dataValue = (is_string($dataValue)) ? Calculation::wrapResult(strtoupper($dataValue)) : $dataValue; } return str_replace('[:' . $criterion . ']', $dataValue, $conditions); } } PK!0'=libraries/vendor/PhpSpreadsheet/Calculation/Database/DGet.phpnu[ 1) { return ExcelError::NAN(); } $row = array_pop($columnData); return array_pop($row); } } PK!N  @libraries/vendor/PhpSpreadsheet/Calculation/Database/DStDevP.phpnu[libraries/vendor/PhpSpreadsheet/Calculation/Database/index.phpnu[libraries/vendor/PhpSpreadsheet/Calculation/Database/DVarP.phpnu[getMessage(); } return $principal; } /** * PDURATION. * * Calculates the number of periods required for an investment to reach a specified value. * * @param mixed $rate Interest rate per period * @param mixed $presentValue Present Value * @param mixed $futureValue Future Value * * @return float|string Result, or a string containing an error */ public static function periods($rate, $presentValue, $futureValue) { $rate = Functions::flattenSingleValue($rate); $presentValue = Functions::flattenSingleValue($presentValue); $futureValue = Functions::flattenSingleValue($futureValue); try { $rate = CashFlowValidations::validateRate($rate); $presentValue = CashFlowValidations::validatePresentValue($presentValue); $futureValue = CashFlowValidations::validateFutureValue($futureValue); } catch (Exception $e) { return $e->getMessage(); } // Validate parameters if ($rate <= 0.0 || $presentValue <= 0.0 || $futureValue <= 0.0) { return ExcelError::NAN(); } return (log($futureValue) - log($presentValue)) / log(1 + $rate); } /** * RRI. * * Calculates the interest rate required for an investment to grow to a specified future value . * * @param mixed $periods The number of periods over which the investment is made, expect array|float * @param mixed $presentValue Present Value, expect array|float * @param mixed $futureValue Future Value, expect array|float * * @return float|string Result, or a string containing an error */ public static function interestRate($periods = 0.0, $presentValue = 0.0, $futureValue = 0.0) { $periods = Functions::flattenSingleValue($periods); $presentValue = Functions::flattenSingleValue($presentValue); $futureValue = Functions::flattenSingleValue($futureValue); try { $periods = CashFlowValidations::validateFloat($periods); $presentValue = CashFlowValidations::validatePresentValue($presentValue); $futureValue = CashFlowValidations::validateFutureValue($futureValue); } catch (Exception $e) { return $e->getMessage(); } // Validate parameters if ($periods <= 0.0 || $presentValue <= 0.0 || $futureValue < 0.0) { return ExcelError::NAN(); } return ($futureValue / $presentValue) ** (1 / $periods) - 1; } } PK! Tlibraries/vendor/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/Periodic.phpnu[ 0.0) { return ExcelError::VALUE(); } $f = self::presentValue($x1, $values); if ($f < 0.0) { $rtb = $x1; $dx = $x2 - $x1; } else { $rtb = $x2; $dx = $x1 - $x2; } for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) { $dx *= 0.5; $x_mid = $rtb + $dx; $f_mid = self::presentValue($x_mid, $values); if ($f_mid <= 0.0) { $rtb = $x_mid; } if ((abs($f_mid) < self::FINANCIAL_PRECISION) || (abs($dx) < self::FINANCIAL_PRECISION)) { return $x_mid; } } return ExcelError::VALUE(); } /** * MIRR. * * Returns the modified internal rate of return for a series of periodic cash flows. MIRR considers both * the cost of the investment and the interest received on reinvestment of cash. * * Excel Function: * MIRR(values,finance_rate, reinvestment_rate) * * @param mixed $values An array or a reference to cells that contain a series of payments and * income occurring at regular intervals. * Payments are negative value, income is positive values. * @param mixed $financeRate The interest rate you pay on the money used in the cash flows * @param mixed $reinvestmentRate The interest rate you receive on the cash flows as you reinvest them * * @return float|string Result, or a string containing an error */ public static function modifiedRate($values, $financeRate, $reinvestmentRate) { if (!is_array($values)) { return ExcelError::DIV0(); } $values = Functions::flattenArray($values); /** @var float */ $financeRate = Functions::flattenSingleValue($financeRate); /** @var float */ $reinvestmentRate = Functions::flattenSingleValue($reinvestmentRate); $n = count($values); $rr = 1.0 + $reinvestmentRate; $fr = 1.0 + $financeRate; $npvPos = $npvNeg = 0.0; foreach ($values as $i => $v) { if ($v >= 0) { $npvPos += $v / $rr ** $i; } else { $npvNeg += $v / $fr ** $i; } } if ($npvNeg === 0.0 || $npvPos === 0.0) { return ExcelError::DIV0(); } $mirr = ((-$npvPos * $rr ** $n) / ($npvNeg * ($rr))) ** (1.0 / ($n - 1)) - 1.0; return is_finite($mirr) ? $mirr : ExcelError::NAN(); } /** * NPV. * * Returns the Net Present Value of a cash flow series given a discount rate. * * @param array $args * @return float|int * @param mixed $rate */ public static function presentValue($rate, ...$args) { $returnValue = 0; /** @var float */ $rate = Functions::flattenSingleValue($rate); $aArgs = Functions::flattenArray($args); // Calculate $countArgs = count($aArgs); for ($i = 1; $i <= $countArgs; ++$i) { // Is it a numeric value? if (is_numeric($aArgs[$i - 1])) { $returnValue += $aArgs[$i - 1] / (1 + $rate) ** $i; } } return $returnValue; } } PK!Qlibraries/vendor/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/index.phpnu[ 1; $datesIsArray = count($dates) > 1; if (!$valuesIsArray && !$datesIsArray) { return ExcelError::NA(); } if (count($values) != count($dates)) { return ExcelError::NAN(); } $datesCount = count($dates); for ($i = 0; $i < $datesCount; ++$i) { try { $dates[$i] = DateTimeExcel\Helpers::getDateValue($dates[$i]); } catch (Exception $e) { return $e->getMessage(); } } return self::xirrPart2($values); } /** @param array $values */ private static function xirrPart2(array &$values): string { $valCount = count($values); $foundpos = false; $foundneg = false; for ($i = 0; $i < $valCount; ++$i) { $fld = $values[$i]; if (!is_numeric($fld)) { //* @phpstan-ignore-line return ExcelError::VALUE(); } elseif ($fld > 0) { $foundpos = true; } elseif ($fld < 0) { $foundneg = true; } } if (!self::bothNegAndPos($foundneg, $foundpos)) { return ExcelError::NAN(); } return ''; } /** * @return float|string */ private static function xirrPart3(array $values, array $dates, float $x1, float $x2) { $f = self::xnpvOrdered($x1, $values, $dates, false); if ($f < 0.0) { $rtb = $x1; $dx = $x2 - $x1; } else { $rtb = $x2; $dx = $x1 - $x2; } $rslt = ExcelError::VALUE(); for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) { $dx *= 0.5; $x_mid = $rtb + $dx; $f_mid = (float) self::xnpvOrdered($x_mid, $values, $dates, false); if ($f_mid <= 0.0) { $rtb = $x_mid; } if ((abs($f_mid) < self::FINANCIAL_PRECISION) || (abs($dx) < self::FINANCIAL_PRECISION)) { $rslt = $x_mid; break; } } return $rslt; } /** * @return float|string */ private static function xirrBisection(array $values, array $dates, float $x1, float $x2) { $rslt = ExcelError::NAN(); for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) { $rslt = ExcelError::NAN(); $f1 = self::xnpvOrdered($x1, $values, $dates, false, true); $f2 = self::xnpvOrdered($x2, $values, $dates, false, true); if (!is_numeric($f1) || !is_numeric($f2)) { break; } $f1 = (float) $f1; $f2 = (float) $f2; if (abs($f1) < self::FINANCIAL_PRECISION && abs($f2) < self::FINANCIAL_PRECISION) { break; } if ($f1 * $f2 > 0) { break; } $rslt = ($x1 + $x2) / 2; $f3 = self::xnpvOrdered($rslt, $values, $dates, false, true); if (!is_float($f3)) { break; } if ($f3 * $f1 < 0) { $x2 = $rslt; } else { $x1 = $rslt; } if (abs($f3) < self::FINANCIAL_PRECISION) { break; } } return $rslt; } /** * @param mixed $values > * @return float|string * @param mixed $rate * @param mixed $dates */ private static function xnpvOrdered($rate, $values, $dates, bool $ordered = true, bool $capAtNegative1 = false) { $rate = Functions::flattenSingleValue($rate); if (!is_numeric($rate)) { return ExcelError::VALUE(); } $values = Functions::flattenArray($values); $dates = Functions::flattenArray($dates); $valCount = count($values); try { self::validateXnpv($rate, $values, $dates); if ($capAtNegative1 && $rate <= -1) { $rate = -1.0 + 1.0E-10; } $date0 = DateTimeExcel\Helpers::getDateValue($dates[0]); } catch (Exception $e) { return $e->getMessage(); } $xnpv = 0.0; for ($i = 0; $i < $valCount; ++$i) { if (!is_numeric($values[$i])) { return ExcelError::VALUE(); } try { $datei = DateTimeExcel\Helpers::getDateValue($dates[$i]); } catch (Exception $e) { return $e->getMessage(); } if ($date0 > $datei) { $dif = $ordered ? ExcelError::NAN() : -((int) DateTimeExcel\Difference::interval($datei, $date0, 'd')); } else { $dif = Functions::scalar(DateTimeExcel\Difference::interval($date0, $datei, 'd')); } if (!is_numeric($dif)) { return StringHelper::convertToString($dif); } if ($rate <= -1.0) { $xnpv += -abs($values[$i] + 0) / (-1 - $rate) ** ($dif / 365); } else { $xnpv += $values[$i] / (1 + $rate) ** ($dif / 365); } } return is_finite($xnpv) ? $xnpv : ExcelError::VALUE(); } /** * @param mixed $rate */ private static function validateXnpv($rate, array $values, array $dates): void { if (!is_numeric($rate)) { throw new Exception(ExcelError::VALUE()); } $valCount = count($values); if ($valCount != count($dates)) { throw new Exception(ExcelError::NAN()); } if (count($values) > 1 && ((min($values) > 0) || (max($values) < 0))) { throw new Exception(ExcelError::NAN()); } } } PK!?Tlibraries/vendor/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic.phpnu[getMessage(); } return self::calculateFutureValue($rate, $numberOfPeriods, $payment, $presentValue, $type); } /** * PV. * * Returns the Present Value of a cash flow with constant payments and interest rate (annuities). * * @param mixed $rate Interest rate per period * @param mixed $numberOfPeriods Number of periods as an integer * @param mixed $payment Periodic payment (annuity) * @param mixed $futureValue Future Value * @param mixed $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period * * @return float|string Result, or a string containing an error */ public static function presentValue( $rate, $numberOfPeriods, $payment = 0.0, $futureValue = 0.0, $type = FinancialConstants::PAYMENT_END_OF_PERIOD ) { $rate = Functions::flattenSingleValue($rate); $numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods); $payment = ($payment === null) ? 0.0 : Functions::flattenSingleValue($payment); $futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue); $type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type); try { $rate = CashFlowValidations::validateRate($rate); $numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods); $payment = CashFlowValidations::validateFloat($payment); $futureValue = CashFlowValidations::validateFutureValue($futureValue); $type = CashFlowValidations::validatePeriodType($type); } catch (Exception $e) { return $e->getMessage(); } // Validate parameters if ($numberOfPeriods < 0) { return ExcelError::NAN(); } return self::calculatePresentValue($rate, $numberOfPeriods, $payment, $futureValue, $type); } /** * NPER. * * Returns the number of periods for a cash flow with constant periodic payments (annuities), and interest rate. * * @param mixed $rate Interest rate per period * @param mixed $payment Periodic payment (annuity) * @param mixed $presentValue Present Value * @param mixed $futureValue Future Value * @param mixed $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period * * @return float|string Result, or a string containing an error */ public static function periods( $rate, $payment, $presentValue, $futureValue = 0.0, $type = FinancialConstants::PAYMENT_END_OF_PERIOD ) { $rate = Functions::flattenSingleValue($rate); $payment = Functions::flattenSingleValue($payment); $presentValue = Functions::flattenSingleValue($presentValue); $futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue); $type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type); try { $rate = CashFlowValidations::validateRate($rate); $payment = CashFlowValidations::validateFloat($payment); $presentValue = CashFlowValidations::validatePresentValue($presentValue); $futureValue = CashFlowValidations::validateFutureValue($futureValue); $type = CashFlowValidations::validatePeriodType($type); } catch (Exception $e) { return $e->getMessage(); } // Validate parameters if ($payment == 0.0) { return ExcelError::NAN(); } return self::calculatePeriods($rate, $payment, $presentValue, $futureValue, $type); } private static function calculateFutureValue( float $rate, int $numberOfPeriods, float $payment, float $presentValue, int $type ): float { if ($rate != 0) { return -$presentValue * (1 + $rate) ** $numberOfPeriods - $payment * (1 + $rate * $type) * ((1 + $rate) ** $numberOfPeriods - 1) / $rate; } return -$presentValue - $payment * $numberOfPeriods; } private static function calculatePresentValue( float $rate, int $numberOfPeriods, float $payment, float $futureValue, int $type ): float { if ($rate != 0.0) { return (-$payment * (1 + $rate * $type) * (((1 + $rate) ** $numberOfPeriods - 1) / $rate) - $futureValue) / (1 + $rate) ** $numberOfPeriods; } return -$futureValue - $payment * $numberOfPeriods; } /** * @return float|string */ private static function calculatePeriods( float $rate, float $payment, float $presentValue, float $futureValue, int $type ) { if ($rate != 0.0) { if ($presentValue == 0.0) { return ExcelError::NAN(); } return log(($payment * (1 + $rate * $type) / $rate - $futureValue) / ($presentValue + $payment * (1 + $rate * $type) / $rate)) / log(1 + $rate); } return (-$presentValue - $futureValue) / $payment; } } PK!Qlibraries/vendor/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/index.phpnu[getMessage(); } // Validate parameters if ($period <= 0 || $period > $numberOfPeriods) { return ExcelError::NAN(); } // Calculate $interestAndPrincipal = new InterestAndPrincipal( $interestRate, $period, $numberOfPeriods, $presentValue, $futureValue, $type ); return $interestAndPrincipal->interest(); } /** * ISPMT. * * Returns the interest payment for an investment based on an interest rate and a constant payment schedule. * * Excel Function: * =ISPMT(interest_rate, period, number_payments, pv) * * @param mixed $interestRate is the interest rate for the investment * @param mixed $period is the period to calculate the interest rate. It must be betweeen 1 and number_payments. * @param mixed $numberOfPeriods is the number of payments for the annuity * @param mixed $principleRemaining is the loan amount or present value of the payments * @return float|string */ public static function schedulePayment($interestRate, $period, $numberOfPeriods, $principleRemaining) { $interestRate = Functions::flattenSingleValue($interestRate); $period = Functions::flattenSingleValue($period); $numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods); $principleRemaining = Functions::flattenSingleValue($principleRemaining); try { $interestRate = CashFlowValidations::validateRate($interestRate); $period = CashFlowValidations::validateInt($period); $numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods); $principleRemaining = CashFlowValidations::validateFloat($principleRemaining); } catch (Exception $e) { return $e->getMessage(); } // Validate parameters if ($period <= 0 || $period > $numberOfPeriods) { return ExcelError::NAN(); } // Return value $returnValue = 0; // Calculate $principlePayment = ($principleRemaining * 1.0) / ($numberOfPeriods * 1.0); for ($i = 0; $i <= $period; ++$i) { $returnValue = $interestRate * $principleRemaining * -1; $principleRemaining -= $principlePayment; // principle needs to be 0 after the last payment, don't let floating point screw it up if ($i == $numberOfPeriods) { $returnValue = 0.0; } } return $returnValue; } /** * RATE. * * Returns the interest rate per period of an annuity. * RATE is calculated by iteration and can have zero or more solutions. * If the successive results of RATE do not converge to within 0.0000001 after 20 iterations, * RATE returns the #NUM! error value. * * Excel Function: * RATE(nper,pmt,pv[,fv[,type[,guess]]]) * * @param mixed $numberOfPeriods The total number of payment periods in an annuity * @param mixed $payment The payment made each period and cannot change over the life of the annuity. * Typically, pmt includes principal and interest but no other fees or taxes. * @param mixed $presentValue The present value - the total amount that a series of future payments is worth now * @param mixed $futureValue The future value, or a cash balance you want to attain after the last payment is made. * If fv is omitted, it is assumed to be 0 (the future value of a loan, * for example, is 0). * @param mixed $type A number 0 or 1 and indicates when payments are due: * 0 or omitted At the end of the period. * 1 At the beginning of the period. * @param mixed $guess Your guess for what the rate will be. * If you omit guess, it is assumed to be 10 percent. * @return float|string */ public static function rate( $numberOfPeriods, $payment, $presentValue, $futureValue = 0.0, $type = FinancialConstants::PAYMENT_END_OF_PERIOD, $guess = 0.1 ) { $numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods); $payment = Functions::flattenSingleValue($payment); $presentValue = Functions::flattenSingleValue($presentValue); $futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue); $type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type); $guess = ($guess === null) ? 0.1 : Functions::flattenSingleValue($guess); try { $numberOfPeriods = CashFlowValidations::validateFloat($numberOfPeriods); $payment = CashFlowValidations::validateFloat($payment); $presentValue = CashFlowValidations::validatePresentValue($presentValue); $futureValue = CashFlowValidations::validateFutureValue($futureValue); $type = CashFlowValidations::validatePeriodType($type); $guess = CashFlowValidations::validateFloat($guess); } catch (Exception $e) { return $e->getMessage(); } $rate = $guess; // rest of code adapted from python/numpy $close = false; $iter = 0; while (!$close && $iter < self::FINANCIAL_MAX_ITERATIONS) { $nextdiff = self::rateNextGuess($rate, $numberOfPeriods, $payment, $presentValue, $futureValue, $type); if (!is_numeric($nextdiff)) { break; } $rate1 = $rate - $nextdiff; $close = abs($rate1 - $rate) < self::FINANCIAL_PRECISION; ++$iter; $rate = $rate1; } return $close ? $rate : ExcelError::NAN(); } /** * @return float|string */ private static function rateNextGuess(float $rate, float $numberOfPeriods, float $payment, float $presentValue, float $futureValue, int $type) { if ($rate == 0.0) { return ExcelError::NAN(); } $tt1 = ($rate + 1) ** $numberOfPeriods; $tt2 = ($rate + 1) ** ($numberOfPeriods - 1); $numerator = $futureValue + $tt1 * $presentValue + $payment * ($tt1 - 1) * ($rate * $type + 1) / $rate; $denominator = $numberOfPeriods * $tt2 * $presentValue - $payment * ($tt1 - 1) * ($rate * $type + 1) / ($rate * $rate) + $numberOfPeriods * $payment * $tt2 * ($rate * $type + 1) / $rate + $payment * ($tt1 - 1) * $type / $rate; if ($denominator == 0) { return ExcelError::NAN(); } return $numerator / $denominator; } } PK!V ]libraries/vendor/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Payments.phpnu[getMessage(); } // Calculate if ($interestRate != 0.0) { return (-$futureValue - $presentValue * (1 + $interestRate) ** $numberOfPeriods) / (1 + $interestRate * $type) / (((1 + $interestRate) ** $numberOfPeriods - 1) / $interestRate); } return (-$presentValue - $futureValue) / $numberOfPeriods; } /** * PPMT. * * Returns the interest payment for a given period for an investment based on periodic, constant payments * and a constant interest rate. * * @param mixed $interestRate Interest rate per period * @param mixed $period Period for which we want to find the interest * @param mixed $numberOfPeriods Number of periods * @param mixed $presentValue Present Value * @param mixed $futureValue Future Value * @param mixed $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period * * @return float|string Result, or a string containing an error */ public static function interestPayment( $interestRate, $period, $numberOfPeriods, $presentValue, $futureValue = 0, $type = FinancialConstants::PAYMENT_END_OF_PERIOD ) { $interestRate = Functions::flattenSingleValue($interestRate); $period = Functions::flattenSingleValue($period); $numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods); $presentValue = Functions::flattenSingleValue($presentValue); $futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue); $type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type); try { $interestRate = CashFlowValidations::validateRate($interestRate); $period = CashFlowValidations::validateInt($period); $numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods); $presentValue = CashFlowValidations::validatePresentValue($presentValue); $futureValue = CashFlowValidations::validateFutureValue($futureValue); $type = CashFlowValidations::validatePeriodType($type); } catch (Exception $e) { return $e->getMessage(); } // Validate parameters if ($period <= 0 || $period > $numberOfPeriods) { return ExcelError::NAN(); } // Calculate $interestAndPrincipal = new InterestAndPrincipal( $interestRate, $period, $numberOfPeriods, $presentValue, $futureValue, $type ); return $interestAndPrincipal->principal(); } } PK!Zlibraries/vendor/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/index.phpnu[interest = $interest; $this->principal = $principal; } public function interest(): float { return $this->interest; } public function principal(): float { return $this->principal; } } PK!;_libraries/vendor/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Cumulative.phpnu[getMessage(); } // Validate parameters if ($start < 1 || $start > $end) { return ExcelError::NAN(); } // Calculate $interest = 0; for ($per = $start; $per <= $end; ++$per) { $ipmt = Interest::payment($rate, $per, $periods, $presentValue, 0, $type); if (is_string($ipmt)) { return $ipmt; } $interest += $ipmt; } return $interest; } /** * CUMPRINC. * * Returns the cumulative principal paid on a loan between the start and end periods. * * Excel Function: * CUMPRINC(rate,nper,pv,start,end[,type]) * * @param mixed $rate The Interest rate * @param mixed $periods The total number of payment periods as an integer * @param mixed $presentValue Present Value * @param mixed $start The first period in the calculation. * Payment periods are numbered beginning with 1. * @param mixed $end the last period in the calculation * @param mixed $type A number 0 or 1 and indicates when payments are due: * 0 or omitted At the end of the period. * 1 At the beginning of the period. * @return float|int|string */ public static function principal( $rate, $periods, $presentValue, $start, $end, $type = FinancialConstants::PAYMENT_END_OF_PERIOD ) { $rate = Functions::flattenSingleValue($rate); $periods = Functions::flattenSingleValue($periods); $presentValue = Functions::flattenSingleValue($presentValue); $start = Functions::flattenSingleValue($start); $end = Functions::flattenSingleValue($end); $type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type); try { $rate = CashFlowValidations::validateRate($rate); $periods = CashFlowValidations::validateInt($periods); $presentValue = CashFlowValidations::validatePresentValue($presentValue); $start = CashFlowValidations::validateInt($start); $end = CashFlowValidations::validateInt($end); $type = CashFlowValidations::validatePeriodType($type); } catch (Exception $e) { return $e->getMessage(); } // Validate parameters if ($start < 1 || $start > $end) { return ExcelError::VALUE(); } // Calculate $principal = 0; for ($per = $start; $per <= $end; ++$per) { $ppmt = Payments::interestPayment($rate, $per, $periods, $presentValue, 0, $type); if (is_string($ppmt)) { return $ppmt; } $principal += $ppmt; } return $principal; } } PK!Hlibraries/vendor/PhpSpreadsheet/Calculation/Financial/CashFlow/index.phpnu[getMessage(); } $daysPerYear = Helpers::daysPerYear(Functions::scalar(DateTimeExcel\DateParts::year($settlement)), $basis); if (is_string($daysPerYear)) { return ExcelError::VALUE(); } $prev = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_PREVIOUS); if ($basis === FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL) { return abs((float) DateTimeExcel\Days::between($prev, $settlement)); } return (float) DateTimeExcel\YearFrac::fraction($prev, $settlement, $basis) * $daysPerYear; } /** * COUPDAYS. * * Returns the number of days in the coupon period that contains the settlement date. * * Excel Function: * COUPDAYS(settlement,maturity,frequency[,basis]) * * @param mixed $settlement The security's settlement date. * The security settlement date is the date after the issue * date when the security is traded to the buyer. * @param mixed $maturity The security's maturity date. * The maturity date is the date when the security expires. * @param mixed $frequency The number of coupon payments per year. * Valid frequency values are: * 1 Annual * 2 Semi-Annual * 4 Quarterly * @param mixed $basis The type of day count to use (int). * 0 or omitted US (NASD) 30/360 * 1 Actual/actual * 2 Actual/360 * 3 Actual/365 * 4 European 30/360 * @return float|int|string */ public static function COUPDAYS( $settlement, $maturity, $frequency, $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD ) { $settlement = Functions::flattenSingleValue($settlement); $maturity = Functions::flattenSingleValue($maturity); $frequency = Functions::flattenSingleValue($frequency); $basis = ($basis === null) ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD : Functions::flattenSingleValue($basis); try { $settlement = FinancialValidations::validateSettlementDate($settlement); $maturity = FinancialValidations::validateMaturityDate($maturity); self::validateCouponPeriod($settlement, $maturity); $frequency = FinancialValidations::validateFrequency($frequency); $basis = FinancialValidations::validateBasis($basis); } catch (Exception $e) { return $e->getMessage(); } switch ($basis) { case FinancialConstants::BASIS_DAYS_PER_YEAR_365: // Actual/365 return 365 / $frequency; case FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL: // Actual/actual if ($frequency == FinancialConstants::FREQUENCY_ANNUAL) { $daysPerYear = (int) Helpers::daysPerYear(Functions::scalar(DateTimeExcel\DateParts::year($settlement)), $basis); return $daysPerYear / $frequency; } $prev = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_PREVIOUS); $next = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_NEXT); return $next - $prev; default: // US (NASD) 30/360, Actual/360 or European 30/360 return 360 / $frequency; } } /** * COUPDAYSNC. * * Returns the number of days from the settlement date to the next coupon date. * * Excel Function: * COUPDAYSNC(settlement,maturity,frequency[,basis]) * * @param mixed $settlement The security's settlement date. * The security settlement date is the date after the issue * date when the security is traded to the buyer. * @param mixed $maturity The security's maturity date. * The maturity date is the date when the security expires. * @param mixed $frequency The number of coupon payments per year. * Valid frequency values are: * 1 Annual * 2 Semi-Annual * 4 Quarterly * @param mixed $basis The type of day count to use (int) . * 0 or omitted US (NASD) 30/360 * 1 Actual/actual * 2 Actual/360 * 3 Actual/365 * 4 European 30/360 * @return float|string */ public static function COUPDAYSNC( $settlement, $maturity, $frequency, $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD ) { $settlement = Functions::flattenSingleValue($settlement); $maturity = Functions::flattenSingleValue($maturity); $frequency = Functions::flattenSingleValue($frequency); $basis = ($basis === null) ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD : Functions::flattenSingleValue($basis); try { $settlement = FinancialValidations::validateSettlementDate($settlement); $maturity = FinancialValidations::validateMaturityDate($maturity); self::validateCouponPeriod($settlement, $maturity); $frequency = FinancialValidations::validateFrequency($frequency); $basis = FinancialValidations::validateBasis($basis); } catch (Exception $e) { return $e->getMessage(); } /** @var int $daysPerYear */ $daysPerYear = Helpers::daysPerYear(Functions::Scalar(DateTimeExcel\DateParts::year($settlement)), $basis); $next = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_NEXT); if ($basis === FinancialConstants::BASIS_DAYS_PER_YEAR_NASD) { $settlementDate = Date::excelToDateTimeObject($settlement); $settlementEoM = Helpers::isLastDayOfMonth($settlementDate); if ($settlementEoM) { ++$settlement; } } return (float) DateTimeExcel\YearFrac::fraction($settlement, $next, $basis) * $daysPerYear; } /** * COUPNCD. * * Returns the next coupon date after the settlement date. * * Excel Function: * COUPNCD(settlement,maturity,frequency[,basis]) * * @param mixed $settlement The security's settlement date. * The security settlement date is the date after the issue * date when the security is traded to the buyer. * @param mixed $maturity The security's maturity date. * The maturity date is the date when the security expires. * @param mixed $frequency The number of coupon payments per year. * Valid frequency values are: * 1 Annual * 2 Semi-Annual * 4 Quarterly * @param mixed $basis The type of day count to use (int). * 0 or omitted US (NASD) 30/360 * 1 Actual/actual * 2 Actual/360 * 3 Actual/365 * 4 European 30/360 * * @return float|string Excel date/time serial value or error message */ public static function COUPNCD( $settlement, $maturity, $frequency, $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD ) { $settlement = Functions::flattenSingleValue($settlement); $maturity = Functions::flattenSingleValue($maturity); $frequency = Functions::flattenSingleValue($frequency); $basis = ($basis === null) ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD : Functions::flattenSingleValue($basis); try { $settlement = FinancialValidations::validateSettlementDate($settlement); $maturity = FinancialValidations::validateMaturityDate($maturity); self::validateCouponPeriod($settlement, $maturity); $frequency = FinancialValidations::validateFrequency($frequency); FinancialValidations::validateBasis($basis); } catch (Exception $e) { return $e->getMessage(); } return self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_NEXT); } /** * COUPNUM. * * Returns the number of coupons payable between the settlement date and maturity date, * rounded up to the nearest whole coupon. * * Excel Function: * COUPNUM(settlement,maturity,frequency[,basis]) * * @param mixed $settlement The security's settlement date. * The security settlement date is the date after the issue * date when the security is traded to the buyer. * @param mixed $maturity The security's maturity date. * The maturity date is the date when the security expires. * @param mixed $frequency The number of coupon payments per year. * Valid frequency values are: * 1 Annual * 2 Semi-Annual * 4 Quarterly * @param mixed $basis The type of day count to use (int). * 0 or omitted US (NASD) 30/360 * 1 Actual/actual * 2 Actual/360 * 3 Actual/365 * 4 European 30/360 * @return int|string */ public static function COUPNUM( $settlement, $maturity, $frequency, $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD ) { $settlement = Functions::flattenSingleValue($settlement); $maturity = Functions::flattenSingleValue($maturity); $frequency = Functions::flattenSingleValue($frequency); $basis = ($basis === null) ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD : Functions::flattenSingleValue($basis); try { $settlement = FinancialValidations::validateSettlementDate($settlement); $maturity = FinancialValidations::validateMaturityDate($maturity); self::validateCouponPeriod($settlement, $maturity); $frequency = FinancialValidations::validateFrequency($frequency); FinancialValidations::validateBasis($basis); } catch (Exception $e) { return $e->getMessage(); } $yearsBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::fraction( $settlement, $maturity, FinancialConstants::BASIS_DAYS_PER_YEAR_NASD ); return (int) ceil((float) $yearsBetweenSettlementAndMaturity * $frequency); } /** * COUPPCD. * * Returns the previous coupon date before the settlement date. * * Excel Function: * COUPPCD(settlement,maturity,frequency[,basis]) * * @param mixed $settlement The security's settlement date. * The security settlement date is the date after the issue * date when the security is traded to the buyer. * @param mixed $maturity The security's maturity date. * The maturity date is the date when the security expires. * @param mixed $frequency The number of coupon payments per year. * Valid frequency values are: * 1 Annual * 2 Semi-Annual * 4 Quarterly * @param mixed $basis The type of day count to use (int). * 0 or omitted US (NASD) 30/360 * 1 Actual/actual * 2 Actual/360 * 3 Actual/365 * 4 European 30/360 * * @return float|string Excel date/time serial value or error message */ public static function COUPPCD( $settlement, $maturity, $frequency, $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD ) { $settlement = Functions::flattenSingleValue($settlement); $maturity = Functions::flattenSingleValue($maturity); $frequency = Functions::flattenSingleValue($frequency); $basis = ($basis === null) ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD : Functions::flattenSingleValue($basis); try { $settlement = FinancialValidations::validateSettlementDate($settlement); $maturity = FinancialValidations::validateMaturityDate($maturity); self::validateCouponPeriod($settlement, $maturity); $frequency = FinancialValidations::validateFrequency($frequency); FinancialValidations::validateBasis($basis); } catch (Exception $e) { return $e->getMessage(); } return self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_PREVIOUS); } private static function monthsDiff(DateTime $result, int $months, string $plusOrMinus, int $day, bool $lastDayFlag): void { $result->setDate((int) $result->format('Y'), (int) $result->format('m'), 1); $result->modify("$plusOrMinus $months months"); $daysInMonth = (int) $result->format('t'); $result->setDate((int) $result->format('Y'), (int) $result->format('m'), $lastDayFlag ? $daysInMonth : min($day, $daysInMonth)); } private static function couponFirstPeriodDate(float $settlement, float $maturity, int $frequency, bool $next): float { $months = 12 / $frequency; $result = Date::excelToDateTimeObject($maturity); $day = (int) $result->format('d'); $lastDayFlag = Helpers::isLastDayOfMonth($result); while ($settlement < Date::PHPToExcel($result)) { self::monthsDiff($result, $months, '-', $day, $lastDayFlag); } if ($next === true) { self::monthsDiff($result, $months, '+', $day, $lastDayFlag); } return (float) Date::PHPToExcel($result); } private static function validateCouponPeriod(float $settlement, float $maturity): void { if ($settlement >= $maturity) { throw new Exception(ExcelError::NAN()); } } } PK!Mk@libraries/vendor/PhpSpreadsheet/Calculation/Financial/Dollar.phpnu[getMessage(); } // Additional parameter validations if ($fraction < 0) { return ExcelError::NAN(); } if ($fraction == 0) { return ExcelError::DIV0(); } $dollars = ($fractionalDollar < 0) ? ceil($fractionalDollar) : floor($fractionalDollar); $cents = fmod($fractionalDollar, 1.0); $cents /= $fraction; $cents *= 10 ** ceil(log10($fraction)); return $dollars + $cents; } /** * DOLLARFR. * * Converts a dollar price expressed as a decimal number into a dollar price * expressed as a fraction. * Fractional dollar numbers are sometimes used for security prices. * * Excel Function: * DOLLARFR(decimal_dollar,fraction) * * @param mixed $decimalDollar Decimal Dollar * Or can be an array of values * @param mixed $fraction Fraction * Or can be an array of values * @return mixed[]|float|string */ public static function fractional($decimalDollar = null, $fraction = 0) { if (is_array($decimalDollar) || is_array($fraction)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $decimalDollar, $fraction); } try { $decimalDollar = FinancialValidations::validateFloat( Functions::flattenSingleValue($decimalDollar) ?? 0.0 ); $fraction = FinancialValidations::validateInt(Functions::flattenSingleValue($fraction)); } catch (Exception $e) { return $e->getMessage(); } // Additional parameter validations if ($fraction < 0) { return ExcelError::NAN(); } if ($fraction == 0) { return ExcelError::DIV0(); } $dollars = ($decimalDollar < 0.0) ? ceil($decimalDollar) : floor($decimalDollar); $cents = fmod($decimalDollar, 1); $cents *= $fraction; $cents *= 10 ** (-ceil(log10($fraction))); return $dollars + $cents; } } PK! Nlibraries/vendor/PhpSpreadsheet/Calculation/Financial/FinancialValidations.phpnu[ 4)) { throw new Exception(ExcelError::NAN()); } return $basis; } /** * @param mixed $price */ public static function validatePrice($price): float { $price = self::validateFloat($price); if ($price < 0.0) { throw new Exception(ExcelError::NAN()); } return $price; } /** * @param mixed $parValue */ public static function validateParValue($parValue): float { $parValue = self::validateFloat($parValue); if ($parValue < 0.0) { throw new Exception(ExcelError::NAN()); } return $parValue; } /** * @param mixed $yield */ public static function validateYield($yield): float { $yield = self::validateFloat($yield); if ($yield < 0.0) { throw new Exception(ExcelError::NAN()); } return $yield; } /** * @param mixed $discount */ public static function validateDiscount($discount): float { $discount = self::validateFloat($discount); if ($discount <= 0.0) { throw new Exception(ExcelError::NAN()); } return $discount; } } PK!Zb3==Flibraries/vendor/PhpSpreadsheet/Calculation/Financial/TreasuryBill.phpnu[getMessage(); } if ($discount <= 0) { return ExcelError::NAN(); } $daysBetweenSettlementAndMaturity = $maturity - $settlement; $daysPerYear = Helpers::daysPerYear( Functions::scalar(DateTimeExcel\DateParts::year($maturity)), FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL ); if ($daysBetweenSettlementAndMaturity > $daysPerYear || $daysBetweenSettlementAndMaturity < 0) { return ExcelError::NAN(); } return (365 * $discount) / (360 - $discount * $daysBetweenSettlementAndMaturity); } /** * TBILLPRICE. * * Returns the price per $100 face value for a Treasury bill. * * @param mixed $settlement The Treasury bill's settlement date. * The Treasury bill's settlement date is the date after the issue date * when the Treasury bill is traded to the buyer. * @param mixed $maturity The Treasury bill's maturity date. * The maturity date is the date when the Treasury bill expires. * @param mixed $discount The Treasury bill's discount rate * * @return float|string Result, or a string containing an error */ public static function price($settlement, $maturity, $discount) { $settlement = Functions::flattenSingleValue($settlement); $maturity = Functions::flattenSingleValue($maturity); $discount = Functions::flattenSingleValue($discount); try { $settlement = FinancialValidations::validateSettlementDate($settlement); $maturity = FinancialValidations::validateMaturityDate($maturity); $discount = FinancialValidations::validateFloat($discount); } catch (Exception $e) { return $e->getMessage(); } if ($discount <= 0) { return ExcelError::NAN(); } $daysBetweenSettlementAndMaturity = $maturity - $settlement; $daysPerYear = Helpers::daysPerYear( Functions::scalar(DateTimeExcel\DateParts::year($maturity)), FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL ); if ($daysBetweenSettlementAndMaturity > $daysPerYear || $daysBetweenSettlementAndMaturity < 0) { return ExcelError::NAN(); } $price = 100 * (1 - (($discount * $daysBetweenSettlementAndMaturity) / 360)); if ($price < 0.0) { return ExcelError::NAN(); } return $price; } /** * TBILLYIELD. * * Returns the yield for a Treasury bill. * * @param mixed $settlement The Treasury bill's settlement date. * The Treasury bill's settlement date is the date after the issue date when * the Treasury bill is traded to the buyer. * @param mixed $maturity The Treasury bill's maturity date. * The maturity date is the date when the Treasury bill expires. * @param float|string $price The Treasury bill's price per $100 face value * @return float|string */ public static function yield($settlement, $maturity, $price) { $settlement = Functions::flattenSingleValue($settlement); $maturity = Functions::flattenSingleValue($maturity); $price = Functions::flattenSingleValue($price); try { $settlement = FinancialValidations::validateSettlementDate($settlement); $maturity = FinancialValidations::validateMaturityDate($maturity); $price = FinancialValidations::validatePrice($price); } catch (Exception $e) { return $e->getMessage(); } $daysBetweenSettlementAndMaturity = $maturity - $settlement; $daysPerYear = Helpers::daysPerYear( Functions::scalar(DateTimeExcel\DateParts::year($maturity)), FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL ); if ($daysBetweenSettlementAndMaturity > $daysPerYear || $daysBetweenSettlementAndMaturity < 0) { return ExcelError::NAN(); } return ((100 - $price) / $price) * (360 / $daysBetweenSettlementAndMaturity); } } PK!( Flibraries/vendor/PhpSpreadsheet/Calculation/Financial/Amortization.phpnu[getMessage(); } $yearFracx = DateTimeExcel\YearFrac::fraction($purchased, $firstPeriod, $basis); if (is_string($yearFracx)) { return $yearFracx; } /** @var float $yearFrac */ $yearFrac = $yearFracx; $amortiseCoeff = self::getAmortizationCoefficient($rate); $rate *= $amortiseCoeff; $rate = (float) (string) $rate; // ugly way to avoid rounding problem $fNRate = round($yearFrac * $rate * $cost, 0); $cost -= $fNRate; $fRest = $cost - $salvage; for ($n = 0; $n < $period; ++$n) { $fNRate = round($rate * $cost, 0); $fRest -= $fNRate; if ($fRest < 0.0) { switch ($period - $n) { case 1: return round($cost * 0.5, 0); default: return 0.0; } } $cost -= $fNRate; } return $fNRate; } /** * AMORLINC. * * Returns the depreciation for each accounting period. * This function is provided for the French accounting system. If an asset is purchased in * the middle of the accounting period, the prorated depreciation is taken into account. * * Excel Function: * AMORLINC(cost,purchased,firstPeriod,salvage,period,rate[,basis]) * * @param mixed $cost The cost of the asset as a float * @param mixed $purchased Date of the purchase of the asset * @param mixed $firstPeriod Date of the end of the first period * @param mixed $salvage The salvage value at the end of the life of the asset * @param mixed $period The period as a float * @param mixed $rate Rate of depreciation as float * @param mixed $basis Integer indicating the type of day count to use. * 0 or omitted US (NASD) 30/360 * 1 Actual/actual * 2 Actual/360 * 3 Actual/365 * 4 European 30/360 * * @return float|string (string containing the error type if there is an error) */ public static function AMORLINC( $cost, $purchased, $firstPeriod, $salvage, $period, $rate, $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD ) { $cost = Functions::flattenSingleValue($cost); $purchased = Functions::flattenSingleValue($purchased); $firstPeriod = Functions::flattenSingleValue($firstPeriod); $salvage = Functions::flattenSingleValue($salvage); $period = Functions::flattenSingleValue($period); $rate = Functions::flattenSingleValue($rate); $basis = ($basis === null) ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD : Functions::flattenSingleValue($basis); try { $cost = FinancialValidations::validateFloat($cost); $purchased = FinancialValidations::validateDate($purchased); $firstPeriod = FinancialValidations::validateDate($firstPeriod); $salvage = FinancialValidations::validateFloat($salvage); $period = FinancialValidations::validateFloat($period); $rate = FinancialValidations::validateFloat($rate); $basis = FinancialValidations::validateBasis($basis); } catch (Exception $e) { return $e->getMessage(); } $fOneRate = $cost * $rate; $fCostDelta = $cost - $salvage; // Note, quirky variation for leap years on the YEARFRAC for this function $purchasedYear = DateTimeExcel\DateParts::year($purchased); $yearFracx = DateTimeExcel\YearFrac::fraction($purchased, $firstPeriod, $basis); if (is_string($yearFracx)) { return $yearFracx; } /** @var float $yearFrac */ $yearFrac = $yearFracx; if ( $basis == FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL && $yearFrac < 1 ) { $temp = Functions::scalar($purchasedYear); if (is_int($temp) || is_string($temp)) { if (DateTimeExcel\Helpers::isLeapYear($temp)) { $yearFrac *= 365 / 366; } } } $f0Rate = $yearFrac * $rate * $cost; $nNumOfFullPeriods = (int) (($cost - $salvage - $f0Rate) / $fOneRate); if ($period == 0) { return $f0Rate; } elseif ($period <= $nNumOfFullPeriods) { return $fOneRate; } elseif ($period == ($nNumOfFullPeriods + 1)) { return $fCostDelta - $fOneRate * $nNumOfFullPeriods - $f0Rate; } return 0.0; } private static function getAmortizationCoefficient(float $rate): float { // The depreciation coefficients are: // Life of assets (1/rate) Depreciation coefficient // Less than 3 years 1 // Between 3 and 4 years 1.5 // Between 5 and 6 years 2 // More than 6 years 2.5 $fUsePer = 1.0 / $rate; if ($fUsePer < 3.0) { return 1.0; } elseif ($fUsePer < 4.0) { return 1.5; } elseif ($fUsePer <= 6.0) { return 2.0; } return 2.5; } } PK!xFlibraries/vendor/PhpSpreadsheet/Calculation/Financial/InterestRate.phpnu[getMessage(); } if ($nominalRate <= 0 || $periodsPerYear < 1) { return ExcelError::NAN(); } return ((1 + $nominalRate / $periodsPerYear) ** $periodsPerYear) - 1; } /** * NOMINAL. * * Returns the nominal interest rate given the effective rate and the number of compounding payments per year. * * @param mixed $effectiveRate Effective interest rate as a float * @param mixed $periodsPerYear Integer number of compounding payments per year * * @return float|string Result, or a string containing an error */ public static function nominal($effectiveRate = 0, $periodsPerYear = 0) { $effectiveRate = Functions::flattenSingleValue($effectiveRate); $periodsPerYear = Functions::flattenSingleValue($periodsPerYear); try { $effectiveRate = FinancialValidations::validateFloat($effectiveRate); $periodsPerYear = FinancialValidations::validateInt($periodsPerYear); } catch (Exception $e) { return $e->getMessage(); } if ($effectiveRate <= 0 || $periodsPerYear < 1) { return ExcelError::NAN(); } // Calculate return $periodsPerYear * (($effectiveRate + 1) ** (1 / $periodsPerYear) - 1); } } PK!?libraries/vendor/PhpSpreadsheet/Calculation/Financial/index.phpnu[getMessage(); } if ($cost === self::$zeroPointZero) { return 0.0; } // Set Fixed Depreciation Rate $fixedDepreciationRate = 1 - ($salvage / $cost) ** (1 / $life); $fixedDepreciationRate = round($fixedDepreciationRate, 3); // Loop through each period calculating the depreciation // TODO Handle period value between 0 and 1 (e.g. 0.5) $previousDepreciation = 0; $depreciation = 0; for ($per = 1; $per <= $period; ++$per) { if ($per == 1) { $depreciation = $cost * $fixedDepreciationRate * $month / 12; } elseif ($per == ($life + 1)) { $depreciation = ($cost - $previousDepreciation) * $fixedDepreciationRate * (12 - $month) / 12; } else { $depreciation = ($cost - $previousDepreciation) * $fixedDepreciationRate; } $previousDepreciation += $depreciation; } return $depreciation; } /** * DDB. * * Returns the depreciation of an asset for a specified period using the * double-declining balance method or some other method you specify. * * Excel Function: * DDB(cost,salvage,life,period[,factor]) * * @param mixed $cost Initial cost of the asset * @param mixed $salvage Value at the end of the depreciation. * (Sometimes called the salvage value of the asset) * @param mixed $life Number of periods over which the asset is depreciated. * (Sometimes called the useful life of the asset) * @param mixed $period The period for which you want to calculate the * depreciation. Period must use the same units as life. * @param mixed $factor The rate at which the balance declines. * If factor is omitted, it is assumed to be 2 (the * double-declining balance method). * @return float|string */ public static function DDB($cost, $salvage, $life, $period, $factor = 2.0) { $cost = Functions::flattenSingleValue($cost); $salvage = Functions::flattenSingleValue($salvage); $life = Functions::flattenSingleValue($life); $period = Functions::flattenSingleValue($period); $factor = Functions::flattenSingleValue($factor); try { $cost = self::validateCost($cost); $salvage = self::validateSalvage($salvage); $life = self::validateLife($life); $period = self::validatePeriod($period); $factor = self::validateFactor($factor); } catch (Exception $e) { return $e->getMessage(); } if ($period > $life) { return ExcelError::NAN(); } // Loop through each period calculating the depreciation // TODO Handling for fractional $period values $previousDepreciation = 0; $depreciation = 0; for ($per = 1; $per <= $period; ++$per) { $depreciation = min( ($cost - $previousDepreciation) * ($factor / $life), ($cost - $salvage - $previousDepreciation) ); $previousDepreciation += $depreciation; } return $depreciation; } /** * SLN. * * Returns the straight-line depreciation of an asset for one period * * @param mixed $cost Initial cost of the asset * @param mixed $salvage Value at the end of the depreciation * @param mixed $life Number of periods over which the asset is depreciated * * @return float|string Result, or a string containing an error */ public static function SLN($cost, $salvage, $life) { $cost = Functions::flattenSingleValue($cost); $salvage = Functions::flattenSingleValue($salvage); $life = Functions::flattenSingleValue($life); try { $cost = self::validateCost($cost, true); $salvage = self::validateSalvage($salvage, true); $life = self::validateLife($life, true); } catch (Exception $e) { return $e->getMessage(); } if ($life === self::$zeroPointZero) { return ExcelError::DIV0(); } return ($cost - $salvage) / $life; } /** * SYD. * * Returns the sum-of-years' digits depreciation of an asset for a specified period. * * @param mixed $cost Initial cost of the asset * @param mixed $salvage Value at the end of the depreciation * @param mixed $life Number of periods over which the asset is depreciated * @param mixed $period Period * * @return float|string Result, or a string containing an error */ public static function SYD($cost, $salvage, $life, $period) { $cost = Functions::flattenSingleValue($cost); $salvage = Functions::flattenSingleValue($salvage); $life = Functions::flattenSingleValue($life); $period = Functions::flattenSingleValue($period); try { $cost = self::validateCost($cost, true); $salvage = self::validateSalvage($salvage); $life = self::validateLife($life); $period = self::validatePeriod($period); } catch (Exception $e) { return $e->getMessage(); } if ($period > $life) { return ExcelError::NAN(); } $syd = (($cost - $salvage) * ($life - $period + 1) * 2) / ($life * ($life + 1)); return $syd; } /** * @param mixed $cost */ private static function validateCost($cost, bool $negativeValueAllowed = false): float { $cost = FinancialValidations::validateFloat($cost); if ($cost < 0.0 && $negativeValueAllowed === false) { throw new Exception(ExcelError::NAN()); } return $cost; } /** * @param mixed $salvage */ private static function validateSalvage($salvage, bool $negativeValueAllowed = false): float { $salvage = FinancialValidations::validateFloat($salvage); if ($salvage < 0.0 && $negativeValueAllowed === false) { throw new Exception(ExcelError::NAN()); } return $salvage; } /** * @param mixed $life */ private static function validateLife($life, bool $negativeValueAllowed = false): float { $life = FinancialValidations::validateFloat($life); if ($life < 0.0 && $negativeValueAllowed === false) { throw new Exception(ExcelError::NAN()); } return $life; } /** * @param mixed $period */ private static function validatePeriod($period, bool $negativeValueAllowed = false): float { $period = FinancialValidations::validateFloat($period); if ($period <= 0.0 && $negativeValueAllowed === false) { throw new Exception(ExcelError::NAN()); } return $period; } /** * @param mixed $month */ private static function validateMonth($month): int { $month = FinancialValidations::validateInt($month); if ($month < 1) { throw new Exception(ExcelError::NAN()); } return $month; } /** * @param mixed $factor */ private static function validateFactor($factor): float { $factor = FinancialValidations::validateFloat($factor); if ($factor <= 0.0) { throw new Exception(ExcelError::NAN()); } return $factor; } } PK!Z]Clibraries/vendor/PhpSpreadsheet/Calculation/Financial/Constants.phpnu[format('d') === $date->format('t'); } } PK!usODDKlibraries/vendor/PhpSpreadsheet/Calculation/Financial/Securities/Yields.phpnu[getMessage(); } $daysPerYear = Helpers::daysPerYear(Functions::scalar(DateTimeExcel\DateParts::year($settlement)), $basis); if (!is_numeric($daysPerYear)) { return $daysPerYear; } $daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis)); if (!is_numeric($daysBetweenSettlementAndMaturity)) { // return date error return StringHelper::convertToString($daysBetweenSettlementAndMaturity); } $daysBetweenSettlementAndMaturity *= $daysPerYear; return (($redemption - $price) / $price) * ($daysPerYear / $daysBetweenSettlementAndMaturity); } /** * YIELDMAT. * * Returns the annual yield of a security that pays interest at maturity. * * @param mixed $settlement The security's settlement date. * The security's settlement date is the date after the issue date when the security * is traded to the buyer. * @param mixed $maturity The security's maturity date. * The maturity date is the date when the security expires. * @param mixed $issue The security's issue date * @param mixed $rate The security's interest rate at date of issue * @param mixed $price The security's price per $100 face value * @param mixed $basis The type of day count to use. * 0 or omitted US (NASD) 30/360 * 1 Actual/actual * 2 Actual/360 * 3 Actual/365 * 4 European 30/360 * * @return float|string Result, or a string containing an error */ public static function yieldAtMaturity( $settlement, $maturity, $issue, $rate, $price, $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD ) { $settlement = Functions::flattenSingleValue($settlement); $maturity = Functions::flattenSingleValue($maturity); $issue = Functions::flattenSingleValue($issue); $rate = Functions::flattenSingleValue($rate); $price = Functions::flattenSingleValue($price); $basis = ($basis === null) ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD : Functions::flattenSingleValue($basis); try { $settlement = SecurityValidations::validateSettlementDate($settlement); $maturity = SecurityValidations::validateMaturityDate($maturity); SecurityValidations::validateSecurityPeriod($settlement, $maturity); $issue = SecurityValidations::validateIssueDate($issue); $rate = SecurityValidations::validateRate($rate); $price = SecurityValidations::validatePrice($price); $basis = SecurityValidations::validateBasis($basis); } catch (Exception $e) { return $e->getMessage(); } $daysPerYear = Helpers::daysPerYear(Functions::scalar(DateTimeExcel\DateParts::year($settlement)), $basis); if (!is_numeric($daysPerYear)) { return $daysPerYear; } $daysBetweenIssueAndSettlement = Functions::scalar(DateTimeExcel\YearFrac::fraction($issue, $settlement, $basis)); if (!is_numeric($daysBetweenIssueAndSettlement)) { // return date error return StringHelper::convertToString($daysBetweenIssueAndSettlement); } $daysBetweenIssueAndSettlement *= $daysPerYear; $daysBetweenIssueAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($issue, $maturity, $basis)); if (!is_numeric($daysBetweenIssueAndMaturity)) { // return date error return StringHelper::convertToString($daysBetweenIssueAndMaturity); } $daysBetweenIssueAndMaturity *= $daysPerYear; $daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis)); if (!is_numeric($daysBetweenSettlementAndMaturity)) { // return date error return StringHelper::convertToString($daysBetweenSettlementAndMaturity); } $daysBetweenSettlementAndMaturity *= $daysPerYear; return ((1 + (($daysBetweenIssueAndMaturity / $daysPerYear) * $rate) - (($price / 100) + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate))) / (($price / 100) + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate))) * ($daysPerYear / $daysBetweenSettlementAndMaturity); } } PK!Jlibraries/vendor/PhpSpreadsheet/Calculation/Financial/Securities/index.phpnu[= $maturity) { throw new Exception(ExcelError::NAN()); } } /** * @param mixed $redemption */ public static function validateRedemption($redemption): float { $redemption = self::validateFloat($redemption); if ($redemption <= 0.0) { throw new Exception(ExcelError::NAN()); } return $redemption; } } PK!E//Jlibraries/vendor/PhpSpreadsheet/Calculation/Financial/Securities/Price.phpnu[getMessage(); } $dsc = (float) Coupons::COUPDAYSNC($settlement, $maturity, $frequency, $basis); $e = (float) Coupons::COUPDAYS($settlement, $maturity, $frequency, $basis); $n = (int) Coupons::COUPNUM($settlement, $maturity, $frequency, $basis); $a = (float) Coupons::COUPDAYBS($settlement, $maturity, $frequency, $basis); $baseYF = 1.0 + ($yield / $frequency); $rfp = 100 * ($rate / $frequency); $de = $dsc / $e; $result = $redemption / $baseYF ** (--$n + $de); for ($k = 0; $k <= $n; ++$k) { $result += $rfp / ($baseYF ** ($k + $de)); } $result -= $rfp * ($a / $e); return $result; } /** * PRICEDISC. * * Returns the price per $100 face value of a discounted security. * * @param mixed $settlement The security's settlement date. * The security settlement date is the date after the issue date when the security * is traded to the buyer. * @param mixed $maturity The security's maturity date. * The maturity date is the date when the security expires. * @param mixed $discount The security's discount rate * @param mixed $redemption The security's redemption value per $100 face value * @param mixed $basis The type of day count to use. * 0 or omitted US (NASD) 30/360 * 1 Actual/actual * 2 Actual/360 * 3 Actual/365 * 4 European 30/360 * * @return float|string Result, or a string containing an error */ public static function priceDiscounted( $settlement, $maturity, $discount, $redemption, $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD ) { $settlement = Functions::flattenSingleValue($settlement); $maturity = Functions::flattenSingleValue($maturity); $discount = Functions::flattenSingleValue($discount); $redemption = Functions::flattenSingleValue($redemption); $basis = ($basis === null) ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD : Functions::flattenSingleValue($basis); try { $settlement = SecurityValidations::validateSettlementDate($settlement); $maturity = SecurityValidations::validateMaturityDate($maturity); SecurityValidations::validateSecurityPeriod($settlement, $maturity); $discount = SecurityValidations::validateDiscount($discount); $redemption = SecurityValidations::validateRedemption($redemption); $basis = SecurityValidations::validateBasis($basis); } catch (Exception $e) { return $e->getMessage(); } $daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis)); if (!is_numeric($daysBetweenSettlementAndMaturity)) { // return date error return StringHelper::convertToString($daysBetweenSettlementAndMaturity); } return $redemption * (1 - $discount * $daysBetweenSettlementAndMaturity); } /** * PRICEMAT. * * Returns the price per $100 face value of a security that pays interest at maturity. * * @param mixed $settlement The security's settlement date. * The security's settlement date is the date after the issue date when the * security is traded to the buyer. * @param mixed $maturity The security's maturity date. * The maturity date is the date when the security expires. * @param mixed $issue The security's issue date * @param mixed $rate The security's interest rate at date of issue * @param mixed $yield The security's annual yield * @param mixed $basis The type of day count to use. * 0 or omitted US (NASD) 30/360 * 1 Actual/actual * 2 Actual/360 * 3 Actual/365 * 4 European 30/360 * * @return float|string Result, or a string containing an error */ public static function priceAtMaturity( $settlement, $maturity, $issue, $rate, $yield, $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD ) { $settlement = Functions::flattenSingleValue($settlement); $maturity = Functions::flattenSingleValue($maturity); $issue = Functions::flattenSingleValue($issue); $rate = Functions::flattenSingleValue($rate); $yield = Functions::flattenSingleValue($yield); $basis = ($basis === null) ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD : Functions::flattenSingleValue($basis); try { $settlement = SecurityValidations::validateSettlementDate($settlement); $maturity = SecurityValidations::validateMaturityDate($maturity); SecurityValidations::validateSecurityPeriod($settlement, $maturity); $issue = SecurityValidations::validateIssueDate($issue); $rate = SecurityValidations::validateRate($rate); $yield = SecurityValidations::validateYield($yield); $basis = SecurityValidations::validateBasis($basis); } catch (Exception $e) { return $e->getMessage(); } $daysPerYear = Helpers::daysPerYear(Functions::scalar(DateTimeExcel\DateParts::year($settlement)), $basis); if (!is_numeric($daysPerYear)) { return $daysPerYear; } $daysBetweenIssueAndSettlement = Functions::scalar(DateTimeExcel\YearFrac::fraction($issue, $settlement, $basis)); if (!is_numeric($daysBetweenIssueAndSettlement)) { // return date error return StringHelper::convertToString($daysBetweenIssueAndSettlement); } $daysBetweenIssueAndSettlement *= $daysPerYear; $daysBetweenIssueAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($issue, $maturity, $basis)); if (!is_numeric($daysBetweenIssueAndMaturity)) { // return date error return StringHelper::convertToString($daysBetweenIssueAndMaturity); } $daysBetweenIssueAndMaturity *= $daysPerYear; $daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis)); if (!is_numeric($daysBetweenSettlementAndMaturity)) { // return date error return StringHelper::convertToString($daysBetweenSettlementAndMaturity); } $daysBetweenSettlementAndMaturity *= $daysPerYear; return (100 + (($daysBetweenIssueAndMaturity / $daysPerYear) * $rate * 100)) / (1 + (($daysBetweenSettlementAndMaturity / $daysPerYear) * $yield)) - (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate * 100); } /** * RECEIVED. * * Returns the amount received at maturity for a fully invested Security. * * @param mixed $settlement The security's settlement date. * The security settlement date is the date after the issue date when the security * is traded to the buyer. * @param mixed $maturity The security's maturity date. * The maturity date is the date when the security expires. * @param mixed $investment The amount invested in the security * @param mixed $discount The security's discount rate * @param mixed $basis The type of day count to use. * 0 or omitted US (NASD) 30/360 * 1 Actual/actual * 2 Actual/360 * 3 Actual/365 * 4 European 30/360 * * @return float|string Result, or a string containing an error */ public static function received( $settlement, $maturity, $investment, $discount, $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD ) { $settlement = Functions::flattenSingleValue($settlement); $maturity = Functions::flattenSingleValue($maturity); $investment = Functions::flattenSingleValue($investment); $discount = Functions::flattenSingleValue($discount); $basis = ($basis === null) ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD : Functions::flattenSingleValue($basis); try { $settlement = SecurityValidations::validateSettlementDate($settlement); $maturity = SecurityValidations::validateMaturityDate($maturity); SecurityValidations::validateSecurityPeriod($settlement, $maturity); $investment = SecurityValidations::validateFloat($investment); $discount = SecurityValidations::validateDiscount($discount); $basis = SecurityValidations::validateBasis($basis); } catch (Exception $e) { return $e->getMessage(); } if ($investment <= 0) { return ExcelError::NAN(); } $daysBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis); if (!is_numeric($daysBetweenSettlementAndMaturity)) { // return date error return StringHelper::convertToString(Functions::scalar($daysBetweenSettlementAndMaturity)); } return $investment / (1 - ($discount * $daysBetweenSettlementAndMaturity)); } } PK!Jlibraries/vendor/PhpSpreadsheet/Calculation/Financial/Securities/Rates.phpnu[getMessage(); } if ($price <= 0.0) { return ExcelError::NAN(); } $daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis)); if (!is_numeric($daysBetweenSettlementAndMaturity)) { // return date error return StringHelper::convertToString($daysBetweenSettlementAndMaturity); } return (1 - $price / $redemption) / $daysBetweenSettlementAndMaturity; } /** * INTRATE. * * Returns the interest rate for a fully invested security. * * Excel Function: * INTRATE(settlement,maturity,investment,redemption[,basis]) * * @param mixed $settlement The security's settlement date. * The security settlement date is the date after the issue date when the security * is traded to the buyer. * @param mixed $maturity The security's maturity date. * The maturity date is the date when the security expires. * @param mixed $investment the amount invested in the security * @param mixed $redemption the amount to be received at maturity * @param mixed $basis The type of day count to use. * 0 or omitted US (NASD) 30/360 * 1 Actual/actual * 2 Actual/360 * 3 Actual/365 * 4 European 30/360 * @return float|string */ public static function interest( $settlement, $maturity, $investment, $redemption, $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD ) { $settlement = Functions::flattenSingleValue($settlement); $maturity = Functions::flattenSingleValue($maturity); $investment = Functions::flattenSingleValue($investment); $redemption = Functions::flattenSingleValue($redemption); $basis = ($basis === null) ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD : Functions::flattenSingleValue($basis); try { $settlement = SecurityValidations::validateSettlementDate($settlement); $maturity = SecurityValidations::validateMaturityDate($maturity); SecurityValidations::validateSecurityPeriod($settlement, $maturity); $investment = SecurityValidations::validateFloat($investment); $redemption = SecurityValidations::validateRedemption($redemption); $basis = SecurityValidations::validateBasis($basis); } catch (Exception $e) { return $e->getMessage(); } if ($investment <= 0) { return ExcelError::NAN(); } $daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis)); if (!is_numeric($daysBetweenSettlementAndMaturity)) { // return date error return StringHelper::convertToString($daysBetweenSettlementAndMaturity); } return (($redemption / $investment) - 1) / ($daysBetweenSettlementAndMaturity); } } PK!{Tlibraries/vendor/PhpSpreadsheet/Calculation/Financial/Securities/AccruedInterest.phpnu[getMessage(); } $daysBetweenIssueAndSettlement = Functions::scalar(YearFrac::fraction($issue, $settlement, $basis)); if (!is_numeric($daysBetweenIssueAndSettlement)) { // return date error return StringHelper::convertToString($daysBetweenIssueAndSettlement); } $daysBetweenFirstInterestAndSettlement = Functions::scalar(YearFrac::fraction($firstInterest, $settlement, $basis)); if (!is_numeric($daysBetweenFirstInterestAndSettlement)) { // return date error return StringHelper::convertToString($daysBetweenFirstInterestAndSettlement); } return $parValue * $rate * $daysBetweenIssueAndSettlement; } /** * ACCRINTM. * * Returns the accrued interest for a security that pays interest at maturity. * * Excel Function: * ACCRINTM(issue,settlement,rate[,par[,basis]]) * * @param mixed $issue The security's issue date * @param mixed $settlement The security's settlement (or maturity) date * @param mixed $rate The security's annual coupon rate * @param mixed $parValue The security's par value. * If you omit parValue, ACCRINT uses $1,000. * @param mixed $basis The type of day count to use. * 0 or omitted US (NASD) 30/360 * 1 Actual/actual * 2 Actual/360 * 3 Actual/365 * 4 European 30/360 * * @return float|string Result, or a string containing an error */ public static function atMaturity( $issue, $settlement, $rate, $parValue = 1000, $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD ) { $issue = Functions::flattenSingleValue($issue); $settlement = Functions::flattenSingleValue($settlement); $rate = Functions::flattenSingleValue($rate); $parValue = ($parValue === null) ? 1000 : Functions::flattenSingleValue($parValue); $basis = ($basis === null) ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD : Functions::flattenSingleValue($basis); try { $issue = SecurityValidations::validateIssueDate($issue); $settlement = SecurityValidations::validateSettlementDate($settlement); SecurityValidations::validateSecurityPeriod($issue, $settlement); $rate = SecurityValidations::validateRate($rate); $parValue = SecurityValidations::validateParValue($parValue); $basis = SecurityValidations::validateBasis($basis); } catch (Exception $e) { return $e->getMessage(); } $daysBetweenIssueAndSettlement = Functions::scalar(YearFrac::fraction($issue, $settlement, $basis)); if (!is_numeric($daysBetweenIssueAndSettlement)) { // return date error return StringHelper::convertToString($daysBetweenIssueAndSettlement); } return $parValue * $rate * $daysBetweenIssueAndSettlement; } } PK!,+~) ) <libraries/vendor/PhpSpreadsheet/Calculation/MathTrig/Sum.phpnu[ $arg) { // Is it a numeric value? if (is_numeric($arg)) { $returnValue += $arg; } elseif (is_bool($arg)) { $returnValue += (int) $arg; } elseif (ErrorValue::isError($arg)) { return $arg; } elseif ($arg !== null && !Functions::isCellValue($k)) { // ignore non-numerics from cell, but fail as literals (except null) return ExcelError::VALUE(); } } return $returnValue; } /** * SUMPRODUCT. * * Excel Function: * SUMPRODUCT(value1[,value2[, ...]]) * * @param mixed ...$args Data values * * @return float|int|string The result, or a string containing an error */ public static function product(...$args) { $arrayList = $args; $wrkArray = Functions::flattenArray(array_shift($arrayList)); $wrkCellCount = count($wrkArray); for ($i = 0; $i < $wrkCellCount; ++$i) { if ((!is_numeric($wrkArray[$i])) || (is_string($wrkArray[$i]))) { $wrkArray[$i] = 0; } } foreach ($arrayList as $matrixData) { $array2 = Functions::flattenArray($matrixData); $count = count($array2); if ($wrkCellCount != $count) { return ExcelError::VALUE(); } foreach ($array2 as $i => $val) { if ((!is_numeric($val)) || (is_string($val))) { $val = 0; } $wrkArray[$i] *= $val; } } return array_sum($wrkArray); } } PK!o-@libraries/vendor/PhpSpreadsheet/Calculation/MathTrig/Ceiling.phpnu[getMessage(); } return self::argumentsOk((float) $number, (float) $significance); } /** * CEILING.MATH. * * Round a number down to the nearest integer or to the nearest multiple of significance. * * Excel Function: * CEILING.MATH(number[,significance[,mode]]) * * @param mixed $number Number to round * Or can be an array of values * @param mixed $significance Significance * Or can be an array of values * @param array|int $mode direction to round negative numbers * Or can be an array of values * * @return array|float|string Rounded Number, or a string containing an error * If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function math($number, $significance = null, $mode = 0) { if (is_array($number) || is_array($significance) || is_array($mode)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $significance, $mode); } try { $number = Helpers::validateNumericNullBool($number); $significance = Helpers::validateNumericNullSubstitution($significance, ($number < 0) ? -1 : 1); $mode = Helpers::validateNumericNullSubstitution($mode, null); } catch (Exception $e) { return $e->getMessage(); } if (empty($significance * $number)) { return 0.0; } if (self::ceilingMathTest((float) $significance, (float) $number, (int) $mode)) { return floor($number / $significance) * $significance; } return ceil($number / $significance) * $significance; } /** * CEILING.PRECISE. * * Rounds number up, away from zero, to the nearest multiple of significance. * * Excel Function: * CEILING.PRECISE(number[,significance]) * * @param mixed $number the number you want to round * Or can be an array of values * @param array|float $significance the multiple to which you want to round * Or can be an array of values * * @return array|float|string Rounded Number, or a string containing an error * If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function precise($number, $significance = 1) { if (is_array($number) || is_array($significance)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $significance); } try { $number = Helpers::validateNumericNullBool($number); $significance = Helpers::validateNumericNullSubstitution($significance, null); } catch (Exception $e) { return $e->getMessage(); } if (!$significance) { return 0.0; } $result = $number / abs($significance); return ceil($result) * $significance * (($significance < 0) ? -1 : 1); } /** * Let CEILINGMATH complexity pass Scrutinizer. */ private static function ceilingMathTest(float $significance, float $number, int $mode): bool { return ($significance < 0) || ($number < 0 && !empty($mode)); } /** * Avoid Scrutinizer problems concerning complexity. * @return float|string */ private static function argumentsOk(float $number, float $significance) { if (empty($number * $significance)) { return 0.0; } if (Helpers::returnSign($number) == Helpers::returnSign($significance)) { return ceil($number / $significance) * $significance; } return ExcelError::NAN(); } private static function floorCheck1Arg(): void { $compatibility = Functions::getCompatibilityMode(); if ($compatibility === Functions::COMPATIBILITY_EXCEL) { throw new Exception('Excel requires 2 arguments for CEILING'); } } } PK! <libraries/vendor/PhpSpreadsheet/Calculation/MathTrig/Gcd.phpnu[getMessage(); } if (count($arrayArgs) <= 0) { return ExcelError::VALUE(); } $gcd = (int) array_pop($arrayArgs); do { $gcd = self::evaluateGCD($gcd, (int) array_pop($arrayArgs)); } while (!empty($arrayArgs)); return $gcd; } } PK!LzAlibraries/vendor/PhpSpreadsheet/Calculation/MathTrig/Subtotal.phpnu[getWorksheet()->getRowDimension((int) $row)->getVisible(); }, ARRAY_FILTER_USE_KEY ); } protected static function filterFormulaArgs(Cell $cellReference, array $args): array { return array_filter( $args, function ($index) use ($cellReference): bool { $explodeArray = explode('.', $index); $row = $explodeArray[1] ?? ''; $column = $explodeArray[2] ?? ''; $retVal = true; if ($cellReference->getWorksheet()->cellExists($column . $row)) { //take this cell out if it contains the SUBTOTAL or AGGREGATE functions in a formula $isFormula = $cellReference->getWorksheet()->getCell($column . $row)->isFormula(); $cellFormula = !preg_match( '/^=.*\b(SUBTOTAL|AGGREGATE)\s*\(/i', $cellReference->getWorksheet()->getCell($column . $row)->getValueString() ); $retVal = !$isFormula || $cellFormula; } return $retVal; }, ARRAY_FILTER_USE_KEY ); } /** * @var array */ private const CALL_FUNCTIONS = [ 1 => [Statistical\Averages::class, 'average'], // 1 and 101 [Statistical\Counts::class, 'COUNT'], // 2 and 102 [Statistical\Counts::class, 'COUNTA'], // 3 and 103 [Statistical\Maximum::class, 'max'], // 4 and 104 [Statistical\Minimum::class, 'min'], // 5 and 105 [Operations::class, 'product'], // 6 and 106 [Statistical\StandardDeviations::class, 'STDEV'], // 7 and 107 [Statistical\StandardDeviations::class, 'STDEVP'], // 8 and 108 [Sum::class, 'sumIgnoringStrings'], // 9 and 109 [Statistical\Variances::class, 'VAR'], // 10 and 110 [Statistical\Variances::class, 'VARP'], // 111 and 111 ]; /** * SUBTOTAL. * * Returns a subtotal in a list or database. * * @param mixed $functionType * A number 1 to 11 that specifies which function to * use in calculating subtotals within a range * list * Numbers 101 to 111 shadow the functions of 1 to 11 * but ignore any values in the range that are * in hidden rows * @param mixed[] $args A mixed data series of values * @return float|int|string */ public static function evaluate($functionType, ...$args) { /** @var Cell */ $cellReference = array_pop($args); $bArgs = Functions::flattenArrayIndexed($args); $aArgs = []; // int keys must come before string keys for PHP 8.0+ // Otherwise, PHP thinks positional args follow keyword // in the subsequent call to call_user_func_array. // Fortunately, order of args is unimportant to Subtotal. foreach ($bArgs as $key => $value) { if (is_int($key)) { $aArgs[$key] = $value; } } foreach ($bArgs as $key => $value) { if (!is_int($key)) { $aArgs[$key] = $value; } } try { $subtotal = (int) Helpers::validateNumericNullBool($functionType); } catch (Exception $e) { return $e->getMessage(); } // Calculate if ($subtotal > 100) { $aArgs = self::filterHiddenArgs($cellReference, $aArgs); $subtotal -= 100; } $aArgs = self::filterFormulaArgs($cellReference, $aArgs); if (array_key_exists($subtotal, self::CALL_FUNCTIONS)) { $call = self::CALL_FUNCTIONS[$subtotal]; return call_user_func_array($call, $aArgs); //* @phpstan-ignore-line } return ExcelError::VALUE(); } } PK!{{>libraries/vendor/PhpSpreadsheet/Calculation/MathTrig/Round.phpnu[getMessage(); } return round($number, (int) $precision); } /** * ROUNDUP. * * Rounds a number up to a specified number of decimal places * * @param array|float $number Number to round, or can be an array of numbers * @param array|int $digits Number of digits to which you want to round $number, or can be an array of numbers * * @return array|float|string Rounded Number, or a string containing an error * If an array of numbers is passed as the argument, then the returned result will also be an array * with the same dimensions */ public static function up($number, $digits = 0) { if (is_array($number) || is_array($digits)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $digits); } try { $number = Helpers::validateNumericNullBool($number); $digits = (int) Helpers::validateNumericNullSubstitution($digits, null); } catch (Exception $e) { return $e->getMessage(); } if ($number == 0.0) { return 0.0; } if (PHP_VERSION_ID >= 80400) { return round( (float) (string) $number, $digits, RoundingMode::AwayFromZero //* @phpstan-ignore-line ); } if ($number < 0.0) { return round($number - 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_DOWN); } return round($number + 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_DOWN); } /** * ROUNDDOWN. * * Rounds a number down to a specified number of decimal places * * @param null|array|float|string $number Number to round, or can be an array of numbers * @param array|float|int|string $digits Number of digits to which you want to round $number, or can be an array of numbers * * @return array|float|string Rounded Number, or a string containing an error * If an array of numbers is passed as the argument, then the returned result will also be an array * with the same dimensions */ public static function down($number, $digits = 0) { if (is_array($number) || is_array($digits)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $digits); } try { $number = Helpers::validateNumericNullBool($number); $digits = (int) Helpers::validateNumericNullSubstitution($digits, null); } catch (Exception $e) { return $e->getMessage(); } if ($number == 0.0) { return 0.0; } if (PHP_VERSION_ID >= 80400) { return round( (float) (string) $number, $digits, RoundingMode::TowardsZero //* @phpstan-ignore-line ); } if ($number < 0.0) { return round($number + 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_UP); } return round($number - 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_UP); } /** * MROUND. * * Rounds a number to the nearest multiple of a specified value * * @param mixed $number Expect float. Number to round, or can be an array of numbers * @param mixed $multiple Expect int. Multiple to which you want to round, or can be an array of numbers. * * @return array|float|int|string Rounded Number, or a string containing an error * If an array of numbers is passed as the argument, then the returned result will also be an array * with the same dimensions */ public static function multiple($number, $multiple) { if (is_array($number) || is_array($multiple)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $multiple); } try { $number = Helpers::validateNumericNullSubstitution($number, 0); $multiple = Helpers::validateNumericNullSubstitution($multiple, null); } catch (Exception $e) { return $e->getMessage(); } if ($number == 0 || $multiple == 0) { return 0; } if ((Helpers::returnSign($number)) == (Helpers::returnSign($multiple))) { $multiplier = 1 / $multiple; return round($number * $multiplier) / $multiplier; } return ExcelError::NAN(); } /** * EVEN. * * Returns number rounded up to the nearest even integer. * You can use this function for processing items that come in twos. For example, * a packing crate accepts rows of one or two items. The crate is full when * the number of items, rounded up to the nearest two, matches the crate's * capacity. * * Excel Function: * EVEN(number) * * @param array|float $number Number to round, or can be an array of numbers * * @return array|float|string Rounded Number, or a string containing an error * If an array of numbers is passed as the argument, then the returned result will also be an array * with the same dimensions */ public static function even($number) { if (is_array($number)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number); } try { $number = Helpers::validateNumericNullBool($number); } catch (Exception $e) { return $e->getMessage(); } return Helpers::getEven($number); } /** * ODD. * * Returns number rounded up to the nearest odd integer. * * @param array|float $number Number to round, or can be an array of numbers * * @return array|float|int|string Rounded Number, or a string containing an error * If an array of numbers is passed as the argument, then the returned result will also be an array * with the same dimensions */ public static function odd($number) { if (is_array($number)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number); } try { $number = Helpers::validateNumericNullBool($number); } catch (Exception $e) { return $e->getMessage(); } $significance = Helpers::returnSign($number); if ($significance == 0) { return 1; } $result = ceil($number / $significance) * $significance; if ($result == Helpers::getEven($result)) { $result += $significance; } return $result; } } PK!Clibraries/vendor/PhpSpreadsheet/Calculation/MathTrig/Operations.phpnu[getMessage(); } if (($dividend < 0.0) && ($divisor > 0.0)) { return $divisor - fmod(abs($dividend), $divisor); } if (($dividend > 0.0) && ($divisor < 0.0)) { return $divisor + fmod($dividend, abs($divisor)); } return fmod($dividend, $divisor); } /** * POWER. * * Computes x raised to the power y. * * @param null|array|bool|float|int|string $x Or can be an array of values * @param null|array|bool|float|int|string $y Or can be an array of values * * @return array|float|int|string The result, or a string containing an error * If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function power($x, $y) { if (is_array($x) || is_array($y)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $x, $y); } try { $x = Helpers::validateNumericNullBool($x); $y = Helpers::validateNumericNullBool($y); } catch (Exception $e) { return $e->getMessage(); } // Validate parameters if (!$x && !$y) { return ExcelError::NAN(); } if (!$x && $y < 0.0) { return ExcelError::DIV0(); } // Return $result = $x ** $y; return Helpers::numberOrNan($result); } /** * PRODUCT. * * PRODUCT returns the product of all the values and cells referenced in the argument list. * * Excel Function: * PRODUCT(value1[,value2[, ...]]) * * @param mixed ...$args Data values * @return float|string */ public static function product(...$args) { $args = array_filter( Functions::flattenArray($args), fn ($value): bool => $value !== null ); // Return value $returnValue = (count($args) === 0) ? 0.0 : 1.0; // Loop through arguments foreach ($args as $arg) { // Is it a numeric value? if (is_numeric($arg)) { $returnValue *= $arg; } else { return ExcelError::throwError($arg); } } return (float) $returnValue; } /** * QUOTIENT. * * QUOTIENT function returns the integer portion of a division. Numerator is the divided number * and denominator is the divisor. * * Excel Function: * QUOTIENT(value1,value2) * * @param mixed $numerator Expect float|int * Or can be an array of values * @param mixed $denominator Expect float|int * Or can be an array of values * * @return array|int|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function quotient($numerator, $denominator) { if (is_array($numerator) || is_array($denominator)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $numerator, $denominator); } try { $numerator = Helpers::validateNumericNullSubstitution($numerator, 0); $denominator = Helpers::validateNumericNullSubstitution($denominator, 0); Helpers::validateNotZero($denominator); } catch (Exception $e) { return $e->getMessage(); } return (int) ($numerator / $denominator); } } PK!C>libraries/vendor/PhpSpreadsheet/Calculation/MathTrig/Trunc.phpnu[getMessage(); } return Helpers::returnSign($number); } } PK!AyAlibraries/vendor/PhpSpreadsheet/Calculation/MathTrig/IntClass.phpnu[getMessage(); } return (int) floor($number); } } PK!A$]]>libraries/vendor/PhpSpreadsheet/Calculation/MathTrig/Angle.phpnu[getMessage(); } return rad2deg($number); } /** * RADIANS. * * Returns the result of builtin function deg2rad after validating args. * * @param mixed $number Should be numeric, or can be an array of numbers * * @return array|float|string Rounded number * If an array of numbers is passed as the argument, then the returned result will also be an array * with the same dimensions */ public static function toRadians($number) { if (is_array($number)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number); } try { $number = Helpers::validateNumericNullBool($number); } catch (Exception $e) { return $e->getMessage(); } return deg2rad($number); } } PK!c c Glibraries/vendor/PhpSpreadsheet/Calculation/MathTrig/Trig/Cotangent.phpnu[getMessage(); } return Helpers::verySmallDenominator(cos($angle), sin($angle)); } /** * COTH. * * Returns the hyperbolic cotangent of an angle. * * @param array|float $angle Number, or can be an array of numbers * * @return array|float|string The hyperbolic cotangent of the angle * If an array of numbers is passed as the argument, then the returned result will also be an array * with the same dimensions */ public static function coth($angle) { if (is_array($angle)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $angle); } try { $angle = Helpers::validateNumericNullBool($angle); } catch (Exception $e) { return $e->getMessage(); } return Helpers::verySmallDenominator(1.0, tanh($angle)); } /** * ACOT. * * Returns the arccotangent of a number. * * @param array|float $number Number, or can be an array of numbers * * @return array|float|string The arccotangent of the number * If an array of numbers is passed as the argument, then the returned result will also be an array * with the same dimensions */ public static function acot($number) { if (is_array($number)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number); } try { $number = Helpers::validateNumericNullBool($number); } catch (Exception $e) { return $e->getMessage(); } return (M_PI / 2) - atan($number); } /** * ACOTH. * * Returns the hyperbolic arccotangent of a number. * * @param array|float $number Number, or can be an array of numbers * * @return array|float|string The hyperbolic arccotangent of the number * If an array of numbers is passed as the argument, then the returned result will also be an array * with the same dimensions */ public static function acoth($number) { if (is_array($number)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number); } try { $number = Helpers::validateNumericNullBool($number); } catch (Exception $e) { return $e->getMessage(); } $result = ($number === 1) ? NAN : (log(($number + 1) / ($number - 1)) / 2); return Helpers::numberOrNan($result); } } PK!o Flibraries/vendor/PhpSpreadsheet/Calculation/MathTrig/Trig/Cosecant.phpnu[getMessage(); } return Helpers::verySmallDenominator(1.0, sin($angle)); } /** * CSCH. * * Returns the hyperbolic cosecant of an angle. * * @param array|float $angle Number, or can be an array of numbers * * @return array|float|string The hyperbolic cosecant of the angle * If an array of numbers is passed as the argument, then the returned result will also be an array * with the same dimensions */ public static function csch($angle) { if (is_array($angle)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $angle); } try { $angle = Helpers::validateNumericNullBool($angle); } catch (Exception $e) { return $e->getMessage(); } return Helpers::verySmallDenominator(1.0, sinh($angle)); } } PK!(I Dlibraries/vendor/PhpSpreadsheet/Calculation/MathTrig/Trig/Cosine.phpnu[getMessage(); } return cos($number); } /** * COSH. * * Returns the result of builtin function cosh after validating args. * * @param mixed $number Should be numeric, or can be an array of numbers * * @return array|float|string hyperbolic cosine * If an array of numbers is passed as the argument, then the returned result will also be an array * with the same dimensions */ public static function cosh($number) { if (is_array($number)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number); } try { $number = Helpers::validateNumericNullBool($number); } catch (Exception $e) { return $e->getMessage(); } return cosh($number); } /** * ACOS. * * Returns the arccosine of a number. * * @param array|float $number Number, or can be an array of numbers * * @return array|float|string The arccosine of the number * If an array of numbers is passed as the argument, then the returned result will also be an array * with the same dimensions */ public static function acos($number) { if (is_array($number)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number); } try { $number = Helpers::validateNumericNullBool($number); } catch (Exception $e) { return $e->getMessage(); } return Helpers::numberOrNan(acos($number)); } /** * ACOSH. * * Returns the arc inverse hyperbolic cosine of a number. * * @param array|float $number Number, or can be an array of numbers * * @return array|float|string The inverse hyperbolic cosine of the number, or an error string * If an array of numbers is passed as the argument, then the returned result will also be an array * with the same dimensions */ public static function acosh($number) { if (is_array($number)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number); } try { $number = Helpers::validateNumericNullBool($number); } catch (Exception $e) { return $e->getMessage(); } return Helpers::numberOrNan(acosh($number)); } } PK!c֢W Blibraries/vendor/PhpSpreadsheet/Calculation/MathTrig/Trig/Sine.phpnu[getMessage(); } return sin($angle); } /** * SINH. * * Returns the result of builtin function sinh after validating args. * * @param mixed $angle Should be numeric, or can be an array of numbers * * @return array|float|string hyperbolic sine * If an array of numbers is passed as the argument, then the returned result will also be an array * with the same dimensions */ public static function sinh($angle) { if (is_array($angle)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $angle); } try { $angle = Helpers::validateNumericNullBool($angle); } catch (Exception $e) { return $e->getMessage(); } return sinh($angle); } /** * ASIN. * * Returns the arcsine of a number. * * @param array|float $number Number, or can be an array of numbers * * @return array|float|string The arcsine of the number * If an array of numbers is passed as the argument, then the returned result will also be an array * with the same dimensions */ public static function asin($number) { if (is_array($number)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number); } try { $number = Helpers::validateNumericNullBool($number); } catch (Exception $e) { return $e->getMessage(); } return Helpers::numberOrNan(asin($number)); } /** * ASINH. * * Returns the inverse hyperbolic sine of a number. * * @param array|float $number Number, or can be an array of numbers * * @return array|float|string The inverse hyperbolic sine of the number * If an array of numbers is passed as the argument, then the returned result will also be an array * with the same dimensions */ public static function asinh($number) { if (is_array($number)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number); } try { $number = Helpers::validateNumericNullBool($number); } catch (Exception $e) { return $e->getMessage(); } return Helpers::numberOrNan(asinh($number)); } } PK!AElibraries/vendor/PhpSpreadsheet/Calculation/MathTrig/Trig/Tangent.phpnu[getMessage(); } return Helpers::verySmallDenominator(sin($angle), cos($angle)); } /** * TANH. * * Returns the result of builtin function sinh after validating args. * * @param mixed $angle Should be numeric, or can be an array of numbers * * @return array|float|string hyperbolic tangent * If an array of numbers is passed as the argument, then the returned result will also be an array * with the same dimensions */ public static function tanh($angle) { if (is_array($angle)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $angle); } try { $angle = Helpers::validateNumericNullBool($angle); } catch (Exception $e) { return $e->getMessage(); } return tanh($angle); } /** * ATAN. * * Returns the arctangent of a number. * * @param array|float $number Number, or can be an array of numbers * * @return array|float|string The arctangent of the number * If an array of numbers is passed as the argument, then the returned result will also be an array * with the same dimensions */ public static function atan($number) { if (is_array($number)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number); } try { $number = Helpers::validateNumericNullBool($number); } catch (Exception $e) { return $e->getMessage(); } return Helpers::numberOrNan(atan($number)); } /** * ATANH. * * Returns the inverse hyperbolic tangent of a number. * * @param array|float $number Number, or can be an array of numbers * * @return array|float|string The inverse hyperbolic tangent of the number * If an array of numbers is passed as the argument, then the returned result will also be an array * with the same dimensions */ public static function atanh($number) { if (is_array($number)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number); } try { $number = Helpers::validateNumericNullBool($number); } catch (Exception $e) { return $e->getMessage(); } return Helpers::numberOrNan(atanh($number)); } /** * ATAN2. * * This function calculates the arc tangent of the two variables x and y. It is similar to * calculating the arc tangent of y ÷ x, except that the signs of both arguments are used * to determine the quadrant of the result. * The arctangent is the angle from the x-axis to a line containing the origin (0, 0) and a * point with coordinates (xCoordinate, yCoordinate). The angle is given in radians between * -pi and pi, excluding -pi. * * Note that the Excel ATAN2() function accepts its arguments in the reverse order to the standard * PHP atan2() function, so we need to reverse them here before calling the PHP atan() function. * * Excel Function: * ATAN2(xCoordinate,yCoordinate) * * @param mixed $xCoordinate should be float, the x-coordinate of the point, or can be an array of numbers * @param mixed $yCoordinate should be float, the y-coordinate of the point, or can be an array of numbers * * @return array|float|string The inverse tangent of the specified x- and y-coordinates, or a string containing an error * If an array of numbers is passed as one of the arguments, then the returned result will also be an array * with the same dimensions */ public static function atan2($xCoordinate, $yCoordinate) { if (is_array($xCoordinate) || is_array($yCoordinate)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $xCoordinate, $yCoordinate); } try { $xCoordinate = Helpers::validateNumericNullBool($xCoordinate); $yCoordinate = Helpers::validateNumericNullBool($yCoordinate); } catch (Exception $e) { return $e->getMessage(); } if (($xCoordinate == 0) && ($yCoordinate == 0)) { return ExcelError::DIV0(); } return atan2($yCoordinate, $xCoordinate); } } PK!Clibraries/vendor/PhpSpreadsheet/Calculation/MathTrig/Trig/index.phpnu[getMessage(); } return Helpers::verySmallDenominator(1.0, cos($angle)); } /** * SECH. * * Returns the hyperbolic secant of an angle. * * @param array|float $angle Number, or can be an array of numbers * * @return array|float|string The hyperbolic secant of the angle * If an array of numbers is passed as the argument, then the returned result will also be an array * with the same dimensions */ public static function sech($angle) { if (is_array($angle)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $angle); } try { $angle = Helpers::validateNumericNullBool($angle); } catch (Exception $e) { return $e->getMessage(); } return Helpers::verySmallDenominator(1.0, cosh($angle)); } } PK!>libraries/vendor/PhpSpreadsheet/Calculation/MathTrig/index.phpnu[= 0. * @param float|int $number */ public static function validateNotNegative($number, ?string $except = null): void { if ($number >= 0) { return; } throw new Exception($except ?? ExcelError::NAN()); } /** * Confirm number > 0. * @param float|int $number */ public static function validatePositive($number, ?string $except = null): void { if ($number > 0) { return; } throw new Exception($except ?? ExcelError::NAN()); } /** * Confirm number != 0. * @param float|int $number */ public static function validateNotZero($number): void { if ($number) { return; } throw new Exception(ExcelError::DIV0()); } public static function returnSign(float $number): int { return $number ? (($number > 0) ? 1 : -1) : 0; } public static function getEven(float $number): float { $significance = 2 * self::returnSign($number); return $significance ? (ceil($number / $significance) * $significance) : 0; } /** * Return NAN or value depending on argument. * @return float|string */ public static function numberOrNan(float $result) { return is_nan($result) ? ExcelError::NAN() : $result; } } PK!'K K ?libraries/vendor/PhpSpreadsheet/Calculation/MathTrig/Arabic.phpnu[ 1000, 'D' => 500, 'C' => 100, 'L' => 50, 'X' => 10, 'V' => 5, 'I' => 1, ]; /** * Recursively calculate the arabic value of a roman numeral. */ private static function calculateArabic(array $roman, int &$sum = 0, int $subtract = 0): int { $numeral = array_shift($roman); if (!isset(self::ROMAN_LOOKUP[$numeral])) { throw new Exception('Invalid character detected'); } $arabic = self::ROMAN_LOOKUP[$numeral]; if (count($roman) > 0 && isset(self::ROMAN_LOOKUP[$roman[0]]) && $arabic < self::ROMAN_LOOKUP[$roman[0]]) { $subtract += $arabic; } else { $sum += ($arabic - $subtract); $subtract = 0; } if (count($roman) > 0) { self::calculateArabic($roman, $sum, $subtract); } return $sum; } /** * ARABIC. * * Converts a Roman numeral to an Arabic numeral. * * Excel Function: * ARABIC(text) * * @param mixed $roman Should be a string, or can be an array of strings * * @return array|int|string the arabic numberal contrived from the roman numeral * If an array of numbers is passed as the argument, then the returned result will also be an array * with the same dimensions */ public static function evaluate($roman) { if (is_array($roman)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $roman); } // An empty string should return 0 $roman = substr(trim(strtoupper((string) $roman)), 0, 255); if ($roman === '') { return 0; } // Convert the roman numeral to an arabic number $negativeNumber = $roman[0] === '-'; if ($negativeNumber) { $roman = trim(substr($roman, 1)); if ($roman === '') { return ExcelError::NAN(); } } try { $arabic = self::calculateArabic(mb_str_split($roman, 1, 'UTF-8')); } catch (Exception $exception) { return ExcelError::VALUE(); // Invalid character detected } if ($negativeNumber) { $arabic *= -1; // The number should be negative } return $arabic; } } PK!{[=libraries/vendor/PhpSpreadsheet/Calculation/MathTrig/Sqrt.phpnu[getMessage(); } return Helpers::numberOrNan(sqrt($number)); } /** * SQRTPI. * * Returns the square root of (number * pi). * * @param array|float $number Number, or can be an array of numbers * * @return array|float|string Square Root of Number * Pi, or a string containing an error * If an array of numbers is passed as the argument, then the returned result will also be an array * with the same dimensions */ public static function pi($number) { if (is_array($number)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number); } try { $number = Helpers::validateNumericNullSubstitution($number, 0); Helpers::validateNotNegative($number); } catch (Exception $e) { return $e->getMessage(); } return sqrt($number * M_PI); } } PK!#/Hlibraries/vendor/PhpSpreadsheet/Calculation/MathTrig/MatrixFunctions.phpnu[getMessage(); } if ($step === 0) { return array_chunk( array_fill(0, $rows * $columns, $start), max($columns, 1) ); } return array_chunk( range($start, $start + (($rows * $columns - 1) * $step), $step), max($columns, 1) ); } /** * MDETERM. * * Returns the matrix determinant of an array. * * Excel Function: * MDETERM(array) * * @param mixed $matrixValues A matrix of values * * @return float|string The result, or a string containing an error */ public static function determinant($matrixValues) { try { $matrix = self::getMatrix($matrixValues); return $matrix->determinant(); } catch (MatrixException $exception) { return ExcelError::VALUE(); } catch (Exception $e) { return $e->getMessage(); } } /** * MINVERSE. * * Returns the inverse matrix for the matrix stored in an array. * * Excel Function: * MINVERSE(array) * * @param mixed $matrixValues A matrix of values * * @return array|string The result, or a string containing an error */ public static function inverse($matrixValues) { try { $matrix = self::getMatrix($matrixValues); return $matrix->inverse()->toArray(); } catch (MatrixDiv0Exception $exception) { return ExcelError::NAN(); } catch (MatrixException $exception) { return ExcelError::VALUE(); } catch (Exception $e) { return $e->getMessage(); } } /** * MMULT. * * @param mixed $matrixData1 A matrix of values * @param mixed $matrixData2 A matrix of values * * @return array|string The result, or a string containing an error */ public static function multiply($matrixData1, $matrixData2) { try { $matrixA = self::getMatrix($matrixData1); $matrixB = self::getMatrix($matrixData2); return $matrixA->multiply($matrixB)->toArray(); } catch (MatrixException $exception) { return ExcelError::VALUE(); } catch (Exception $e) { return $e->getMessage(); } } /** * MUnit. * * @param mixed $dimension Number of rows and columns * * @return array|string The result, or a string containing an error */ public static function identity($dimension) { try { $dimension = (int) Helpers::validateNumericNullBool($dimension); Helpers::validatePositive($dimension, ExcelError::VALUE()); $matrix = Builder::createIdentityMatrix($dimension, 0)->toArray(); return $matrix; } catch (Exception $e) { return $e->getMessage(); } } } PK!  <libraries/vendor/PhpSpreadsheet/Calculation/MathTrig/Lcm.phpnu[ 1; --$i) { if (($value % $i) == 0) { $factorArray = array_merge($factorArray, self::factors($value / $i)); $factorArray = array_merge($factorArray, self::factors($i)); if ($i <= sqrt($value)) { break; } } } if (!empty($factorArray)) { rsort($factorArray); return $factorArray; } return [(int) $value]; } /** * LCM. * * Returns the lowest common multiplier of a series of numbers * The least common multiple is the smallest positive integer that is a multiple * of all integer arguments number1, number2, and so on. Use LCM to add fractions * with different denominators. * * Excel Function: * LCM(number1[,number2[, ...]]) * * @param mixed ...$args Data values * * @return int|string Lowest Common Multiplier, or a string containing an error */ public static function evaluate(...$args) { try { $arrayArgs = []; $anyZeros = 0; $anyNonNulls = 0; foreach (Functions::flattenArray($args) as $value1) { $anyNonNulls += (int) ($value1 !== null); $value = Helpers::validateNumericNullSubstitution($value1, 1); Helpers::validateNotNegative($value); $arrayArgs[] = (int) $value; $anyZeros += (int) !((bool) $value); } self::testNonNulls($anyNonNulls); if ($anyZeros) { return 0; } } catch (Exception $e) { return $e->getMessage(); } $returnValue = 1; $allPoweredFactors = []; // Loop through arguments foreach ($arrayArgs as $value) { $myFactors = self::factors(floor($value)); $myCountedFactors = array_count_values($myFactors); $myPoweredFactors = []; foreach ($myCountedFactors as $myCountedFactor => $myCountedPower) { $myPoweredFactors[$myCountedFactor] = $myCountedFactor ** $myCountedPower; } self::processPoweredFactors($allPoweredFactors, $myPoweredFactors); } foreach ($allPoweredFactors as $allPoweredFactor) { $returnValue *= (int) $allPoweredFactor; } return $returnValue; } private static function processPoweredFactors(array &$allPoweredFactors, array &$myPoweredFactors): void { foreach ($myPoweredFactors as $myPoweredValue => $myPoweredFactor) { if (isset($allPoweredFactors[$myPoweredValue])) { if ($allPoweredFactors[$myPoweredValue] < $myPoweredFactor) { $allPoweredFactors[$myPoweredValue] = $myPoweredFactor; } } else { $allPoweredFactors[$myPoweredValue] = $myPoweredFactor; } } } private static function testNonNulls(int $anyNonNulls): void { if (!$anyNonNulls) { throw new Exception(ExcelError::VALUE()); } } } PK!/$f  Blibraries/vendor/PhpSpreadsheet/Calculation/MathTrig/Factorial.phpnu[getMessage(); } $factLoop = floor($factVal); if ($factVal > $factLoop) { if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { return Statistical\Distributions\Gamma::gammaValue($factVal + 1); } } $factorial = 1; while ($factLoop > 1) { $factorial *= $factLoop--; } return $factorial; } /** * FACTDOUBLE. * * Returns the double factorial of a number. * * Excel Function: * FACTDOUBLE(factVal) * * @param array|float $factVal Factorial Value, or can be an array of numbers * * @return array|float|int|string Double Factorial, or a string containing an error * If an array of numbers is passed as the argument, then the returned result will also be an array * with the same dimensions */ public static function factDouble($factVal) { if (is_array($factVal)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $factVal); } try { $factVal = Helpers::validateNumericNullSubstitution($factVal, 0); Helpers::validateNotNegative($factVal); } catch (Exception $e) { return $e->getMessage(); } $factLoop = floor($factVal); $factorial = 1; while ($factLoop > 1) { $factorial *= $factLoop; $factLoop -= 2; } return $factorial; } /** * MULTINOMIAL. * * Returns the ratio of the factorial of a sum of values to the product of factorials. * * @param mixed[] $args An array of mixed values for the Data Series * * @return float|int|string The result, or a string containing an error */ public static function multinomial(...$args) { $summer = 0; $divisor = 1; try { // Loop through arguments foreach (Functions::flattenArray($args) as $argx) { $arg = Helpers::validateNumericNullSubstitution($argx, null); Helpers::validateNotNegative($arg); $arg = (int) $arg; $summer += $arg; $divisor *= self::fact($arg); } } catch (Exception $e) { return $e->getMessage(); } $summer = self::fact($summer); return is_numeric($summer) ? ($summer / $divisor) : ExcelError::VALUE(); } } PK!`^^<libraries/vendor/PhpSpreadsheet/Calculation/MathTrig/Exp.phpnu[getMessage(); } return exp($number); } } PK!{bBlibraries/vendor/PhpSpreadsheet/Calculation/MathTrig/SeriesSum.phpnu[getMessage(); } return $returnValue; } } PK!$}XUXU>libraries/vendor/PhpSpreadsheet/Calculation/MathTrig/Roman.phpnu[ ['VL'], 46 => ['VLI'], 47 => ['VLII'], 48 => ['VLIII'], 49 => ['VLIV', 'IL'], 95 => ['VC'], 96 => ['VCI'], 97 => ['VCII'], 98 => ['VCIII'], 99 => ['VCIV', 'IC'], 145 => ['CVL'], 146 => ['CVLI'], 147 => ['CVLII'], 148 => ['CVLIII'], 149 => ['CVLIV', 'CIL'], 195 => ['CVC'], 196 => ['CVCI'], 197 => ['CVCII'], 198 => ['CVCIII'], 199 => ['CVCIV', 'CIC'], 245 => ['CCVL'], 246 => ['CCVLI'], 247 => ['CCVLII'], 248 => ['CCVLIII'], 249 => ['CCVLIV', 'CCIL'], 295 => ['CCVC'], 296 => ['CCVCI'], 297 => ['CCVCII'], 298 => ['CCVCIII'], 299 => ['CCVCIV', 'CCIC'], 345 => ['CCCVL'], 346 => ['CCCVLI'], 347 => ['CCCVLII'], 348 => ['CCCVLIII'], 349 => ['CCCVLIV', 'CCCIL'], 395 => ['CCCVC'], 396 => ['CCCVCI'], 397 => ['CCCVCII'], 398 => ['CCCVCIII'], 399 => ['CCCVCIV', 'CCCIC'], 445 => ['CDVL'], 446 => ['CDVLI'], 447 => ['CDVLII'], 448 => ['CDVLIII'], 449 => ['CDVLIV', 'CDIL'], 450 => ['LD'], 451 => ['LDI'], 452 => ['LDII'], 453 => ['LDIII'], 454 => ['LDIV'], 455 => ['LDV'], 456 => ['LDVI'], 457 => ['LDVII'], 458 => ['LDVIII'], 459 => ['LDIX'], 460 => ['LDX'], 461 => ['LDXI'], 462 => ['LDXII'], 463 => ['LDXIII'], 464 => ['LDXIV'], 465 => ['LDXV'], 466 => ['LDXVI'], 467 => ['LDXVII'], 468 => ['LDXVIII'], 469 => ['LDXIX'], 470 => ['LDXX'], 471 => ['LDXXI'], 472 => ['LDXXII'], 473 => ['LDXXIII'], 474 => ['LDXXIV'], 475 => ['LDXXV'], 476 => ['LDXXVI'], 477 => ['LDXXVII'], 478 => ['LDXXVIII'], 479 => ['LDXXIX'], 480 => ['LDXXX'], 481 => ['LDXXXI'], 482 => ['LDXXXII'], 483 => ['LDXXXIII'], 484 => ['LDXXXIV'], 485 => ['LDXXXV'], 486 => ['LDXXXVI'], 487 => ['LDXXXVII'], 488 => ['LDXXXVIII'], 489 => ['LDXXXIX'], 490 => ['LDXL', 'XD'], 491 => ['LDXLI', 'XDI'], 492 => ['LDXLII', 'XDII'], 493 => ['LDXLIII', 'XDIII'], 494 => ['LDXLIV', 'XDIV'], 495 => ['LDVL', 'XDV', 'VD'], 496 => ['LDVLI', 'XDVI', 'VDI'], 497 => ['LDVLII', 'XDVII', 'VDII'], 498 => ['LDVLIII', 'XDVIII', 'VDIII'], 499 => ['LDVLIV', 'XDIX', 'VDIV', 'ID'], 545 => ['DVL'], 546 => ['DVLI'], 547 => ['DVLII'], 548 => ['DVLIII'], 549 => ['DVLIV', 'DIL'], 595 => ['DVC'], 596 => ['DVCI'], 597 => ['DVCII'], 598 => ['DVCIII'], 599 => ['DVCIV', 'DIC'], 645 => ['DCVL'], 646 => ['DCVLI'], 647 => ['DCVLII'], 648 => ['DCVLIII'], 649 => ['DCVLIV', 'DCIL'], 695 => ['DCVC'], 696 => ['DCVCI'], 697 => ['DCVCII'], 698 => ['DCVCIII'], 699 => ['DCVCIV', 'DCIC'], 745 => ['DCCVL'], 746 => ['DCCVLI'], 747 => ['DCCVLII'], 748 => ['DCCVLIII'], 749 => ['DCCVLIV', 'DCCIL'], 795 => ['DCCVC'], 796 => ['DCCVCI'], 797 => ['DCCVCII'], 798 => ['DCCVCIII'], 799 => ['DCCVCIV', 'DCCIC'], 845 => ['DCCCVL'], 846 => ['DCCCVLI'], 847 => ['DCCCVLII'], 848 => ['DCCCVLIII'], 849 => ['DCCCVLIV', 'DCCCIL'], 895 => ['DCCCVC'], 896 => ['DCCCVCI'], 897 => ['DCCCVCII'], 898 => ['DCCCVCIII'], 899 => ['DCCCVCIV', 'DCCCIC'], 945 => ['CMVL'], 946 => ['CMVLI'], 947 => ['CMVLII'], 948 => ['CMVLIII'], 949 => ['CMVLIV', 'CMIL'], 950 => ['LM'], 951 => ['LMI'], 952 => ['LMII'], 953 => ['LMIII'], 954 => ['LMIV'], 955 => ['LMV'], 956 => ['LMVI'], 957 => ['LMVII'], 958 => ['LMVIII'], 959 => ['LMIX'], 960 => ['LMX'], 961 => ['LMXI'], 962 => ['LMXII'], 963 => ['LMXIII'], 964 => ['LMXIV'], 965 => ['LMXV'], 966 => ['LMXVI'], 967 => ['LMXVII'], 968 => ['LMXVIII'], 969 => ['LMXIX'], 970 => ['LMXX'], 971 => ['LMXXI'], 972 => ['LMXXII'], 973 => ['LMXXIII'], 974 => ['LMXXIV'], 975 => ['LMXXV'], 976 => ['LMXXVI'], 977 => ['LMXXVII'], 978 => ['LMXXVIII'], 979 => ['LMXXIX'], 980 => ['LMXXX'], 981 => ['LMXXXI'], 982 => ['LMXXXII'], 983 => ['LMXXXIII'], 984 => ['LMXXXIV'], 985 => ['LMXXXV'], 986 => ['LMXXXVI'], 987 => ['LMXXXVII'], 988 => ['LMXXXVIII'], 989 => ['LMXXXIX'], 990 => ['LMXL', 'XM'], 991 => ['LMXLI', 'XMI'], 992 => ['LMXLII', 'XMII'], 993 => ['LMXLIII', 'XMIII'], 994 => ['LMXLIV', 'XMIV'], 995 => ['LMVL', 'XMV', 'VM'], 996 => ['LMVLI', 'XMVI', 'VMI'], 997 => ['LMVLII', 'XMVII', 'VMII'], 998 => ['LMVLIII', 'XMVIII', 'VMIII'], 999 => ['LMVLIV', 'XMIX', 'VMIV', 'IM'], 1045 => ['MVL'], 1046 => ['MVLI'], 1047 => ['MVLII'], 1048 => ['MVLIII'], 1049 => ['MVLIV', 'MIL'], 1095 => ['MVC'], 1096 => ['MVCI'], 1097 => ['MVCII'], 1098 => ['MVCIII'], 1099 => ['MVCIV', 'MIC'], 1145 => ['MCVL'], 1146 => ['MCVLI'], 1147 => ['MCVLII'], 1148 => ['MCVLIII'], 1149 => ['MCVLIV', 'MCIL'], 1195 => ['MCVC'], 1196 => ['MCVCI'], 1197 => ['MCVCII'], 1198 => ['MCVCIII'], 1199 => ['MCVCIV', 'MCIC'], 1245 => ['MCCVL'], 1246 => ['MCCVLI'], 1247 => ['MCCVLII'], 1248 => ['MCCVLIII'], 1249 => ['MCCVLIV', 'MCCIL'], 1295 => ['MCCVC'], 1296 => ['MCCVCI'], 1297 => ['MCCVCII'], 1298 => ['MCCVCIII'], 1299 => ['MCCVCIV', 'MCCIC'], 1345 => ['MCCCVL'], 1346 => ['MCCCVLI'], 1347 => ['MCCCVLII'], 1348 => ['MCCCVLIII'], 1349 => ['MCCCVLIV', 'MCCCIL'], 1395 => ['MCCCVC'], 1396 => ['MCCCVCI'], 1397 => ['MCCCVCII'], 1398 => ['MCCCVCIII'], 1399 => ['MCCCVCIV', 'MCCCIC'], 1445 => ['MCDVL'], 1446 => ['MCDVLI'], 1447 => ['MCDVLII'], 1448 => ['MCDVLIII'], 1449 => ['MCDVLIV', 'MCDIL'], 1450 => ['MLD'], 1451 => ['MLDI'], 1452 => ['MLDII'], 1453 => ['MLDIII'], 1454 => ['MLDIV'], 1455 => ['MLDV'], 1456 => ['MLDVI'], 1457 => ['MLDVII'], 1458 => ['MLDVIII'], 1459 => ['MLDIX'], 1460 => ['MLDX'], 1461 => ['MLDXI'], 1462 => ['MLDXII'], 1463 => ['MLDXIII'], 1464 => ['MLDXIV'], 1465 => ['MLDXV'], 1466 => ['MLDXVI'], 1467 => ['MLDXVII'], 1468 => ['MLDXVIII'], 1469 => ['MLDXIX'], 1470 => ['MLDXX'], 1471 => ['MLDXXI'], 1472 => ['MLDXXII'], 1473 => ['MLDXXIII'], 1474 => ['MLDXXIV'], 1475 => ['MLDXXV'], 1476 => ['MLDXXVI'], 1477 => ['MLDXXVII'], 1478 => ['MLDXXVIII'], 1479 => ['MLDXXIX'], 1480 => ['MLDXXX'], 1481 => ['MLDXXXI'], 1482 => ['MLDXXXII'], 1483 => ['MLDXXXIII'], 1484 => ['MLDXXXIV'], 1485 => ['MLDXXXV'], 1486 => ['MLDXXXVI'], 1487 => ['MLDXXXVII'], 1488 => ['MLDXXXVIII'], 1489 => ['MLDXXXIX'], 1490 => ['MLDXL', 'MXD'], 1491 => ['MLDXLI', 'MXDI'], 1492 => ['MLDXLII', 'MXDII'], 1493 => ['MLDXLIII', 'MXDIII'], 1494 => ['MLDXLIV', 'MXDIV'], 1495 => ['MLDVL', 'MXDV', 'MVD'], 1496 => ['MLDVLI', 'MXDVI', 'MVDI'], 1497 => ['MLDVLII', 'MXDVII', 'MVDII'], 1498 => ['MLDVLIII', 'MXDVIII', 'MVDIII'], 1499 => ['MLDVLIV', 'MXDIX', 'MVDIV', 'MID'], 1545 => ['MDVL'], 1546 => ['MDVLI'], 1547 => ['MDVLII'], 1548 => ['MDVLIII'], 1549 => ['MDVLIV', 'MDIL'], 1595 => ['MDVC'], 1596 => ['MDVCI'], 1597 => ['MDVCII'], 1598 => ['MDVCIII'], 1599 => ['MDVCIV', 'MDIC'], 1645 => ['MDCVL'], 1646 => ['MDCVLI'], 1647 => ['MDCVLII'], 1648 => ['MDCVLIII'], 1649 => ['MDCVLIV', 'MDCIL'], 1695 => ['MDCVC'], 1696 => ['MDCVCI'], 1697 => ['MDCVCII'], 1698 => ['MDCVCIII'], 1699 => ['MDCVCIV', 'MDCIC'], 1745 => ['MDCCVL'], 1746 => ['MDCCVLI'], 1747 => ['MDCCVLII'], 1748 => ['MDCCVLIII'], 1749 => ['MDCCVLIV', 'MDCCIL'], 1795 => ['MDCCVC'], 1796 => ['MDCCVCI'], 1797 => ['MDCCVCII'], 1798 => ['MDCCVCIII'], 1799 => ['MDCCVCIV', 'MDCCIC'], 1845 => ['MDCCCVL'], 1846 => ['MDCCCVLI'], 1847 => ['MDCCCVLII'], 1848 => ['MDCCCVLIII'], 1849 => ['MDCCCVLIV', 'MDCCCIL'], 1895 => ['MDCCCVC'], 1896 => ['MDCCCVCI'], 1897 => ['MDCCCVCII'], 1898 => ['MDCCCVCIII'], 1899 => ['MDCCCVCIV', 'MDCCCIC'], 1945 => ['MCMVL'], 1946 => ['MCMVLI'], 1947 => ['MCMVLII'], 1948 => ['MCMVLIII'], 1949 => ['MCMVLIV', 'MCMIL'], 1950 => ['MLM'], 1951 => ['MLMI'], 1952 => ['MLMII'], 1953 => ['MLMIII'], 1954 => ['MLMIV'], 1955 => ['MLMV'], 1956 => ['MLMVI'], 1957 => ['MLMVII'], 1958 => ['MLMVIII'], 1959 => ['MLMIX'], 1960 => ['MLMX'], 1961 => ['MLMXI'], 1962 => ['MLMXII'], 1963 => ['MLMXIII'], 1964 => ['MLMXIV'], 1965 => ['MLMXV'], 1966 => ['MLMXVI'], 1967 => ['MLMXVII'], 1968 => ['MLMXVIII'], 1969 => ['MLMXIX'], 1970 => ['MLMXX'], 1971 => ['MLMXXI'], 1972 => ['MLMXXII'], 1973 => ['MLMXXIII'], 1974 => ['MLMXXIV'], 1975 => ['MLMXXV'], 1976 => ['MLMXXVI'], 1977 => ['MLMXXVII'], 1978 => ['MLMXXVIII'], 1979 => ['MLMXXIX'], 1980 => ['MLMXXX'], 1981 => ['MLMXXXI'], 1982 => ['MLMXXXII'], 1983 => ['MLMXXXIII'], 1984 => ['MLMXXXIV'], 1985 => ['MLMXXXV'], 1986 => ['MLMXXXVI'], 1987 => ['MLMXXXVII'], 1988 => ['MLMXXXVIII'], 1989 => ['MLMXXXIX'], 1990 => ['MLMXL', 'MXM'], 1991 => ['MLMXLI', 'MXMI'], 1992 => ['MLMXLII', 'MXMII'], 1993 => ['MLMXLIII', 'MXMIII'], 1994 => ['MLMXLIV', 'MXMIV'], 1995 => ['MLMVL', 'MXMV', 'MVM'], 1996 => ['MLMVLI', 'MXMVI', 'MVMI'], 1997 => ['MLMVLII', 'MXMVII', 'MVMII'], 1998 => ['MLMVLIII', 'MXMVIII', 'MVMIII'], 1999 => ['MLMVLIV', 'MXMIX', 'MVMIV', 'MIM'], 2045 => ['MMVL'], 2046 => ['MMVLI'], 2047 => ['MMVLII'], 2048 => ['MMVLIII'], 2049 => ['MMVLIV', 'MMIL'], 2095 => ['MMVC'], 2096 => ['MMVCI'], 2097 => ['MMVCII'], 2098 => ['MMVCIII'], 2099 => ['MMVCIV', 'MMIC'], 2145 => ['MMCVL'], 2146 => ['MMCVLI'], 2147 => ['MMCVLII'], 2148 => ['MMCVLIII'], 2149 => ['MMCVLIV', 'MMCIL'], 2195 => ['MMCVC'], 2196 => ['MMCVCI'], 2197 => ['MMCVCII'], 2198 => ['MMCVCIII'], 2199 => ['MMCVCIV', 'MMCIC'], 2245 => ['MMCCVL'], 2246 => ['MMCCVLI'], 2247 => ['MMCCVLII'], 2248 => ['MMCCVLIII'], 2249 => ['MMCCVLIV', 'MMCCIL'], 2295 => ['MMCCVC'], 2296 => ['MMCCVCI'], 2297 => ['MMCCVCII'], 2298 => ['MMCCVCIII'], 2299 => ['MMCCVCIV', 'MMCCIC'], 2345 => ['MMCCCVL'], 2346 => ['MMCCCVLI'], 2347 => ['MMCCCVLII'], 2348 => ['MMCCCVLIII'], 2349 => ['MMCCCVLIV', 'MMCCCIL'], 2395 => ['MMCCCVC'], 2396 => ['MMCCCVCI'], 2397 => ['MMCCCVCII'], 2398 => ['MMCCCVCIII'], 2399 => ['MMCCCVCIV', 'MMCCCIC'], 2445 => ['MMCDVL'], 2446 => ['MMCDVLI'], 2447 => ['MMCDVLII'], 2448 => ['MMCDVLIII'], 2449 => ['MMCDVLIV', 'MMCDIL'], 2450 => ['MMLD'], 2451 => ['MMLDI'], 2452 => ['MMLDII'], 2453 => ['MMLDIII'], 2454 => ['MMLDIV'], 2455 => ['MMLDV'], 2456 => ['MMLDVI'], 2457 => ['MMLDVII'], 2458 => ['MMLDVIII'], 2459 => ['MMLDIX'], 2460 => ['MMLDX'], 2461 => ['MMLDXI'], 2462 => ['MMLDXII'], 2463 => ['MMLDXIII'], 2464 => ['MMLDXIV'], 2465 => ['MMLDXV'], 2466 => ['MMLDXVI'], 2467 => ['MMLDXVII'], 2468 => ['MMLDXVIII'], 2469 => ['MMLDXIX'], 2470 => ['MMLDXX'], 2471 => ['MMLDXXI'], 2472 => ['MMLDXXII'], 2473 => ['MMLDXXIII'], 2474 => ['MMLDXXIV'], 2475 => ['MMLDXXV'], 2476 => ['MMLDXXVI'], 2477 => ['MMLDXXVII'], 2478 => ['MMLDXXVIII'], 2479 => ['MMLDXXIX'], 2480 => ['MMLDXXX'], 2481 => ['MMLDXXXI'], 2482 => ['MMLDXXXII'], 2483 => ['MMLDXXXIII'], 2484 => ['MMLDXXXIV'], 2485 => ['MMLDXXXV'], 2486 => ['MMLDXXXVI'], 2487 => ['MMLDXXXVII'], 2488 => ['MMLDXXXVIII'], 2489 => ['MMLDXXXIX'], 2490 => ['MMLDXL', 'MMXD'], 2491 => ['MMLDXLI', 'MMXDI'], 2492 => ['MMLDXLII', 'MMXDII'], 2493 => ['MMLDXLIII', 'MMXDIII'], 2494 => ['MMLDXLIV', 'MMXDIV'], 2495 => ['MMLDVL', 'MMXDV', 'MMVD'], 2496 => ['MMLDVLI', 'MMXDVI', 'MMVDI'], 2497 => ['MMLDVLII', 'MMXDVII', 'MMVDII'], 2498 => ['MMLDVLIII', 'MMXDVIII', 'MMVDIII'], 2499 => ['MMLDVLIV', 'MMXDIX', 'MMVDIV', 'MMID'], 2545 => ['MMDVL'], 2546 => ['MMDVLI'], 2547 => ['MMDVLII'], 2548 => ['MMDVLIII'], 2549 => ['MMDVLIV', 'MMDIL'], 2595 => ['MMDVC'], 2596 => ['MMDVCI'], 2597 => ['MMDVCII'], 2598 => ['MMDVCIII'], 2599 => ['MMDVCIV', 'MMDIC'], 2645 => ['MMDCVL'], 2646 => ['MMDCVLI'], 2647 => ['MMDCVLII'], 2648 => ['MMDCVLIII'], 2649 => ['MMDCVLIV', 'MMDCIL'], 2695 => ['MMDCVC'], 2696 => ['MMDCVCI'], 2697 => ['MMDCVCII'], 2698 => ['MMDCVCIII'], 2699 => ['MMDCVCIV', 'MMDCIC'], 2745 => ['MMDCCVL'], 2746 => ['MMDCCVLI'], 2747 => ['MMDCCVLII'], 2748 => ['MMDCCVLIII'], 2749 => ['MMDCCVLIV', 'MMDCCIL'], 2795 => ['MMDCCVC'], 2796 => ['MMDCCVCI'], 2797 => ['MMDCCVCII'], 2798 => ['MMDCCVCIII'], 2799 => ['MMDCCVCIV', 'MMDCCIC'], 2845 => ['MMDCCCVL'], 2846 => ['MMDCCCVLI'], 2847 => ['MMDCCCVLII'], 2848 => ['MMDCCCVLIII'], 2849 => ['MMDCCCVLIV', 'MMDCCCIL'], 2895 => ['MMDCCCVC'], 2896 => ['MMDCCCVCI'], 2897 => ['MMDCCCVCII'], 2898 => ['MMDCCCVCIII'], 2899 => ['MMDCCCVCIV', 'MMDCCCIC'], 2945 => ['MMCMVL'], 2946 => ['MMCMVLI'], 2947 => ['MMCMVLII'], 2948 => ['MMCMVLIII'], 2949 => ['MMCMVLIV', 'MMCMIL'], 2950 => ['MMLM'], 2951 => ['MMLMI'], 2952 => ['MMLMII'], 2953 => ['MMLMIII'], 2954 => ['MMLMIV'], 2955 => ['MMLMV'], 2956 => ['MMLMVI'], 2957 => ['MMLMVII'], 2958 => ['MMLMVIII'], 2959 => ['MMLMIX'], 2960 => ['MMLMX'], 2961 => ['MMLMXI'], 2962 => ['MMLMXII'], 2963 => ['MMLMXIII'], 2964 => ['MMLMXIV'], 2965 => ['MMLMXV'], 2966 => ['MMLMXVI'], 2967 => ['MMLMXVII'], 2968 => ['MMLMXVIII'], 2969 => ['MMLMXIX'], 2970 => ['MMLMXX'], 2971 => ['MMLMXXI'], 2972 => ['MMLMXXII'], 2973 => ['MMLMXXIII'], 2974 => ['MMLMXXIV'], 2975 => ['MMLMXXV'], 2976 => ['MMLMXXVI'], 2977 => ['MMLMXXVII'], 2978 => ['MMLMXXVIII'], 2979 => ['MMLMXXIX'], 2980 => ['MMLMXXX'], 2981 => ['MMLMXXXI'], 2982 => ['MMLMXXXII'], 2983 => ['MMLMXXXIII'], 2984 => ['MMLMXXXIV'], 2985 => ['MMLMXXXV'], 2986 => ['MMLMXXXVI'], 2987 => ['MMLMXXXVII'], 2988 => ['MMLMXXXVIII'], 2989 => ['MMLMXXXIX'], 2990 => ['MMLMXL', 'MMXM'], 2991 => ['MMLMXLI', 'MMXMI'], 2992 => ['MMLMXLII', 'MMXMII'], 2993 => ['MMLMXLIII', 'MMXMIII'], 2994 => ['MMLMXLIV', 'MMXMIV'], 2995 => ['MMLMVL', 'MMXMV', 'MMVM'], 2996 => ['MMLMVLI', 'MMXMVI', 'MMVMI'], 2997 => ['MMLMVLII', 'MMXMVII', 'MMVMII'], 2998 => ['MMLMVLIII', 'MMXMVIII', 'MMVMIII'], 2999 => ['MMLMVLIV', 'MMXMIX', 'MMVMIV', 'MMIM'], 3045 => ['MMMVL'], 3046 => ['MMMVLI'], 3047 => ['MMMVLII'], 3048 => ['MMMVLIII'], 3049 => ['MMMVLIV', 'MMMIL'], 3095 => ['MMMVC'], 3096 => ['MMMVCI'], 3097 => ['MMMVCII'], 3098 => ['MMMVCIII'], 3099 => ['MMMVCIV', 'MMMIC'], 3145 => ['MMMCVL'], 3146 => ['MMMCVLI'], 3147 => ['MMMCVLII'], 3148 => ['MMMCVLIII'], 3149 => ['MMMCVLIV', 'MMMCIL'], 3195 => ['MMMCVC'], 3196 => ['MMMCVCI'], 3197 => ['MMMCVCII'], 3198 => ['MMMCVCIII'], 3199 => ['MMMCVCIV', 'MMMCIC'], 3245 => ['MMMCCVL'], 3246 => ['MMMCCVLI'], 3247 => ['MMMCCVLII'], 3248 => ['MMMCCVLIII'], 3249 => ['MMMCCVLIV', 'MMMCCIL'], 3295 => ['MMMCCVC'], 3296 => ['MMMCCVCI'], 3297 => ['MMMCCVCII'], 3298 => ['MMMCCVCIII'], 3299 => ['MMMCCVCIV', 'MMMCCIC'], 3345 => ['MMMCCCVL'], 3346 => ['MMMCCCVLI'], 3347 => ['MMMCCCVLII'], 3348 => ['MMMCCCVLIII'], 3349 => ['MMMCCCVLIV', 'MMMCCCIL'], 3395 => ['MMMCCCVC'], 3396 => ['MMMCCCVCI'], 3397 => ['MMMCCCVCII'], 3398 => ['MMMCCCVCIII'], 3399 => ['MMMCCCVCIV', 'MMMCCCIC'], 3445 => ['MMMCDVL'], 3446 => ['MMMCDVLI'], 3447 => ['MMMCDVLII'], 3448 => ['MMMCDVLIII'], 3449 => ['MMMCDVLIV', 'MMMCDIL'], 3450 => ['MMMLD'], 3451 => ['MMMLDI'], 3452 => ['MMMLDII'], 3453 => ['MMMLDIII'], 3454 => ['MMMLDIV'], 3455 => ['MMMLDV'], 3456 => ['MMMLDVI'], 3457 => ['MMMLDVII'], 3458 => ['MMMLDVIII'], 3459 => ['MMMLDIX'], 3460 => ['MMMLDX'], 3461 => ['MMMLDXI'], 3462 => ['MMMLDXII'], 3463 => ['MMMLDXIII'], 3464 => ['MMMLDXIV'], 3465 => ['MMMLDXV'], 3466 => ['MMMLDXVI'], 3467 => ['MMMLDXVII'], 3468 => ['MMMLDXVIII'], 3469 => ['MMMLDXIX'], 3470 => ['MMMLDXX'], 3471 => ['MMMLDXXI'], 3472 => ['MMMLDXXII'], 3473 => ['MMMLDXXIII'], 3474 => ['MMMLDXXIV'], 3475 => ['MMMLDXXV'], 3476 => ['MMMLDXXVI'], 3477 => ['MMMLDXXVII'], 3478 => ['MMMLDXXVIII'], 3479 => ['MMMLDXXIX'], 3480 => ['MMMLDXXX'], 3481 => ['MMMLDXXXI'], 3482 => ['MMMLDXXXII'], 3483 => ['MMMLDXXXIII'], 3484 => ['MMMLDXXXIV'], 3485 => ['MMMLDXXXV'], 3486 => ['MMMLDXXXVI'], 3487 => ['MMMLDXXXVII'], 3488 => ['MMMLDXXXVIII'], 3489 => ['MMMLDXXXIX'], 3490 => ['MMMLDXL', 'MMMXD'], 3491 => ['MMMLDXLI', 'MMMXDI'], 3492 => ['MMMLDXLII', 'MMMXDII'], 3493 => ['MMMLDXLIII', 'MMMXDIII'], 3494 => ['MMMLDXLIV', 'MMMXDIV'], 3495 => ['MMMLDVL', 'MMMXDV', 'MMMVD'], 3496 => ['MMMLDVLI', 'MMMXDVI', 'MMMVDI'], 3497 => ['MMMLDVLII', 'MMMXDVII', 'MMMVDII'], 3498 => ['MMMLDVLIII', 'MMMXDVIII', 'MMMVDIII'], 3499 => ['MMMLDVLIV', 'MMMXDIX', 'MMMVDIV', 'MMMID'], 3545 => ['MMMDVL'], 3546 => ['MMMDVLI'], 3547 => ['MMMDVLII'], 3548 => ['MMMDVLIII'], 3549 => ['MMMDVLIV', 'MMMDIL'], 3595 => ['MMMDVC'], 3596 => ['MMMDVCI'], 3597 => ['MMMDVCII'], 3598 => ['MMMDVCIII'], 3599 => ['MMMDVCIV', 'MMMDIC'], 3645 => ['MMMDCVL'], 3646 => ['MMMDCVLI'], 3647 => ['MMMDCVLII'], 3648 => ['MMMDCVLIII'], 3649 => ['MMMDCVLIV', 'MMMDCIL'], 3695 => ['MMMDCVC'], 3696 => ['MMMDCVCI'], 3697 => ['MMMDCVCII'], 3698 => ['MMMDCVCIII'], 3699 => ['MMMDCVCIV', 'MMMDCIC'], 3745 => ['MMMDCCVL'], 3746 => ['MMMDCCVLI'], 3747 => ['MMMDCCVLII'], 3748 => ['MMMDCCVLIII'], 3749 => ['MMMDCCVLIV', 'MMMDCCIL'], 3795 => ['MMMDCCVC'], 3796 => ['MMMDCCVCI'], 3797 => ['MMMDCCVCII'], 3798 => ['MMMDCCVCIII'], 3799 => ['MMMDCCVCIV', 'MMMDCCIC'], 3845 => ['MMMDCCCVL'], 3846 => ['MMMDCCCVLI'], 3847 => ['MMMDCCCVLII'], 3848 => ['MMMDCCCVLIII'], 3849 => ['MMMDCCCVLIV', 'MMMDCCCIL'], 3895 => ['MMMDCCCVC'], 3896 => ['MMMDCCCVCI'], 3897 => ['MMMDCCCVCII'], 3898 => ['MMMDCCCVCIII'], 3899 => ['MMMDCCCVCIV', 'MMMDCCCIC'], 3945 => ['MMMCMVL'], 3946 => ['MMMCMVLI'], 3947 => ['MMMCMVLII'], 3948 => ['MMMCMVLIII'], 3949 => ['MMMCMVLIV', 'MMMCMIL'], 3950 => ['MMMLM'], 3951 => ['MMMLMI'], 3952 => ['MMMLMII'], 3953 => ['MMMLMIII'], 3954 => ['MMMLMIV'], 3955 => ['MMMLMV'], 3956 => ['MMMLMVI'], 3957 => ['MMMLMVII'], 3958 => ['MMMLMVIII'], 3959 => ['MMMLMIX'], 3960 => ['MMMLMX'], 3961 => ['MMMLMXI'], 3962 => ['MMMLMXII'], 3963 => ['MMMLMXIII'], 3964 => ['MMMLMXIV'], 3965 => ['MMMLMXV'], 3966 => ['MMMLMXVI'], 3967 => ['MMMLMXVII'], 3968 => ['MMMLMXVIII'], 3969 => ['MMMLMXIX'], 3970 => ['MMMLMXX'], 3971 => ['MMMLMXXI'], 3972 => ['MMMLMXXII'], 3973 => ['MMMLMXXIII'], 3974 => ['MMMLMXXIV'], 3975 => ['MMMLMXXV'], 3976 => ['MMMLMXXVI'], 3977 => ['MMMLMXXVII'], 3978 => ['MMMLMXXVIII'], 3979 => ['MMMLMXXIX'], 3980 => ['MMMLMXXX'], 3981 => ['MMMLMXXXI'], 3982 => ['MMMLMXXXII'], 3983 => ['MMMLMXXXIII'], 3984 => ['MMMLMXXXIV'], 3985 => ['MMMLMXXXV'], 3986 => ['MMMLMXXXVI'], 3987 => ['MMMLMXXXVII'], 3988 => ['MMMLMXXXVIII'], 3989 => ['MMMLMXXXIX'], 3990 => ['MMMLMXL', 'MMMXM'], 3991 => ['MMMLMXLI', 'MMMXMI'], 3992 => ['MMMLMXLII', 'MMMXMII'], 3993 => ['MMMLMXLIII', 'MMMXMIII'], 3994 => ['MMMLMXLIV', 'MMMXMIV'], 3995 => ['MMMLMVL', 'MMMXMV', 'MMMVM'], 3996 => ['MMMLMVLI', 'MMMXMVI', 'MMMVMI'], 3997 => ['MMMLMVLII', 'MMMXMVII', 'MMMVMII'], 3998 => ['MMMLMVLIII', 'MMMXMVIII', 'MMMVMIII'], 3999 => ['MMMLMVLIV', 'MMMXMIX', 'MMMVMIV', 'MMMIM'], ]; private const THOUSANDS = ['', 'M', 'MM', 'MMM']; private const HUNDREDS = ['', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM']; private const TENS = ['', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC']; private const ONES = ['', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX']; const MAX_ROMAN_VALUE = 3999; const MAX_ROMAN_STYLE = 4; private static function valueOk(int $aValue, int $style): string { $origValue = $aValue; $m = \intdiv($aValue, 1000); $aValue %= 1000; $c = \intdiv($aValue, 100); $aValue %= 100; $t = \intdiv($aValue, 10); $aValue %= 10; $result = self::THOUSANDS[$m] . self::HUNDREDS[$c] . self::TENS[$t] . self::ONES[$aValue]; if ($style > 0) { if (array_key_exists($origValue, self::VALUES)) { $arr = self::VALUES[$origValue]; $idx = min($style, count($arr)) - 1; $result = $arr[$idx]; } } return $result; } private static function styleOk(int $aValue, int $style): string { return ($aValue < 0 || $aValue > self::MAX_ROMAN_VALUE) ? ExcelError::VALUE() : self::valueOk($aValue, $style); } public static function calculateRoman(int $aValue, int $style): string { return ($style < 0 || $style > self::MAX_ROMAN_STYLE) ? ExcelError::VALUE() : self::styleOk($aValue, $style); } /** * ROMAN. * * Converts a number to Roman numeral * * @param mixed $aValue Number to convert * Or can be an array of numbers * @param mixed $style Number indicating one of five possible forms * Or can be an array of styles * * @return array|string Roman numeral, or a string containing an error * If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function evaluate($aValue, $style = 0) { if (is_array($aValue) || is_array($style)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $aValue, $style); } try { $aValue = Helpers::validateNumericNullBool($aValue); if (is_bool($style)) { $style = $style ? 0 : 4; } $style = Helpers::validateNumericNullSubstitution($style, null); } catch (Exception $e) { return $e->getMessage(); } return self::calculateRoman((int) $aValue, (int) $style); } } PK!U((=libraries/vendor/PhpSpreadsheet/Calculation/MathTrig/Base.phpnu[getMessage(); } return self::calculate($number, $radix, $minLength); } /** * @param mixed $minLength */ private static function calculate(float $number, int $radix, $minLength): string { if ($minLength === null || is_numeric($minLength)) { if ($number < 0 || $number >= 2 ** 53 || $radix < 2 || $radix > 36) { return ExcelError::NAN(); // Numeric range constraints } $outcome = strtoupper((string) base_convert("$number", 10, $radix)); if ($minLength !== null) { $outcome = str_pad($outcome, (int) $minLength, '0', STR_PAD_LEFT); // String padding } return $outcome; } return ExcelError::VALUE(); } } PK!hAlibraries/vendor/PhpSpreadsheet/Calculation/MathTrig/Absolute.phpnu[getMessage(); } return abs($number); } } PK!VJ J Clibraries/vendor/PhpSpreadsheet/Calculation/MathTrig/Logarithms.phpnu[getMessage(); } return log($number, $base); } /** * LOG10. * * Returns the result of builtin function log after validating args. * * @param mixed $number Should be numeric * Or can be an array of values * * @return array|float|string Rounded number * If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function base10($number) { if (is_array($number)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number); } try { $number = Helpers::validateNumericNullBool($number); Helpers::validatePositive($number); } catch (Exception $e) { return $e->getMessage(); } return log10($number); } /** * LN. * * Returns the result of builtin function log after validating args. * * @param mixed $number Should be numeric * Or can be an array of values * * @return array|float|string Rounded number * If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function natural($number) { if (is_array($number)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number); } try { $number = Helpers::validateNumericNullBool($number); Helpers::validatePositive($number); } catch (Exception $e) { return $e->getMessage(); } return log($number); } } PK!LWH1 1 ?libraries/vendor/PhpSpreadsheet/Calculation/MathTrig/Random.phpnu[getMessage(); } return mt_rand($min, $max); } /** * RANDARRAY. * * Generates a list of sequential numbers in an array. * * Excel Function: * RANDARRAY([rows],[columns],[start],[step]) * * @param mixed $rows the number of rows to return, defaults to 1 * @param mixed $columns the number of columns to return, defaults to 1 * @param mixed $min the minimum number to be returned, defaults to 0 * @param mixed $max the maximum number to be returned, defaults to 1 * @param bool $wholeNumber the type of numbers to return: * False - Decimal numbers to 15 decimal places. (default) * True - Whole (integer) numbers * * @return array|string The resulting array, or a string containing an error */ public static function randArray($rows = 1, $columns = 1, $min = 0, $max = 1, bool $wholeNumber = false) { try { $rows = (int) Helpers::validateNumericNullSubstitution($rows, 1); Helpers::validatePositive($rows); $columns = (int) Helpers::validateNumericNullSubstitution($columns, 1); Helpers::validatePositive($columns); $min = Helpers::validateNumericNullSubstitution($min, 1); $max = Helpers::validateNumericNullSubstitution($max, 1); if ($max <= $min) { return ExcelError::VALUE(); } } catch (Exception $e) { return $e->getMessage(); } return array_chunk( array_map( fn () => $wholeNumber ? mt_rand((int) $min, (int) $max) : (mt_rand() / mt_getrandmax()) * ($max - $min) + $min, array_fill(0, $rows * $columns, $min) ), max($columns, 1) ); } } PK!getMessage(); } /** @var float */ $quotient = Factorial::fact($numObjs); /** @var float */ $divisor1 = Factorial::fact($numObjs - $numInSet); /** @var float */ $divisor2 = Factorial::fact($numInSet); return round($quotient / ($divisor1 * $divisor2)); } /** * COMBINA. * * Returns the number of combinations for a given number of items. Use COMBIN to * determine the total possible number of groups for a given number of items. * * Excel Function: * COMBINA(numObjs,numInSet) * * @param mixed $numObjs Number of different objects, or can be an array of numbers * @param mixed $numInSet Number of objects in each combination, or can be an array of numbers * * @return array|float|int|string Number of combinations, or a string containing an error * If an array of numbers is passed as the argument, then the returned result will also be an array * with the same dimensions */ public static function withRepetition($numObjs, $numInSet) { if (is_array($numObjs) || is_array($numInSet)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $numObjs, $numInSet); } try { $numObjs = Helpers::validateNumericNullSubstitution($numObjs, null); $numInSet = Helpers::validateNumericNullSubstitution($numInSet, null); Helpers::validateNotNegative($numInSet); Helpers::validateNotNegative($numObjs); $numObjs = (int) $numObjs; $numInSet = (int) $numInSet; // Microsoft documentation says following is true, but Excel // does not enforce this restriction. //Helpers::validateNotNegative($numObjs - $numInSet); if ($numObjs === 0) { Helpers::validateNotNegative(-$numInSet); return 1; } } catch (Exception $e) { return $e->getMessage(); } /** @var float */ $quotient = Factorial::fact($numObjs + $numInSet - 1); /** @var float */ $divisor1 = Factorial::fact($numObjs - 1); /** @var float */ $divisor2 = Factorial::fact($numInSet); return round($quotient / ($divisor1 * $divisor2)); } } PK!aA>libraries/vendor/PhpSpreadsheet/Calculation/MathTrig/Floor.phpnu[getMessage(); } return self::argumentsOk((float) $number, (float) $significance); } /** * FLOOR.MATH. * * Round a number down to the nearest integer or to the nearest multiple of significance. * * Excel Function: * FLOOR.MATH(number[,significance[,mode]]) * * @param mixed $number Number to round * Or can be an array of values * @param mixed $significance Significance * Or can be an array of values * @param mixed $mode direction to round negative numbers * Or can be an array of values * * @return array|float|string Rounded Number, or a string containing an error * If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function math($number, $significance = null, $mode = 0) { if (is_array($number) || is_array($significance) || is_array($mode)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $significance, $mode); } try { $number = Helpers::validateNumericNullBool($number); $significance = Helpers::validateNumericNullSubstitution($significance, ($number < 0) ? -1 : 1); $mode = Helpers::validateNumericNullSubstitution($mode, null); } catch (Exception $e) { return $e->getMessage(); } return self::argsOk((float) $number, (float) $significance, (int) $mode); } /** * FLOOR.PRECISE. * * Rounds number down, toward zero, to the nearest multiple of significance. * * Excel Function: * FLOOR.PRECISE(number[,significance]) * * @param array|float $number Number to round * Or can be an array of values * @param array|float $significance Significance * Or can be an array of values * * @return array|float|string Rounded Number, or a string containing an error * If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function precise($number, $significance = 1) { if (is_array($number) || is_array($significance)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $significance); } try { $number = Helpers::validateNumericNullBool($number); $significance = Helpers::validateNumericNullSubstitution($significance, null); } catch (Exception $e) { return $e->getMessage(); } return self::argumentsOkPrecise((float) $number, (float) $significance); } /** * Avoid Scrutinizer problems concerning complexity. * @return float|string */ private static function argumentsOkPrecise(float $number, float $significance) { if ($significance == 0.0) { return ExcelError::DIV0(); } if ($number == 0.0) { return 0.0; } return floor($number / abs($significance)) * abs($significance); } /** * Avoid Scrutinizer complexity problems. * * @return float|string Rounded Number, or a string containing an error */ private static function argsOk(float $number, float $significance, int $mode) { if (!$significance) { return ExcelError::DIV0(); } if (!$number) { return 0.0; } if (self::floorMathTest($number, $significance, $mode)) { return ceil($number / $significance) * $significance; } return floor($number / $significance) * $significance; } /** * Let FLOORMATH complexity pass Scrutinizer. */ private static function floorMathTest(float $number, float $significance, int $mode): bool { return Helpers::returnSign($significance) == -1 || (Helpers::returnSign($number) == -1 && !empty($mode)); } /** * Avoid Scrutinizer problems concerning complexity. * @return float|string */ private static function argumentsOk(float $number, float $significance) { if ($significance == 0.0) { return ExcelError::DIV0(); } if ($number == 0.0) { return 0.0; } if (Helpers::returnSign($significance) == 1) { return floor($number / $significance) * $significance; } if (Helpers::returnSign($number) == -1 && Helpers::returnSign($significance) == -1) { return floor($number / $significance) * $significance; } return ExcelError::NAN(); } } PK!e5H Clibraries/vendor/PhpSpreadsheet/Calculation/MathTrig/SumSquares.phpnu[getMessage(); } return $returnValue; } private static function getCount(array $array1, array $array2): int { $count = count($array1); if ($count !== count($array2)) { throw new Exception(ExcelError::NA()); } return $count; } /** * These functions accept only numeric arguments, not even strings which are numeric. * @param mixed $item */ private static function numericNotString($item): bool { return is_numeric($item) && !is_string($item); } /** * SUMX2MY2. * * @param mixed[] $matrixData1 Matrix #1 * @param mixed[] $matrixData2 Matrix #2 * @return float|int|string */ public static function sumXSquaredMinusYSquared(array $matrixData1, array $matrixData2) { try { $array1 = Functions::flattenArray($matrixData1); $array2 = Functions::flattenArray($matrixData2); $count = self::getCount($array1, $array2); $result = 0; for ($i = 0; $i < $count; ++$i) { if (self::numericNotString($array1[$i]) && self::numericNotString($array2[$i])) { $result += ($array1[$i] * $array1[$i]) - ($array2[$i] * $array2[$i]); } } } catch (Exception $e) { return $e->getMessage(); } return $result; } /** * SUMX2PY2. * * @param mixed[] $matrixData1 Matrix #1 * @param mixed[] $matrixData2 Matrix #2 * @return float|int|string */ public static function sumXSquaredPlusYSquared(array $matrixData1, array $matrixData2) { try { $array1 = Functions::flattenArray($matrixData1); $array2 = Functions::flattenArray($matrixData2); $count = self::getCount($array1, $array2); $result = 0; for ($i = 0; $i < $count; ++$i) { if (self::numericNotString($array1[$i]) && self::numericNotString($array2[$i])) { $result += ($array1[$i] * $array1[$i]) + ($array2[$i] * $array2[$i]); } } } catch (Exception $e) { return $e->getMessage(); } return $result; } /** * SUMXMY2. * * @param mixed[] $matrixData1 Matrix #1 * @param mixed[] $matrixData2 Matrix #2 * @return float|int|string */ public static function sumXMinusYSquared(array $matrixData1, array $matrixData2) { try { $array1 = Functions::flattenArray($matrixData1); $array2 = Functions::flattenArray($matrixData2); $count = self::getCount($array1, $array2); $result = 0; for ($i = 0; $i < $count; ++$i) { if (self::numericNotString($array1[$i]) && self::numericNotString($array2[$i])) { $result += ($array1[$i] - $array2[$i]) * ($array1[$i] - $array2[$i]); } } } catch (Exception $e) { return $e->getMessage(); } return $result; } } PK!: ?libraries/vendor/PhpSpreadsheet/Calculation/TextData/Search.phpnu[getMessage(); } if (StringHelper::countCharacters($haystack) >= $offset) { if (StringHelper::countCharacters($needle) === 0) { return $offset; } $pos = mb_strpos($haystack, $needle, --$offset, 'UTF-8'); if ($pos !== false) { return ++$pos; } } return ExcelError::VALUE(); } /** * SEARCH (case insensitive search). * * @param mixed $needle The string to look for * Or can be an array of values * @param mixed $haystack The string in which to look * Or can be an array of values * @param mixed $offset Integer offset within $haystack to start searching from * Or can be an array of values * * @return array|int|string The offset where the first occurrence of needle was found in the haystack * If an array of values is passed for the $value or $chars arguments, then the returned result * will also be an array with matching dimensions */ public static function insensitive($needle, $haystack, $offset = 1) { if (is_array($needle) || is_array($haystack) || is_array($offset)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $needle, $haystack, $offset); } try { $needle = Helpers::extractString($needle, true); $haystack = Helpers::extractString($haystack, true); $offset = Helpers::extractInt($offset, 1, 0, true); } catch (CalcExp $e) { return $e->getMessage(); } if (StringHelper::countCharacters($haystack) >= $offset) { if (StringHelper::countCharacters($needle) === 0) { return $offset; } $pos = mb_stripos($haystack, $needle, --$offset, 'UTF-8'); if ($pos !== false) { return ++$pos; } } return ExcelError::VALUE(); } } PK!O  Dlibraries/vendor/PhpSpreadsheet/Calculation/TextData/CaseConvert.phpnu[getMessage(); } return StringHelper::strToLower($mixedCaseValue); } /** * UPPERCASE. * * Converts a string value to upper case. * * @param mixed $mixedCaseValue The string value to convert to upper case * Or can be an array of values * * @return array|string If an array of values is passed as the argument, then the returned result will also be an array * with the same dimensions */ public static function upper($mixedCaseValue) { if (is_array($mixedCaseValue)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $mixedCaseValue); } try { $mixedCaseValue = Helpers::extractString($mixedCaseValue, true); } catch (CalcExp $e) { return $e->getMessage(); } return StringHelper::strToUpper($mixedCaseValue); } /** * PROPERCASE. * * Converts a string value to proper or title case. * * @param mixed $mixedCaseValue The string value to convert to title case * Or can be an array of values * * @return array|string If an array of values is passed as the argument, then the returned result will also be an array * with the same dimensions */ public static function proper($mixedCaseValue) { if (is_array($mixedCaseValue)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $mixedCaseValue); } try { $mixedCaseValue = Helpers::extractString($mixedCaseValue, true); } catch (CalcExp $e) { return $e->getMessage(); } return StringHelper::strToTitle($mixedCaseValue); } } PK!Dlibraries/vendor/PhpSpreadsheet/Calculation/TextData/Concatenate.phpnu[ DataType::MAX_STRING_LENGTH) { $returnValue = ExcelError::CALC(); break; } } return $returnValue; } /** * This implements the CONCATENATE function. * * @param array $args data to be concatenated * @return mixed[]|string */ public static function actualCONCATENATE(...$args) { if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_GNUMERIC) { return self::CONCATENATE(...$args); } $result = ''; foreach ($args as $operand2) { $result = self::concatenate2Args($result, $operand2); if (ErrorValue::isError($result, true) === true) { break; } } return $result; } /** * @param mixed[]|string $operand1 * @param null|mixed[]|bool|float|int|string $operand2 * @return mixed[]|string */ private static function concatenate2Args($operand1, $operand2) { if (is_array($operand1) || is_array($operand2)) { $operand1 = Calculation::boolToString($operand1); $operand2 = Calculation::boolToString($operand2); [$rows, $columns] = Calculation::checkMatrixOperands($operand1, $operand2, 2); $errorFound = false; for ($row = 0; $row < $rows && !$errorFound; ++$row) { for ($column = 0; $column < $columns; ++$column) { if (ErrorValue::isError($operand2[$row][$column])) { return $operand2[$row][$column]; } $operand1[$row][$column] = StringHelper::convertToString($operand1[$row][$column], true, '', true) . StringHelper::convertToString($operand2[$row][$column], true, '', true); if (mb_strlen($operand1[$row][$column]) > DataType::MAX_STRING_LENGTH) { $operand1 = ExcelError::CALC(); $errorFound = true; break; } } } } elseif (ErrorValue::isError($operand2, true) === true) { $operand1 = (string) $operand2; } else { $operand1 .= StringHelper::convertToString($operand2, true, '', true); if (mb_strlen($operand1) > DataType::MAX_STRING_LENGTH) { $operand1 = ExcelError::CALC(); } } return $operand1; } /** * TEXTJOIN. * * @param null|string|string[] $delimiter The delimiter to use between the joined arguments * Or can be an array of values * @param null|bool|bool[] $ignoreEmpty true/false Flag indicating whether empty arguments should be skipped * Or can be an array of values * @param mixed $args The values to join * * @return array|string The joined string * If an array of values is passed for the $delimiter or $ignoreEmpty arguments, then the returned result * will also be an array with matching dimensions */ public static function TEXTJOIN($delimiter = '', $ignoreEmpty = true, ...$args) { if (is_array($delimiter) || is_array($ignoreEmpty)) { return self::evaluateArrayArgumentsSubset( [self::class, __FUNCTION__], 2, $delimiter, $ignoreEmpty, ...$args ); } $delimiter ??= ''; $ignoreEmpty ??= true; /** @var array */ $aArgs = Functions::flattenArray($args); $returnValue = self::evaluateTextJoinArray($ignoreEmpty, $aArgs); $returnValue ??= implode($delimiter, $aArgs); if (StringHelper::countCharacters($returnValue) > DataType::MAX_STRING_LENGTH) { $returnValue = ExcelError::CALC(); } return $returnValue; } private static function evaluateTextJoinArray(bool $ignoreEmpty, array &$aArgs): ?string { foreach ($aArgs as $key => &$arg) { $value = Helpers::extractString($arg); if (ErrorValue::isError($value, true)) { return $value; } if ($ignoreEmpty === true && ((is_string($arg) && trim($arg) === '') || $arg === null)) { unset($aArgs[$key]); } elseif (is_bool($arg)) { $arg = Helpers::convertBooleanValue($arg); } } return null; } /** * REPT. * * Returns the result of builtin function round after validating args. * * @param mixed $stringValue The value to repeat * Or can be an array of values * @param mixed $repeatCount The number of times the string value should be repeated * Or can be an array of values * * @return array|string The repeated string * If an array of values is passed for the $stringValue or $repeatCount arguments, then the returned result * will also be an array with matching dimensions */ public static function builtinREPT($stringValue, $repeatCount) { if (is_array($stringValue) || is_array($repeatCount)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $stringValue, $repeatCount); } $stringValue = Helpers::extractString($stringValue); if (!is_numeric($repeatCount) || $repeatCount < 0) { $returnValue = ExcelError::VALUE(); } elseif (ErrorValue::isError($stringValue, true)) { $returnValue = $stringValue; } else { $returnValue = str_repeat($stringValue, (int) $repeatCount); if (StringHelper::countCharacters($returnValue) > DataType::MAX_STRING_LENGTH) { $returnValue = ExcelError::VALUE(); // note VALUE not CALC } } return $returnValue; } } PK!,4ȣ=libraries/vendor/PhpSpreadsheet/Calculation/TextData/Text.phpnu[getMessage(); } return mb_strlen($value, 'UTF-8'); } /** * Compares two text strings and returns TRUE if they are exactly the same, FALSE otherwise. * EXACT is case-sensitive but ignores formatting differences. * Use EXACT to test text being entered into a document. * * @param mixed $value1 String Value * Or can be an array of values * @param mixed $value2 String Value * Or can be an array of values * * @return array|bool|string If an array of values is passed for either of the arguments, then the returned result * will also be an array with matching dimensions */ public static function exact($value1, $value2) { if (is_array($value1) || is_array($value2)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $value1, $value2); } try { $value1 = Helpers::extractString($value1, true); $value2 = Helpers::extractString($value2, true); } catch (CalcExp $e) { return $e->getMessage(); } return $value2 === $value1; } /** * T. * * @param mixed $testValue Value to check * Or can be an array of values * * @return array|string If an array of values is passed for the argument, then the returned result * will also be an array with matching dimensions */ public static function test($testValue = '') { if (is_array($testValue)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $testValue); } if (is_string($testValue)) { return $testValue; } return ''; } /** * TEXTSPLIT. * * @param mixed $text the text that you're searching * @param null|array|string $columnDelimiter The text that marks the point where to spill the text across columns. * Multiple delimiters can be passed as an array of string values * @param null|array|string $rowDelimiter The text that marks the point where to spill the text down rows. * Multiple delimiters can be passed as an array of string values * @param bool $ignoreEmpty Specify FALSE to create an empty cell when two delimiters are consecutive. * true = create empty cells * false = skip empty cells * Defaults to TRUE, which creates an empty cell * @param bool $matchMode Determines whether the match is case-sensitive or not. * true = case-sensitive * false = case-insensitive * By default, a case-sensitive match is done. * @param mixed $padding The value with which to pad the result. * The default is #N/A. * * @return array|string the array built from the text, split by the row and column delimiters, or an error string */ public static function split($text, $columnDelimiter = null, $rowDelimiter = null, bool $ignoreEmpty = false, bool $matchMode = true, $padding = '#N/A') { $text = Functions::flattenSingleValue($text); if (ErrorValue::isError($text, true)) { return StringHelper::convertToString($text); } $flags = self::matchFlags($matchMode); if ($rowDelimiter !== null) { $delimiter = self::buildDelimiter($rowDelimiter); $rows = ($delimiter === '()') ? [$text] : Preg::split("/{$delimiter}/{$flags}", StringHelper::convertToString($text)); } else { $rows = [$text]; } if ($ignoreEmpty === true) { $rows = array_values(array_filter( $rows, fn ($row): bool => $row !== '' )); } if ($columnDelimiter !== null) { $delimiter = self::buildDelimiter($columnDelimiter); array_walk( $rows, function (&$row) use ($delimiter, $flags, $ignoreEmpty): void { $row = ($delimiter === '()') ? [$row] : Preg::split("/{$delimiter}/{$flags}", StringHelper::convertToString($row)); if ($ignoreEmpty === true) { $row = array_values(array_filter( $row, fn ($value): bool => $value !== '' )); } } ); if ($ignoreEmpty === true) { $rows = array_values(array_filter( $rows, fn ($row): bool => $row !== [] && $row !== [''] )); } } return self::applyPadding($rows, $padding); } /** * @param mixed $padding */ private static function applyPadding(array $rows, $padding): array { $columnCount = array_reduce( $rows, fn (int $counter, array $row): int => max($counter, count($row)), 0 ); return array_map( fn (array $row): array => (count($row) < $columnCount) ? array_merge($row, array_fill(0, $columnCount - count($row), $padding)) : $row, $rows ); } /** * @param null|array|string $delimiter the text that marks the point before which you want to split * Multiple delimiters can be passed as an array of string values */ private static function buildDelimiter($delimiter): string { $valueSet = Functions::flattenArray($delimiter); if (is_array($delimiter) && count($valueSet) > 1) { $quotedDelimiters = array_map( fn ($delimiter): string => preg_quote($delimiter ?? '', '/'), $valueSet ); $delimiters = implode('|', $quotedDelimiters); return '(' . $delimiters . ')'; } return '(' . preg_quote(StringHelper::convertToString(Functions::flattenSingleValue($delimiter)), '/') . ')'; } private static function matchFlags(bool $matchMode): string { return ($matchMode === true) ? 'miu' : 'mu'; } public static function fromArray(array $array, int $format = 0): string { $result = []; foreach ($array as $row) { $cells = []; foreach ($row as $cellValue) { $value = ($format === 1) ? self::formatValueMode1($cellValue) : self::formatValueMode0($cellValue); $cells[] = $value; } $result[] = implode(($format === 1) ? ',' : ', ', $cells); } $result = implode(($format === 1) ? ';' : ', ', $result); return ($format === 1) ? '{' . $result . '}' : $result; } /** * @param mixed $cellValue */ private static function formatValueMode0($cellValue): string { if (is_bool($cellValue)) { return Calculation::getLocaleBoolean($cellValue ? 'TRUE' : 'FALSE'); } return StringHelper::convertToString($cellValue); } /** * @param mixed $cellValue */ private static function formatValueMode1($cellValue): string { if (is_string($cellValue) && ErrorValue::isError($cellValue) === false) { return Calculation::FORMULA_STRING_QUOTE . $cellValue . Calculation::FORMULA_STRING_QUOTE; } elseif (is_bool($cellValue)) { return Calculation::getLocaleBoolean($cellValue ? 'TRUE' : 'FALSE'); } return StringHelper::convertToString($cellValue); } } PK!>libraries/vendor/PhpSpreadsheet/Calculation/TextData/index.phpnu[getMessage(); } $min = Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE ? 0 : 1; if ($character < $min || $character > 255) { return ExcelError::VALUE(); } $result = iconv('UCS-4LE', 'UTF-8', pack('V', $character)); return ($result === false) ? '' : $result; } /** * CODE. * * @param mixed $characters String character to convert to its ASCII value * Or can be an array of values * * @return array|int|string A string if arguments are invalid * If an array of values is passed as the argument, then the returned result will also be an array * with the same dimensions */ public static function code($characters) { if (is_array($characters)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $characters); } try { $characters = Helpers::extractString($characters, true); } catch (CalcExp $e) { return $e->getMessage(); } if ($characters === '') { return ExcelError::VALUE(); } $character = $characters; if (mb_strlen($characters, 'UTF-8') > 1) { $character = mb_substr($characters, 0, 1, 'UTF-8'); } return self::unicodeToOrd($character); } private static function unicodeToOrd(string $character): int { $retVal = 0; $iconv = iconv('UTF-8', 'UCS-4LE', $character); if ($iconv !== false) { /** @var false|int[] */ $result = unpack('V', $iconv); if (is_array($result) && isset($result[1])) { $retVal = $result[1]; } } return $retVal; } } PK!DRl=libraries/vendor/PhpSpreadsheet/Calculation/TextData/Trim.phpnu[>@libraries/vendor/PhpSpreadsheet/Calculation/TextData/Replace.phpnu[getMessage(); } $returnValue = $left . $newText . $right; if (StringHelper::countCharacters($returnValue) > DataType::MAX_STRING_LENGTH) { $returnValue = ExcelError::VALUE(); } return $returnValue; } /** * SUBSTITUTE. * * @param mixed $text The text string value to modify * Or can be an array of values * @param mixed $fromText The string value that we want to replace in $text * Or can be an array of values * @param mixed $toText The string value that we want to replace with in $text * Or can be an array of values * @param mixed $instance Integer instance Number for the occurrence of frmText to change * Or can be an array of values * * @return array|string If an array of values is passed for either of the arguments, then the returned result * will also be an array with matching dimensions */ public static function substitute($text = '', $fromText = '', $toText = '', $instance = null) { if (is_array($text) || is_array($fromText) || is_array($toText) || is_array($instance)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $text, $fromText, $toText, $instance); } try { $text = Helpers::extractString($text, true); $fromText = Helpers::extractString($fromText, true); $toText = Helpers::extractString($toText, true); if ($instance === null) { $returnValue = str_replace($fromText, $toText, $text); } else { if (is_bool($instance)) { if ($instance === false || Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_OPENOFFICE) { return ExcelError::Value(); } $instance = 1; } $instance = Helpers::extractInt($instance, 1, 0, true); $returnValue = self::executeSubstitution($text, $fromText, $toText, $instance); } } catch (CalcExp $e) { return $e->getMessage(); } if (StringHelper::countCharacters($returnValue) > DataType::MAX_STRING_LENGTH) { $returnValue = ExcelError::VALUE(); } return $returnValue; } private static function executeSubstitution(string $text, string $fromText, string $toText, int $instance): string { $pos = -1; while ($instance > 0) { $pos = mb_strpos($text, $fromText, $pos + 1, 'UTF-8'); if ($pos === false) { return $text; } --$instance; } return StringHelper::convertToString(Functions::scalar(self::REPLACE($text, ++$pos, StringHelper::countCharacters($fromText), $toText))); } } PK!,zK-K-@libraries/vendor/PhpSpreadsheet/Calculation/TextData/Extract.phpnu[getMessage(); } return mb_substr($value, 0, $chars, 'UTF-8'); } /** * MID. * * @param mixed $value String value from which to extract characters * Or can be an array of values * @param mixed $start Integer offset of the first character that we want to extract * Or can be an array of values * @param mixed $chars The number of characters to extract (as an integer) * Or can be an array of values * * @return array|string The joined string * If an array of values is passed for the $value, $start or $chars arguments, then the returned result * will also be an array with matching dimensions */ public static function mid($value, $start, $chars) { if (is_array($value) || is_array($start) || is_array($chars)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $start, $chars); } try { $value = Helpers::extractString($value, true); $start = Helpers::extractInt($start, 1); $chars = Helpers::extractInt($chars, 0); } catch (CalcExp $e) { return $e->getMessage(); } return mb_substr($value, --$start, $chars, 'UTF-8'); } /** * RIGHT. * * @param mixed $value String value from which to extract characters * Or can be an array of values * @param mixed $chars The number of characters to extract (as an integer) * Or can be an array of values * * @return array|string The joined string * If an array of values is passed for the $value or $chars arguments, then the returned result * will also be an array with matching dimensions */ public static function right($value, $chars = 1) { if (is_array($value) || is_array($chars)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $chars); } try { $value = Helpers::extractString($value, true); $chars = Helpers::extractInt($chars, 0, 1); } catch (CalcExp $e) { return $e->getMessage(); } return mb_substr($value, mb_strlen($value, 'UTF-8') - $chars, $chars, 'UTF-8'); } /** * TEXTBEFORE. * * @param mixed $text the text that you're searching * Or can be an array of values * @param null|array|string $delimiter the text that marks the point before which you want to extract * Multiple delimiters can be passed as an array of string values * @param mixed $instance The instance of the delimiter after which you want to extract the text. * By default, this is the first instance (1). * A negative value means start searching from the end of the text string. * Or can be an array of values * @param mixed $matchMode Determines whether the match is case-sensitive or not. * 0 - Case-sensitive * 1 - Case-insensitive * Or can be an array of values * @param mixed $matchEnd Treats the end of text as a delimiter. * 0 - Don't match the delimiter against the end of the text. * 1 - Match the delimiter against the end of the text. * Or can be an array of values * @param mixed $ifNotFound value to return if no match is found * The default is a #N/A Error * Or can be an array of values * * @return array|string the string extracted from text before the delimiter; or the $ifNotFound value * If an array of values is passed for any of the arguments, then the returned result * will also be an array with matching dimensions */ public static function before($text, $delimiter, $instance = 1, $matchMode = 0, $matchEnd = 0, $ifNotFound = '#N/A') { if (is_array($text) || is_array($instance) || is_array($matchMode) || is_array($matchEnd) || is_array($ifNotFound)) { return self::evaluateArrayArgumentsIgnore([self::class, __FUNCTION__], 1, $text, $delimiter, $instance, $matchMode, $matchEnd, $ifNotFound); } try { $text = Helpers::extractString($text ?? '', true); Helpers::extractString(Functions::flattenSingleValue($delimiter ?? ''), true); } catch (CalcExp $e) { return $e->getMessage(); } $instance = (int) StringHelper::convertToString($instance); $matchMode = (int) StringHelper::convertToString($matchMode); $matchEnd = (int) StringHelper::convertToString($matchEnd); $split = self::validateTextBeforeAfter($text, $delimiter, $instance, $matchMode, $matchEnd, $ifNotFound); if (is_string($split)) { return $split; } if (Helpers::extractString(Functions::flattenSingleValue($delimiter ?? '')) === '') { return ($instance > 0) ? '' : $text; } // Adjustment for a match as the first element of the split $flags = self::matchFlags($matchMode); $delimiter = self::buildDelimiter($delimiter); $adjust = preg_match('/^' . $delimiter . "\$/{$flags}", $split[0]); $oddReverseAdjustment = count($split) % 2; $split = ($instance < 0) ? array_slice($split, 0, max(count($split) - (abs($instance) * 2 - 1) - $adjust - $oddReverseAdjustment, 0)) : array_slice($split, 0, $instance * 2 - 1 - $adjust); return implode('', $split); } /** * TEXTAFTER. * * @param mixed $text the text that you're searching * @param null|array|string $delimiter the text that marks the point before which you want to extract * Multiple delimiters can be passed as an array of string values * @param mixed $instance The instance of the delimiter after which you want to extract the text. * By default, this is the first instance (1). * A negative value means start searching from the end of the text string. * Or can be an array of values * @param mixed $matchMode Determines whether the match is case-sensitive or not. * 0 - Case-sensitive * 1 - Case-insensitive * Or can be an array of values * @param mixed $matchEnd Treats the end of text as a delimiter. * 0 - Don't match the delimiter against the end of the text. * 1 - Match the delimiter against the end of the text. * Or can be an array of values * @param mixed $ifNotFound value to return if no match is found * The default is a #N/A Error * Or can be an array of values * * @return array|string the string extracted from text before the delimiter; or the $ifNotFound value * If an array of values is passed for any of the arguments, then the returned result * will also be an array with matching dimensions */ public static function after($text, $delimiter, $instance = 1, $matchMode = 0, $matchEnd = 0, $ifNotFound = '#N/A') { if (is_array($text) || is_array($instance) || is_array($matchMode) || is_array($matchEnd) || is_array($ifNotFound)) { return self::evaluateArrayArgumentsIgnore([self::class, __FUNCTION__], 1, $text, $delimiter, $instance, $matchMode, $matchEnd, $ifNotFound); } try { $text = Helpers::extractString($text ?? '', true); Helpers::extractString(Functions::flattenSingleValue($delimiter ?? ''), true); } catch (CalcExp $e) { return $e->getMessage(); } $instance = (int) StringHelper::convertToString($instance); $matchMode = (int) StringHelper::convertToString($matchMode); $matchEnd = (int) StringHelper::convertToString($matchEnd); $split = self::validateTextBeforeAfter($text, $delimiter, $instance, $matchMode, $matchEnd, $ifNotFound); if (is_string($split)) { return $split; } if (Helpers::extractString(Functions::flattenSingleValue($delimiter ?? '')) === '') { return ($instance < 0) ? '' : $text; } // Adjustment for a match as the first element of the split $flags = self::matchFlags($matchMode); $delimiter = self::buildDelimiter($delimiter); $adjust = preg_match('/^' . $delimiter . "\$/{$flags}", $split[0]); $oddReverseAdjustment = count($split) % 2; $split = ($instance < 0) ? array_slice($split, count($split) - ((int) abs($instance + 1) * 2) - $adjust - $oddReverseAdjustment) : array_slice($split, $instance * 2 - $adjust); return implode('', $split); } /** * @param null|mixed[]|string $delimiter * @return mixed[]|string * @param mixed $ifNotFound */ private static function validateTextBeforeAfter(string $text, $delimiter, int $instance, int $matchMode, int $matchEnd, $ifNotFound) { $flags = self::matchFlags($matchMode); $delimiter = self::buildDelimiter($delimiter); if (preg_match('/' . $delimiter . "/{$flags}", $text) === 0 && $matchEnd === 0) { return is_array($ifNotFound) ? $ifNotFound : StringHelper::convertToString($ifNotFound); } $split = preg_split('/' . $delimiter . "/{$flags}", $text, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); if ($split === false) { return ExcelError::NA(); } if ($instance === 0 || abs($instance) > StringHelper::countCharacters($text)) { return ExcelError::VALUE(); } if ($matchEnd === 0 && (abs($instance) > floor(count($split) / 2))) { return ExcelError::NA(); } elseif ($matchEnd !== 0 && (abs($instance) - 1 > ceil(count($split) / 2))) { return ExcelError::NA(); } return $split; } /** * @param null|array|string $delimiter the text that marks the point before which you want to extract * Multiple delimiters can be passed as an array of string values */ private static function buildDelimiter($delimiter): string { if (is_array($delimiter)) { $delimiter = Functions::flattenArray($delimiter); $quotedDelimiters = array_map( fn ($delimiter): string => preg_quote($delimiter ?? '', '/'), $delimiter ); $delimiters = implode('|', $quotedDelimiters); return '(' . $delimiters . ')'; } return '(' . preg_quote($delimiter ?? '', '/') . ')'; } private static function matchFlags(int $matchMode): string { return ($matchMode === 0) ? 'mu' : 'miu'; } } PK! Y33?libraries/vendor/PhpSpreadsheet/Calculation/TextData/Format.phpnu[getMessage(); } $mask = '$#,##0'; if ($decimals > 0) { $mask .= '.' . str_repeat('0', $decimals); } else { $round = 10 ** abs($decimals); if ($value < 0) { $round = 0 - $round; } /** @var float|int|string */ $value = MathTrig\Round::multiple($value, $round); } $mask = "{$mask};-{$mask}"; return NumberFormat::toFormattedString($value, $mask); } /** * FIXED. * * @param mixed $value The value to format * Or can be an array of values * @param mixed $decimals Integer value for the number of decimal places that should be formatted * Or can be an array of values * @param mixed $noCommas Boolean value indicating whether the value should have thousands separators or not * Or can be an array of values * * @return array|string If an array of values is passed for either of the arguments, then the returned result * will also be an array with matching dimensions */ public static function FIXEDFORMAT($value, $decimals = 2, $noCommas = false) { if (is_array($value) || is_array($decimals) || is_array($noCommas)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $decimals, $noCommas); } try { $value = Helpers::extractFloat($value); $decimals = Helpers::extractInt($decimals, -100, 0, true); } catch (CalcExp $e) { return $e->getMessage(); } $valueResult = round($value, $decimals); if ($decimals < 0) { $decimals = 0; } if ($noCommas === false) { $valueResult = number_format( $valueResult, $decimals, StringHelper::getDecimalSeparator(), StringHelper::getThousandsSeparator() ); } return (string) $valueResult; } /** * TEXT. * * @param mixed $value The value to format * Or can be an array of values * @param mixed $format A string with the Format mask that should be used * Or can be an array of values * * @return array|string If an array of values is passed for either of the arguments, then the returned result * will also be an array with matching dimensions */ public static function TEXTFORMAT($value, $format) { if (is_array($value) || is_array($format)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $format); } try { $value = Helpers::extractString($value, true); $format = Helpers::extractString($format, true); } catch (CalcExp $e) { return $e->getMessage(); } $format = (string) NumberFormat::convertSystemFormats($format); if (!is_numeric($value) && Date::isDateTimeFormatCode($format) && !Preg::isMatch('/^\s*\d+(\s+\d+)+\s*$/', $value)) { $value1 = DateTimeExcel\DateValue::fromString($value); $value2 = DateTimeExcel\TimeValue::fromString($value); /** @var float|int|string */ $value = (is_numeric($value1) && is_numeric($value2)) ? ($value1 + $value2) : (is_numeric($value1) ? $value1 : (is_numeric($value2) ? $value2 : $value)); } return (string) NumberFormat::toFormattedString($value, $format); } /** * @param mixed $value Value to check * @return mixed */ private static function convertValue($value, bool $spacesMeanZero = false) { $value = $value ?? 0; if (is_bool($value)) { if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) { $value = (int) $value; } else { throw new CalcExp(ExcelError::VALUE()); } } if (is_string($value)) { $value = trim($value); if (ErrorValue::isError($value, true)) { throw new CalcExp($value); } if ($spacesMeanZero && $value === '') { $value = 0; } } return $value; } /** * VALUE. * * @param mixed $value Value to check * Or can be an array of values * * @return array|DateTimeInterface|float|int|string A string if arguments are invalid * If an array of values is passed for the argument, then the returned result * will also be an array with matching dimensions */ public static function VALUE($value = '') { if (is_array($value)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value); } try { $value = self::convertValue($value); } catch (CalcExp $e) { return $e->getMessage(); } if (!is_numeric($value)) { $value = StringHelper::convertToString($value); $numberValue = str_replace( StringHelper::getThousandsSeparator(), '', trim($value, " \t\n\r\0\x0B" . StringHelper::getCurrencyCode()) ); if ($numberValue === '') { return ExcelError::VALUE(); } if (is_numeric($numberValue)) { return (float) $numberValue; } $dateSetting = Functions::getReturnDateType(); Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); if (str_contains($value, ':')) { $timeValue = Functions::scalar(DateTimeExcel\TimeValue::fromString($value)); if ($timeValue !== ExcelError::VALUE()) { Functions::setReturnDateType($dateSetting); return $timeValue; //* @phpstan-ignore-line } } $dateValue = Functions::scalar(DateTimeExcel\DateValue::fromString($value)); if ($dateValue !== ExcelError::VALUE()) { Functions::setReturnDateType($dateSetting); return $dateValue; //* @phpstan-ignore-line } Functions::setReturnDateType($dateSetting); return ExcelError::VALUE(); } return (float) $value; } /** * VALUETOTEXT. * * @param mixed $value The value to format * Or can be an array of values * * @return array|string If an array of values is passed for either of the arguments, then the returned result * will also be an array with matching dimensions * @param mixed $format */ public static function valueToText($value, $format = false) { if (is_array($value) || is_array($format)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $format); } $format = (bool) $format; if (is_object($value) && $value instanceof RichText) { $value = $value->getPlainText(); } if (is_string($value)) { $value = ($format === true) ? StringHelper::convertToString(Calculation::wrapResult($value)) : $value; $value = str_replace("\n", '', $value); } elseif (is_bool($value)) { $value = Calculation::getLocaleBoolean($value ? 'TRUE' : 'FALSE'); } return StringHelper::convertToString($value); } /** * @param mixed $decimalSeparator */ private static function getDecimalSeparator($decimalSeparator): string { return empty($decimalSeparator) ? StringHelper::getDecimalSeparator() : StringHelper::convertToString($decimalSeparator); } /** * @param mixed $groupSeparator */ private static function getGroupSeparator($groupSeparator): string { return empty($groupSeparator) ? StringHelper::getThousandsSeparator() : StringHelper::convertToString($groupSeparator); } /** * NUMBERVALUE. * * @param mixed $value The value to format * Or can be an array of values * @param mixed $decimalSeparator A string with the decimal separator to use, defaults to locale defined value * Or can be an array of values * @param mixed $groupSeparator A string with the group/thousands separator to use, defaults to locale defined value * Or can be an array of values * @return mixed[]|float|string */ public static function NUMBERVALUE($value = '', $decimalSeparator = null, $groupSeparator = null) { if (is_array($value) || is_array($decimalSeparator) || is_array($groupSeparator)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $decimalSeparator, $groupSeparator); } try { $value = self::convertValue($value, true); $decimalSeparator = self::getDecimalSeparator($decimalSeparator); $groupSeparator = self::getGroupSeparator($groupSeparator); } catch (CalcExp $e) { return $e->getMessage(); } /** @var null|array|scalar $value */ if (!is_array($value) && !is_numeric($value)) { $value = StringHelper::convertToString($value); $decimalPositions = Preg::matchAllWithOffsets('/' . preg_quote($decimalSeparator, '/') . '/', $value, $matches); if ($decimalPositions > 1) { return ExcelError::VALUE(); } $decimalOffset = array_pop($matches[0])[1] ?? null; if ($decimalOffset === null || strpos($value, $groupSeparator, $decimalOffset) !== false) { return ExcelError::VALUE(); } $value = str_replace([$groupSeparator, $decimalSeparator], ['', '.'], $value); // Handle the special case of trailing % signs $percentageString = rtrim($value, '%'); if (!is_numeric($percentageString)) { return ExcelError::VALUE(); } $percentageAdjustment = strlen($value) - strlen($percentageString); if ($percentageAdjustment) { $value = (float) $percentageString; $value /= 10 ** ($percentageAdjustment * 2); } } return is_array($value) ? ExcelError::VALUE() : (float) $value; } /** * NUMBER_FORMAT (Specific to TablePress). * * Formats a number with the . (period) as the decimal separator and the , (comma) as the thousands separator, rounded to a precision. * * The is a common number format in English-language regions. * * @param mixed $value The value to format. * @param mixed $decimals Optional. Integer value for the number of decimal places that should be formatted. Default 0. * @return string Formatted number. */ public static function NUMBER_FORMAT( $value, $decimals = 0 ) { $current_decimal_separator = StringHelper::getDecimalSeparator(); $current_thousands_separator = StringHelper::getThousandsSeparator(); StringHelper::setDecimalSeparator( '.' ); StringHelper::setThousandsSeparator( ',' ); $result = self::FIXEDFORMAT( $value, $decimals, false ); StringHelper::setDecimalSeparator( $current_decimal_separator ); StringHelper::setThousandsSeparator( $current_thousands_separator ); return $result; } /** * NUMBER_FORMAT_EU (Specific to TablePress). * * Formats a number with the , (comma) as the decimal separator and the . (period) as the thousands separator, rounded to a precision. * * The is a common number format in non-English-language regions, mainly in Europe. * * @param mixed $value The value to format. * @param mixed $decimals Optional. Integer value for the number of decimal places that should be formatted. Default 0. * @return string Formatted number. */ public static function NUMBER_FORMAT_EU( $value, $decimals = 0 ) { $current_decimal_separator = StringHelper::getDecimalSeparator(); $current_thousands_separator = StringHelper::getThousandsSeparator(); StringHelper::setDecimalSeparator( ',' ); StringHelper::setThousandsSeparator( '.' ); $result = self::FIXEDFORMAT( $value, $decimals, false ); StringHelper::setDecimalSeparator( $current_decimal_separator ); StringHelper::setThousandsSeparator( $current_thousands_separator ); return $result; } } PK!mG8F8F=libraries/vendor/PhpSpreadsheet/Calculation/FormulaParser.phpnu[<'; const OPERATORS_POSTFIX = '%'; /** * Formula. */ private string $formula; /** * Tokens. * * @var FormulaToken[] */ private array $tokens = []; /** * Create a new FormulaParser. * * @param ?string $formula Formula to parse */ public function __construct(?string $formula = '') { // Check parameters if ($formula === null) { throw new Exception('Invalid parameter passed: formula'); } // Initialise values $this->formula = trim($formula); // Parse! $this->parseToTokens(); } /** * Get Formula. */ public function getFormula(): string { return $this->formula; } /** * Get Token. * * @param int $id Token id */ public function getToken(int $id = 0): FormulaToken { if (isset($this->tokens[$id])) { return $this->tokens[$id]; } throw new Exception("Token with id $id does not exist."); } /** * Get Token count. */ public function getTokenCount(): int { return count($this->tokens); } /** * Get Tokens. * * @return FormulaToken[] */ public function getTokens(): array { return $this->tokens; } /** * Parse to tokens. */ private function parseToTokens(): void { // No attempt is made to verify formulas; assumes formulas are derived from Excel, where // they can only exist if valid; stack overflows/underflows sunk as nulls without exceptions. // Check if the formula has a valid starting = $formulaLength = strlen($this->formula); if ($formulaLength < 2 || $this->formula[0] != '=') { return; } // Helper variables $tokens1 = $tokens2 = $stack = []; $inString = $inPath = $inRange = $inError = false; $nextToken = null; //$token = $previousToken = null; $index = 1; $value = ''; $ERRORS = ['#NULL!', '#DIV/0!', '#VALUE!', '#REF!', '#NAME?', '#NUM!', '#N/A']; $COMPARATORS_MULTI = ['>=', '<=', '<>']; while ($index < $formulaLength) { // state-dependent character evaluation (order is important) // double-quoted strings // embeds are doubled // end marks token if ($inString) { if ($this->formula[$index] == self::QUOTE_DOUBLE) { if ((($index + 2) <= $formulaLength) && ($this->formula[$index + 1] == self::QUOTE_DOUBLE)) { $value .= self::QUOTE_DOUBLE; ++$index; } else { $inString = false; $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND, FormulaToken::TOKEN_SUBTYPE_TEXT); $value = ''; } } else { $value .= $this->formula[$index]; } ++$index; continue; } // single-quoted strings (links) // embeds are double // end does not mark a token if ($inPath) { if ($this->formula[$index] == self::QUOTE_SINGLE) { if ((($index + 2) <= $formulaLength) && ($this->formula[$index + 1] == self::QUOTE_SINGLE)) { $value .= self::QUOTE_SINGLE; ++$index; } else { $inPath = false; } } else { $value .= $this->formula[$index]; } ++$index; continue; } // bracked strings (R1C1 range index or linked workbook name) // no embeds (changed to "()" by Excel) // end does not mark a token if ($inRange) { if ($this->formula[$index] == self::BRACKET_CLOSE) { $inRange = false; } $value .= $this->formula[$index]; ++$index; continue; } // error values // end marks a token, determined from absolute list of values if ($inError) { $value .= $this->formula[$index]; ++$index; if (in_array($value, $ERRORS)) { $inError = false; $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND, FormulaToken::TOKEN_SUBTYPE_ERROR); $value = ''; } continue; } // scientific notation check if (str_contains(self::OPERATORS_SN, $this->formula[$index])) { if (strlen($value) > 1) { if (preg_match('/^[1-9]{1}(\.\d+)?E{1}$/', $this->formula[$index]) != 0) { $value .= $this->formula[$index]; ++$index; continue; } } } // independent character evaluation (order not important) // establish state-dependent character evaluations if ($this->formula[$index] == self::QUOTE_DOUBLE) { if ($value !== '') { // unexpected $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_UNKNOWN); $value = ''; } $inString = true; ++$index; continue; } if ($this->formula[$index] == self::QUOTE_SINGLE) { if ($value !== '') { // unexpected $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_UNKNOWN); $value = ''; } $inPath = true; ++$index; continue; } if ($this->formula[$index] == self::BRACKET_OPEN) { $inRange = true; $value .= self::BRACKET_OPEN; ++$index; continue; } if ($this->formula[$index] == self::ERROR_START) { if ($value !== '') { // unexpected $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_UNKNOWN); $value = ''; } $inError = true; $value .= self::ERROR_START; ++$index; continue; } // mark start and end of arrays and array rows if ($this->formula[$index] == self::BRACE_OPEN) { if ($value !== '') { // unexpected $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_UNKNOWN); $value = ''; } $tmp = new FormulaToken('ARRAY', FormulaToken::TOKEN_TYPE_FUNCTION, FormulaToken::TOKEN_SUBTYPE_START); $tokens1[] = $tmp; $stack[] = clone $tmp; $tmp = new FormulaToken('ARRAYROW', FormulaToken::TOKEN_TYPE_FUNCTION, FormulaToken::TOKEN_SUBTYPE_START); $tokens1[] = $tmp; $stack[] = clone $tmp; ++$index; continue; } if ($this->formula[$index] == self::SEMICOLON) { if ($value !== '') { $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND); $value = ''; } /** @var FormulaToken $tmp */ $tmp = array_pop($stack); $tmp->setValue(''); $tmp->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_STOP); $tokens1[] = $tmp; $tmp = new FormulaToken(',', FormulaToken::TOKEN_TYPE_ARGUMENT); $tokens1[] = $tmp; $tmp = new FormulaToken('ARRAYROW', FormulaToken::TOKEN_TYPE_FUNCTION, FormulaToken::TOKEN_SUBTYPE_START); $tokens1[] = $tmp; $stack[] = clone $tmp; ++$index; continue; } if ($this->formula[$index] == self::BRACE_CLOSE) { if ($value !== '') { $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND); $value = ''; } /** @var FormulaToken $tmp */ $tmp = array_pop($stack); $tmp->setValue(''); $tmp->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_STOP); $tokens1[] = $tmp; /** @var FormulaToken $tmp */ $tmp = array_pop($stack); $tmp->setValue(''); $tmp->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_STOP); $tokens1[] = $tmp; ++$index; continue; } // trim white-space if ($this->formula[$index] == self::WHITESPACE) { if ($value !== '') { $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND); $value = ''; } $tokens1[] = new FormulaToken('', FormulaToken::TOKEN_TYPE_WHITESPACE); ++$index; while (($this->formula[$index] == self::WHITESPACE) && ($index < $formulaLength)) { ++$index; } continue; } // multi-character comparators if (($index + 2) <= $formulaLength) { if (in_array(substr($this->formula, $index, 2), $COMPARATORS_MULTI)) { if ($value !== '') { $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND); $value = ''; } $tokens1[] = new FormulaToken(substr($this->formula, $index, 2), FormulaToken::TOKEN_TYPE_OPERATORINFIX, FormulaToken::TOKEN_SUBTYPE_LOGICAL); $index += 2; continue; } } // standard infix operators if (str_contains(self::OPERATORS_INFIX, $this->formula[$index])) { if ($value !== '') { $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND); $value = ''; } $tokens1[] = new FormulaToken($this->formula[$index], FormulaToken::TOKEN_TYPE_OPERATORINFIX); ++$index; continue; } // standard postfix operators (only one) if (str_contains(self::OPERATORS_POSTFIX, $this->formula[$index])) { if ($value !== '') { $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND); $value = ''; } $tokens1[] = new FormulaToken($this->formula[$index], FormulaToken::TOKEN_TYPE_OPERATORPOSTFIX); ++$index; continue; } // start subexpression or function if ($this->formula[$index] == self::PAREN_OPEN) { if ($value !== '') { $tmp = new FormulaToken($value, FormulaToken::TOKEN_TYPE_FUNCTION, FormulaToken::TOKEN_SUBTYPE_START); $tokens1[] = $tmp; $stack[] = clone $tmp; $value = ''; } else { $tmp = new FormulaToken('', FormulaToken::TOKEN_TYPE_SUBEXPRESSION, FormulaToken::TOKEN_SUBTYPE_START); $tokens1[] = $tmp; $stack[] = clone $tmp; } ++$index; continue; } // function, subexpression, or array parameters, or operand unions if ($this->formula[$index] == self::COMMA) { if ($value !== '') { $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND); $value = ''; } /** @var FormulaToken $tmp */ $tmp = array_pop($stack); $tmp->setValue(''); $tmp->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_STOP); $stack[] = $tmp; if ($tmp->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) { $tokens1[] = new FormulaToken(',', FormulaToken::TOKEN_TYPE_OPERATORINFIX, FormulaToken::TOKEN_SUBTYPE_UNION); } else { $tokens1[] = new FormulaToken(',', FormulaToken::TOKEN_TYPE_ARGUMENT); } ++$index; continue; } // stop subexpression if ($this->formula[$index] == self::PAREN_CLOSE) { if ($value !== '') { $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND); $value = ''; } /** @var FormulaToken $tmp */ $tmp = array_pop($stack); $tmp->setValue(''); $tmp->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_STOP); $tokens1[] = $tmp; ++$index; continue; } // token accumulation $value .= $this->formula[$index]; ++$index; } // dump remaining accumulation if ($value !== '') { $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND); } // move tokenList to new set, excluding unnecessary white-space tokens and converting necessary ones to intersections $tokenCount = count($tokens1); for ($i = 0; $i < $tokenCount; ++$i) { $token = $tokens1[$i]; if (isset($tokens1[$i - 1])) { $previousToken = $tokens1[$i - 1]; } else { $previousToken = null; } if (isset($tokens1[$i + 1])) { $nextToken = $tokens1[$i + 1]; } else { $nextToken = null; } if ($token->getTokenType() != FormulaToken::TOKEN_TYPE_WHITESPACE) { $tokens2[] = $token; continue; } if ($previousToken === null) { continue; } if ( !( (($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) && ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP)) || (($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_SUBEXPRESSION) && ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP)) || ($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND) ) ) { continue; } if ($nextToken === null) { continue; } if ( !( (($nextToken->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) && ($nextToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_START)) || (($nextToken->getTokenType() == FormulaToken::TOKEN_TYPE_SUBEXPRESSION) && ($nextToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_START)) || ($nextToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND) ) ) { continue; } $tokens2[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERATORINFIX, FormulaToken::TOKEN_SUBTYPE_INTERSECTION); } // move tokens to final list, switching infix "-" operators to prefix when appropriate, switching infix "+" operators // to noop when appropriate, identifying operand and infix-operator subtypes, and pulling "@" from function names $this->tokens = []; $tokenCount = count($tokens2); for ($i = 0; $i < $tokenCount; ++$i) { $token = $tokens2[$i]; if (isset($tokens2[$i - 1])) { $previousToken = $tokens2[$i - 1]; } else { $previousToken = null; } if ($token->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORINFIX && $token->getValue() == '-') { if ($i == 0) { $token->setTokenType(FormulaToken::TOKEN_TYPE_OPERATORPREFIX); } elseif ( (((($nullsafeVariable1 = $previousToken) ? $nullsafeVariable1->getTokenType() : null) == FormulaToken::TOKEN_TYPE_FUNCTION) && ((($nullsafeVariable2 = $previousToken) ? $nullsafeVariable2->getTokenSubType() : null) == FormulaToken::TOKEN_SUBTYPE_STOP)) || (((($nullsafeVariable3 = $previousToken) ? $nullsafeVariable3->getTokenType() : null) == FormulaToken::TOKEN_TYPE_SUBEXPRESSION) && ((($nullsafeVariable4 = $previousToken) ? $nullsafeVariable4->getTokenSubType() : null) == FormulaToken::TOKEN_SUBTYPE_STOP)) || ((($nullsafeVariable5 = $previousToken) ? $nullsafeVariable5->getTokenType() : null) == FormulaToken::TOKEN_TYPE_OPERATORPOSTFIX) || ((($nullsafeVariable6 = $previousToken) ? $nullsafeVariable6->getTokenType() : null) == FormulaToken::TOKEN_TYPE_OPERAND) ) { $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_MATH); } else { $token->setTokenType(FormulaToken::TOKEN_TYPE_OPERATORPREFIX); } $this->tokens[] = $token; continue; } if ($token->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORINFIX && $token->getValue() == '+') { if ($i == 0) { continue; } elseif ( (((($nullsafeVariable7 = $previousToken) ? $nullsafeVariable7->getTokenType() : null) == FormulaToken::TOKEN_TYPE_FUNCTION) && ((($nullsafeVariable8 = $previousToken) ? $nullsafeVariable8->getTokenSubType() : null) == FormulaToken::TOKEN_SUBTYPE_STOP)) || (((($nullsafeVariable9 = $previousToken) ? $nullsafeVariable9->getTokenType() : null) == FormulaToken::TOKEN_TYPE_SUBEXPRESSION) && ((($nullsafeVariable10 = $previousToken) ? $nullsafeVariable10->getTokenSubType() : null) == FormulaToken::TOKEN_SUBTYPE_STOP)) || ((($nullsafeVariable11 = $previousToken) ? $nullsafeVariable11->getTokenType() : null) == FormulaToken::TOKEN_TYPE_OPERATORPOSTFIX) || ((($nullsafeVariable12 = $previousToken) ? $nullsafeVariable12->getTokenType() : null) == FormulaToken::TOKEN_TYPE_OPERAND) ) { $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_MATH); } else { continue; } $this->tokens[] = $token; continue; } if ( $token->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORINFIX && $token->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_NOTHING ) { if (str_contains('<>=', substr($token->getValue(), 0, 1))) { $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_LOGICAL); } elseif ($token->getValue() == '&') { $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_CONCATENATION); } else { $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_MATH); } $this->tokens[] = $token; continue; } if ( $token->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND && $token->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_NOTHING ) { if (!is_numeric($token->getValue())) { if (strtoupper($token->getValue()) == 'TRUE' || strtoupper($token->getValue()) == 'FALSE') { $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_LOGICAL); } else { $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_RANGE); } } else { $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_NUMBER); } $this->tokens[] = $token; continue; } if ($token->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) { if ($token->getValue() !== '') { if (str_starts_with($token->getValue(), '@')) { $token->setValue(substr($token->getValue(), 1)); } } } $this->tokens[] = $token; } } } PK!9libraries/vendor/PhpSpreadsheet/Calculation/Web/index.phpnu[ 2048) { return ExcelError::VALUE(); // Invalid URL length } if (!preg_match('/^http[s]?:\/\//', $url)) { return ExcelError::VALUE(); // Invalid protocol } // Get results from the webservice $client = Settings::getHttpClient(); $requestFactory = Settings::getRequestFactory(); $request = $requestFactory->createRequest('GET', $url); try { $response = $client->sendRequest($request); } catch (ClientExceptionInterface $exception) { return ExcelError::VALUE(); // cURL error } if ($response->getStatusCode() != 200) { return ExcelError::VALUE(); // cURL error } $output = $response->getBody()->getContents(); if (strlen($output) > 32767) { return ExcelError::VALUE(); // Output not a string or too long } return $output; } /** * URLENCODE. * * Returns data from a web service on the Internet or Intranet. * * Excel Function: * urlEncode(text) * * @return string the url encoded output * @param mixed $text */ public static function urlEncode($text): string { if (!is_string($text)) { return ExcelError::VALUE(); } return str_replace('+', '%20', urlencode($text)); } } PK!Ն,6,6Alibraries/vendor/PhpSpreadsheet/Calculation/CalculationLocale.phpnu[ */ protected static array $localeBoolean = [ 'TRUE' => 'TRUE', 'FALSE' => 'FALSE', 'NULL' => 'NULL', ]; /** @var array> */ protected static array $falseTrueArray = []; public static function getLocaleBoolean(string $index): string { return self::$localeBoolean[$index]; } protected static function loadLocales(): void { $localeFileDirectory = __DIR__ . '/locale/'; $localeFileNames = glob($localeFileDirectory . '*', GLOB_ONLYDIR) ?: []; foreach ($localeFileNames as $filename) { $filename = substr($filename, strlen($localeFileDirectory)); if ($filename != 'en') { self::$validLocaleLanguages[] = $filename; } } } /** * Return the locale-specific translation of TRUE. * * @return string locale-specific translation of TRUE */ public static function getTRUE(): string { return self::$localeBoolean['TRUE']; } /** * Return the locale-specific translation of FALSE. * * @return string locale-specific translation of FALSE */ public static function getFALSE(): string { return self::$localeBoolean['FALSE']; } /** * Get the currently defined locale code. */ public function getLocale(): string { return self::$localeLanguage; } protected function getLocaleFile(string $localeDir, string $locale, string $language, string $file): string { $localeFileName = $localeDir . str_replace('_', DIRECTORY_SEPARATOR, $locale) . DIRECTORY_SEPARATOR . $file; if (!file_exists($localeFileName)) { // If there isn't a locale specific file, look for a language specific file $localeFileName = $localeDir . $language . DIRECTORY_SEPARATOR . $file; if (!file_exists($localeFileName)) { throw new Exception('Locale file not found'); } } return $localeFileName; } /** @return array> */ public function getFalseTrueArray(): array { if (!empty(self::$falseTrueArray)) { return self::$falseTrueArray; } if (count(self::$validLocaleLanguages) == 1) { self::loadLocales(); } $falseTrueArray = [['FALSE'], ['TRUE']]; foreach (self::$validLocaleLanguages as $language) { if (str_starts_with($language, 'en')) { continue; } $locale = $language; if (str_contains($locale, '_')) { [$language] = explode('_', $locale); } $localeDir = implode(DIRECTORY_SEPARATOR, [__DIR__, 'locale', null]); try { $functionNamesFile = $this->getLocaleFile($localeDir, $locale, $language, 'functions'); } catch (Exception $e) { continue; } // Retrieve the list of locale or language specific function names $localeFunctions = file($functionNamesFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: []; foreach ($localeFunctions as $localeFunction) { [$localeFunction] = explode('##', $localeFunction); // Strip out comments if (str_contains($localeFunction, '=')) { [$fName, $lfName] = array_map('trim', explode('=', $localeFunction)); if ($fName === 'FALSE') { $falseTrueArray[0][] = $lfName; } elseif ($fName === 'TRUE') { $falseTrueArray[1][] = $lfName; } } } } self::$falseTrueArray = $falseTrueArray; return $falseTrueArray; } /** * Set the locale code. * * @param string $locale The locale to use for formula translation, eg: 'en_us' */ public function setLocale(string $locale): bool { // Identify our locale and language $language = $locale = strtolower($locale); if (str_contains($locale, '_')) { [$language] = explode('_', $locale); } if (count(self::$validLocaleLanguages) == 1) { self::loadLocales(); } // Test whether we have any language data for this language (any locale) if (in_array($language, self::$validLocaleLanguages, true)) { // initialise language/locale settings self::$localeFunctions = []; self::$localeArgumentSeparator = ','; self::$localeBoolean = ['TRUE' => 'TRUE', 'FALSE' => 'FALSE', 'NULL' => 'NULL']; // Default is US English, if user isn't requesting US english, then read the necessary data from the locale files if ($locale !== 'en_us') { $localeDir = implode(DIRECTORY_SEPARATOR, [__DIR__, 'locale', null]); // Search for a file with a list of function names for locale try { $functionNamesFile = $this->getLocaleFile($localeDir, $locale, $language, 'functions'); } catch (Exception $e) { return false; } // Retrieve the list of locale or language specific function names $localeFunctions = file($functionNamesFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: []; $phpSpreadsheetFunctions = &self::getFunctionsAddress(); foreach ($localeFunctions as $localeFunction) { [$localeFunction] = explode('##', $localeFunction); // Strip out comments if (str_contains($localeFunction, '=')) { [$fName, $lfName] = array_map('trim', explode('=', $localeFunction)); if ((str_starts_with($fName, '*') || isset($phpSpreadsheetFunctions[$fName])) && ($lfName != '') && ($fName != $lfName)) { self::$localeFunctions[$fName] = $lfName; } } } // Default the TRUE and FALSE constants to the locale names of the TRUE() and FALSE() functions if (isset(self::$localeFunctions['TRUE'])) { self::$localeBoolean['TRUE'] = self::$localeFunctions['TRUE']; } if (isset(self::$localeFunctions['FALSE'])) { self::$localeBoolean['FALSE'] = self::$localeFunctions['FALSE']; } try { $configFile = $this->getLocaleFile($localeDir, $locale, $language, 'config'); } catch (Exception $exception) { return false; } $localeSettings = file($configFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: []; foreach ($localeSettings as $localeSetting) { [$localeSetting] = explode('##', $localeSetting); // Strip out comments if (str_contains($localeSetting, '=')) { [$settingName, $settingValue] = array_map('trim', explode('=', $localeSetting)); $settingName = strtoupper($settingName); if ($settingValue !== '') { switch ($settingName) { case 'ARGUMENTSEPARATOR': self::$localeArgumentSeparator = $settingValue; break; } } } } } self::$functionReplaceFromExcel = self::$functionReplaceToExcel = self::$functionReplaceFromLocale = self::$functionReplaceToLocale = null; self::$localeLanguage = $locale; return true; } return false; } public static function translateSeparator( string $fromSeparator, string $toSeparator, string $formula, int &$inBracesLevel, string $openBrace = self::FORMULA_OPEN_FUNCTION_BRACE, string $closeBrace = self::FORMULA_CLOSE_FUNCTION_BRACE ): string { $strlen = mb_strlen($formula); for ($i = 0; $i < $strlen; ++$i) { $chr = mb_substr($formula, $i, 1); switch ($chr) { case $openBrace: ++$inBracesLevel; break; case $closeBrace: --$inBracesLevel; break; case $fromSeparator: if ($inBracesLevel > 0) { $formula = mb_substr($formula, 0, $i) . $toSeparator . mb_substr($formula, $i + 1); } } } return $formula; } protected static function translateFormulaBlock( array $from, array $to, string $formula, int &$inFunctionBracesLevel, int &$inMatrixBracesLevel, string $fromSeparator, string $toSeparator ): string { // Function Names $formula = (string) preg_replace($from, $to, $formula); // Temporarily adjust matrix separators so that they won't be confused with function arguments $formula = self::translateSeparator(';', '|', $formula, $inMatrixBracesLevel, self::FORMULA_OPEN_MATRIX_BRACE, self::FORMULA_CLOSE_MATRIX_BRACE); $formula = self::translateSeparator(',', '!', $formula, $inMatrixBracesLevel, self::FORMULA_OPEN_MATRIX_BRACE, self::FORMULA_CLOSE_MATRIX_BRACE); // Function Argument Separators $formula = self::translateSeparator($fromSeparator, $toSeparator, $formula, $inFunctionBracesLevel); // Restore matrix separators $formula = self::translateSeparator('|', ';', $formula, $inMatrixBracesLevel, self::FORMULA_OPEN_MATRIX_BRACE, self::FORMULA_CLOSE_MATRIX_BRACE); $formula = self::translateSeparator('!', ',', $formula, $inMatrixBracesLevel, self::FORMULA_OPEN_MATRIX_BRACE, self::FORMULA_CLOSE_MATRIX_BRACE); return $formula; } protected static function translateFormula(array $from, array $to, string $formula, string $fromSeparator, string $toSeparator): string { // Convert any Excel function names and constant names to the required language; // and adjust function argument separators if (self::$localeLanguage !== 'en_us') { $inFunctionBracesLevel = 0; $inMatrixBracesLevel = 0; // If there is the possibility of separators within a quoted string, then we treat them as literals if (str_contains($formula, self::FORMULA_STRING_QUOTE)) { // So instead we skip replacing in any quoted strings by only replacing in every other array element // after we've exploded the formula $temp = explode(self::FORMULA_STRING_QUOTE, $formula); $notWithinQuotes = false; foreach ($temp as &$value) { // Only adjust in alternating array entries $notWithinQuotes = $notWithinQuotes === false; if ($notWithinQuotes === true) { $value = self::translateFormulaBlock($from, $to, $value, $inFunctionBracesLevel, $inMatrixBracesLevel, $fromSeparator, $toSeparator); } } unset($value); // Then rebuild the formula string $formula = implode(self::FORMULA_STRING_QUOTE, $temp); } else { // If there's no quoted strings, then we do a simple count/replace $formula = self::translateFormulaBlock($from, $to, $formula, $inFunctionBracesLevel, $inMatrixBracesLevel, $fromSeparator, $toSeparator); } } return $formula; } private static ?array $functionReplaceFromExcel; private static ?array $functionReplaceToLocale; public function translateFormulaToLocale(string $formula): string { $formula = preg_replace(self::CALCULATION_REGEXP_STRIP_XLFN_XLWS, '', $formula) ?? ''; // Build list of function names and constants for translation if (self::$functionReplaceFromExcel === null) { self::$functionReplaceFromExcel = []; foreach (array_keys(self::$localeFunctions) as $excelFunctionName) { self::$functionReplaceFromExcel[] = '/(@?[^\w\.])' . preg_quote($excelFunctionName, '/') . '([\s]*\()/ui'; } foreach (array_keys(self::$localeBoolean) as $excelBoolean) { self::$functionReplaceFromExcel[] = '/(@?[^\w\.])' . preg_quote($excelBoolean, '/') . '([^\w\.])/ui'; } } if (self::$functionReplaceToLocale === null) { self::$functionReplaceToLocale = []; foreach (self::$localeFunctions as $localeFunctionName) { self::$functionReplaceToLocale[] = '$1' . trim($localeFunctionName) . '$2'; } foreach (self::$localeBoolean as $localeBoolean) { self::$functionReplaceToLocale[] = '$1' . trim($localeBoolean) . '$2'; } } return self::translateFormula( self::$functionReplaceFromExcel, self::$functionReplaceToLocale, $formula, ',', self::$localeArgumentSeparator ); } protected static ?array $functionReplaceFromLocale; protected static ?array $functionReplaceToExcel; public function translateFormulaToEnglish(string $formula): string { if (self::$functionReplaceFromLocale === null) { self::$functionReplaceFromLocale = []; foreach (self::$localeFunctions as $localeFunctionName) { self::$functionReplaceFromLocale[] = '/(@?[^\w\.])' . preg_quote($localeFunctionName, '/') . '([\s]*\()/ui'; } foreach (self::$localeBoolean as $excelBoolean) { self::$functionReplaceFromLocale[] = '/(@?[^\w\.])' . preg_quote($excelBoolean, '/') . '([^\w\.])/ui'; } } if (self::$functionReplaceToExcel === null) { self::$functionReplaceToExcel = []; foreach (array_keys(self::$localeFunctions) as $excelFunctionName) { self::$functionReplaceToExcel[] = '$1' . trim($excelFunctionName) . '$2'; } foreach (array_keys(self::$localeBoolean) as $excelBoolean) { self::$functionReplaceToExcel[] = '$1' . trim($excelBoolean) . '$2'; } } return self::translateFormula(self::$functionReplaceFromLocale, self::$functionReplaceToExcel, $formula, self::$localeArgumentSeparator, ','); } public static function localeFunc(string $function): string { if (self::$localeLanguage !== 'en_us') { $functionName = trim($function, '('); if (isset(self::$localeFunctions[$functionName])) { $brace = ($functionName != $function); $function = self::$localeFunctions[$functionName]; if ($brace) { $function .= '('; } } } return $function; } } PK!U9libraries/vendor/PhpSpreadsheet/Calculation/Exception.phpnu[line = $line; $e->file = $file; throw $e; } } PK!R%_Clibraries/vendor/PhpSpreadsheet/Calculation/LookupRef/Hyperlink.phpnu[getHyperlink() ->setUrl($linkURL); $cell->getHyperlink()->setTooltip($displayName); return $displayName; } } PK!Ӟv!!Alibraries/vendor/PhpSpreadsheet/Calculation/LookupRef/HLookup.phpnu[getMessage(); } $f = array_keys($lookupArray); $firstRow = reset($f); if ((!is_array($lookupArray[$firstRow])) || ($indexNumber > count($lookupArray))) { return ExcelError::REF(); } $firstkey = $f[0] - 1; $returnColumn = $firstkey + $indexNumber; $firstColumn = array_shift($f) ?? 1; $rowNumber = self::hLookupSearch($lookupValue, $lookupArray, $firstColumn, $notExactMatch); if ($rowNumber !== null) { // otherwise return the appropriate value return $lookupArray[$returnColumn][Coordinate::stringFromColumnIndex($rowNumber)]; } return ExcelError::NA(); } /** * @param mixed $lookupValue The value that you want to match in lookup_array * @param int|string $column */ private static function hLookupSearch($lookupValue, array $lookupArray, $column, bool $notExactMatch): ?int { $lookupLower = StringHelper::strToLower(StringHelper::convertToString($lookupValue)); $rowNumber = null; foreach ($lookupArray[$column] as $rowKey => $rowData) { // break if we have passed possible keys $bothNumeric = is_numeric($lookupValue) && is_numeric($rowData); $bothNotNumeric = !is_numeric($lookupValue) && !is_numeric($rowData); $cellDataLower = StringHelper::strToLower((string) $rowData); if ( $notExactMatch && (($bothNumeric && $rowData > $lookupValue) || ($bothNotNumeric && $cellDataLower > $lookupLower)) ) { break; } $rowNumber = self::checkMatch( $bothNumeric, $bothNotNumeric, $notExactMatch, Coordinate::columnIndexFromString($rowKey), $cellDataLower, $lookupLower, $rowNumber ); } return $rowNumber; } private static function convertLiteralArray(array $lookupArray): array { if (array_key_exists(0, $lookupArray)) { $lookupArray2 = []; $row = 0; foreach ($lookupArray as $arrayVal) { ++$row; if (!is_array($arrayVal)) { $arrayVal = [$arrayVal]; } $arrayVal2 = []; foreach ($arrayVal as $key2 => $val2) { $index = Coordinate::stringFromColumnIndex($key2 + 1); $arrayVal2[$index] = $val2; } $lookupArray2[$row] = $arrayVal2; } $lookupArray = $lookupArray2; } return $lookupArray; } } PK! Alibraries/vendor/PhpSpreadsheet/Calculation/LookupRef/Address.phpnu[ '') { if (str_contains($sheetName, ' ') || str_contains($sheetName, '[')) { $sheetName = "'{$sheetName}'"; } $sheetName .= '!'; } return $sheetName; } private static function formatAsA1(int $row, int $column, int $relativity, string $sheetName): string { $rowRelative = $columnRelative = '$'; if (($relativity == self::ADDRESS_COLUMN_RELATIVE) || ($relativity == self::ADDRESS_RELATIVE)) { $columnRelative = ''; } if (($relativity == self::ADDRESS_ROW_RELATIVE) || ($relativity == self::ADDRESS_RELATIVE)) { $rowRelative = ''; } $column = Coordinate::stringFromColumnIndex($column); return "{$sheetName}{$columnRelative}{$column}{$rowRelative}{$row}"; } private static function formatAsR1C1(int $row, int $column, int $relativity, string $sheetName): string { if (($relativity == self::ADDRESS_COLUMN_RELATIVE) || ($relativity == self::ADDRESS_RELATIVE)) { $column = "[{$column}]"; } if (($relativity == self::ADDRESS_ROW_RELATIVE) || ($relativity == self::ADDRESS_RELATIVE)) { $row = "[{$row}]"; } [$rowChar, $colChar] = AddressHelper::getRowAndColumnChars(); return "{$sheetName}$rowChar{$row}$colChar{$column}"; } } PK!dP++>libraries/vendor/PhpSpreadsheet/Calculation/LookupRef/Sort.phpnu[getMessage(); } /** @var array $sortOrder */ // We want a simple, enumrated array of arrays where we can reference column by its index number. $sortArray = array_values(array_map('array_values', $sortArray)); return ($byColumn === true) ? self::sortByColumn($sortArray, $sortIndex, $sortOrder) : self::sortByRow($sortArray, $sortIndex, $sortOrder); } /** * SORTBY * The SORTBY function sorts the contents of a range or array based on the values in a corresponding range or array. * The returned array is the same shape as the provided array argument. * Both $sortIndex and $sortOrder can be arrays, to provide multi-level sorting. * * @param mixed $sortArray The range of cells being sorted * @param mixed $args * At least one additional argument must be provided, The vector or range to sort on * After that, arguments are passed as pairs: * sort order: ascending or descending * Ascending = 1 (self::ORDER_ASCENDING) * Descending = -1 (self::ORDER_DESCENDING) * additional arrays or ranges for multi-level sorting * * @return mixed The sorted values from the sort range */ public static function sortBy($sortArray, ...$args) { if (!is_array($sortArray)) { // Scalars are always returned "as is" return $sortArray; } $sortArray = self::enumerateArrayKeys($sortArray); $lookupArraySize = count($sortArray); $argumentCount = count($args); try { $sortBy = $sortOrder = []; for ($i = 0; $i < $argumentCount; $i += 2) { $sortBy[] = self::validateSortVector($args[$i], $lookupArraySize); $sortOrder[] = self::validateSortOrder($args[$i + 1] ?? self::ORDER_ASCENDING); } } catch (Exception $e) { return $e->getMessage(); } return self::processSortBy($sortArray, $sortBy, $sortOrder); } private static function enumerateArrayKeys(array $sortArray): array { array_walk( $sortArray, function (&$columns): void { if (is_array($columns)) { $columns = array_values($columns); } } ); return array_values($sortArray); } /** * @param mixed $sortIndex * @param mixed $sortOrder */ private static function validateScalarArgumentsForSort(&$sortIndex, &$sortOrder, int $sortArraySize): void { if (is_array($sortIndex) || is_array($sortOrder)) { throw new Exception(ExcelError::VALUE()); } $sortIndex = self::validatePositiveInt($sortIndex, false); if ($sortIndex > $sortArraySize) { throw new Exception(ExcelError::VALUE()); } $sortOrder = self::validateSortOrder($sortOrder); } /** * @param mixed $sortVector */ private static function validateSortVector($sortVector, int $sortArraySize): array { if (!is_array($sortVector)) { throw new Exception(ExcelError::VALUE()); } // It doesn't matter if it's a row or a column vectors, it works either way $sortVector = Functions::flattenArray($sortVector); if (count($sortVector) !== $sortArraySize) { throw new Exception(ExcelError::VALUE()); } return $sortVector; } /** * @param mixed $sortOrder */ private static function validateSortOrder($sortOrder): int { $sortOrder = self::validateInt($sortOrder); if (($sortOrder == self::ORDER_ASCENDING || $sortOrder === self::ORDER_DESCENDING) === false) { throw new Exception(ExcelError::VALUE()); } return $sortOrder; } /** * @param mixed $sortOrder */ private static function validateArrayArgumentsForSort(array &$sortIndex, &$sortOrder, int $sortArraySize): void { // It doesn't matter if they're row or column vectors, it works either way $sortIndex = Functions::flattenArray($sortIndex); $sortOrder = Functions::flattenArray($sortOrder); if ( count($sortOrder) === 0 || count($sortOrder) > $sortArraySize || (count($sortOrder) > count($sortIndex)) ) { throw new Exception(ExcelError::VALUE()); } if (count($sortIndex) > count($sortOrder)) { // If $sortOrder has fewer elements than $sortIndex, then the last order element is repeated. $sortOrder = array_merge( $sortOrder, array_fill(0, count($sortIndex) - count($sortOrder), array_pop($sortOrder)) ); } foreach ($sortIndex as $key => &$value) { self::validateScalarArgumentsForSort($value, $sortOrder[$key], $sortArraySize); } } private static function prepareSortVectorValues(array $sortVector): array { // Strings should be sorted case-insensitive; with booleans converted to locale-strings return array_map( function ($value) { if (is_bool($value)) { return ($value) ? Calculation::getTRUE() : Calculation::getFALSE(); } elseif (is_string($value)) { return StringHelper::strToLower($value); } return $value; }, $sortVector ); } /** * @param array[] $sortIndex * @param int[] $sortOrder */ private static function processSortBy(array $sortArray, array $sortIndex, array $sortOrder): array { $sortArguments = []; $sortData = []; foreach ($sortIndex as $index => $sortValues) { $sortData[] = $sortValues; $sortArguments[] = self::prepareSortVectorValues($sortValues); $sortArguments[] = $sortOrder[$index] === self::ORDER_ASCENDING ? SORT_ASC : SORT_DESC; } $sortArguments = self::applyPHP7Patch($sortArray, $sortArguments); $sortVector = self::executeVectorSortQuery($sortData, $sortArguments); return self::sortLookupArrayFromVector($sortArray, $sortVector); } /** * @param int[] $sortIndex * @param int[] $sortOrder */ private static function sortByRow(array $sortArray, array $sortIndex, array $sortOrder): array { $sortVector = self::buildVectorForSort($sortArray, $sortIndex, $sortOrder); return self::sortLookupArrayFromVector($sortArray, $sortVector); } /** * @param int[] $sortIndex * @param int[] $sortOrder */ private static function sortByColumn(array $sortArray, array $sortIndex, array $sortOrder): array { $sortArray = Matrix::transpose($sortArray); $result = self::sortByRow($sortArray, $sortIndex, $sortOrder); return Matrix::transpose($result); } /** * @param int[] $sortIndex * @param int[] $sortOrder */ private static function buildVectorForSort(array $sortArray, array $sortIndex, array $sortOrder): array { $sortArguments = []; $sortData = []; foreach ($sortIndex as $index => $sortIndexValue) { $sortValues = array_column($sortArray, $sortIndexValue - 1); $sortData[] = $sortValues; $sortArguments[] = self::prepareSortVectorValues($sortValues); $sortArguments[] = $sortOrder[$index] === self::ORDER_ASCENDING ? SORT_ASC : SORT_DESC; } $sortArguments = self::applyPHP7Patch($sortArray, $sortArguments); $sortData = self::executeVectorSortQuery($sortData, $sortArguments); return $sortData; } private static function executeVectorSortQuery(array $sortData, array $sortArguments): array { $sortData = Matrix::transpose($sortData); // We need to set an index that can be retained, as array_multisort doesn't maintain numeric keys. $sortDataIndexed = []; foreach ($sortData as $key => $value) { $sortDataIndexed[Coordinate::stringFromColumnIndex($key + 1)] = $value; } unset($sortData); $sortArguments[] = &$sortDataIndexed; array_multisort(...$sortArguments); // After the sort, we restore the numeric keys that will now be in the correct, sorted order $sortedData = []; foreach (array_keys($sortDataIndexed) as $key) { $sortedData[] = Coordinate::columnIndexFromString($key) - 1; } return $sortedData; } private static function sortLookupArrayFromVector(array $sortArray, array $sortVector): array { // Building a new array in the correct (sorted) order works; but may be memory heavy for larger arrays $sortedArray = []; foreach ($sortVector as $index) { $sortedArray[] = $sortArray[$index]; } return $sortedArray; // uksort( // $lookupArray, // function (int $a, int $b) use (array $sortVector) { // return $sortVector[$a] <=> $sortVector[$b]; // } // ); // // return $lookupArray; } /** * Hack to handle PHP 7: * From PHP 8.0.0, If two members compare as equal in a sort, they retain their original order; * but prior to PHP 8.0.0, their relative order in the sorted array was undefined. * MS Excel replicates the PHP 8.0.0 behaviour, retaining the original order of matching elements. * To replicate that behaviour with PHP 7, we add an extra sort based on the row index. */ private static function applyPHP7Patch(array $sortArray, array $sortArguments): array { if (PHP_VERSION_ID < 80000) { $sortArguments[] = range(1, count($sortArray)); $sortArguments[] = SORT_ASC; } return $sortArguments; } } PK!$7Nlibraries/vendor/PhpSpreadsheet/Calculation/LookupRef/LookupRefValidations.phpnu[ 1 && (count($values, COUNT_NORMAL) === 1 || count($values, COUNT_RECURSIVE) === count($values, COUNT_NORMAL)); } /** * TRANSPOSE. * * @param array|mixed $matrixData A matrix of values */ public static function transpose($matrixData): array { $returnMatrix = []; if (!is_array($matrixData)) { $matrixData = [[$matrixData]]; } $column = 0; /** @var iterable $matrixData */ foreach ($matrixData as $matrixRow) { $row = 0; foreach ($matrixRow as $matrixCell) { $returnMatrix[$row][$column] = $matrixCell; ++$row; } ++$column; } return $returnMatrix; } /** * INDEX. * * Uses an index to choose a value from a reference or array * * Excel Function: * =INDEX(range_array, row_num, [column_num], [area_num]) * * @param mixed $matrix A range of cells or an array constant * @param mixed $rowNum The row in the array or range from which to return a value. * If row_num is omitted, column_num is required. * Or can be an array of values * @param mixed $columnNum The column in the array or range from which to return a value. * If column_num is omitted, row_num is required. * Or can be an array of values * * TODO Provide support for area_num, currently not supported * * @return mixed the value of a specified cell or array of cells * If an array of values is passed as the $rowNum and/or $columnNum arguments, then the returned result * will also be an array with the same dimensions */ public static function index($matrix, $rowNum = 0, $columnNum = null) { if (is_array($rowNum) || is_array($columnNum)) { return self::evaluateArrayArgumentsSubsetFrom([self::class, __FUNCTION__], 1, $matrix, $rowNum, $columnNum); } $rowNum = $rowNum ?? 0; $columnNum = $columnNum ?? 0; if (is_scalar($matrix)) { if ($rowNum === 0 || $rowNum === 1) { if ($columnNum === 0 || $columnNum === 1) { if ($columnNum === 1 || $rowNum === 1) { return $matrix; } } } } try { $rowNum = LookupRefValidations::validatePositiveInt($rowNum); $columnNum = LookupRefValidations::validatePositiveInt($columnNum); } catch (Exception $e) { return $e->getMessage(); } if (is_array($matrix) && count($matrix) === 1 && $rowNum > 1) { $matrixKey = array_keys($matrix)[0]; if (is_array($matrix[$matrixKey])) { $tempMatrix = []; foreach ($matrix[$matrixKey] as $key => $value) { $tempMatrix[$key] = [$value]; } $matrix = $tempMatrix; } } if (!is_array($matrix) || ($rowNum > count($matrix))) { return ExcelError::REF(); } $rowKeys = array_keys($matrix); $columnKeys = @array_keys($matrix[$rowKeys[0]]); //* @phpstan-ignore-line if ($columnNum > count($columnKeys)) { return ExcelError::REF(); } if ($columnNum === 0) { return self::extractRowValue($matrix, $rowKeys, $rowNum); } $columnNum = $columnKeys[--$columnNum]; if ($rowNum === 0) { return array_map( fn ($value): array => [$value], array_column($matrix, $columnNum) ); } $rowNum = $rowKeys[--$rowNum]; /** @var array[] $matrix */ return $matrix[$rowNum][$columnNum]; } /** * @return mixed */ private static function extractRowValue(array $matrix, array $rowKeys, int $rowNum) { if ($rowNum === 0) { return $matrix; } $rowNum = $rowKeys[--$rowNum]; $row = $matrix[$rowNum]; if (is_array($row)) { return [$rowNum => $row]; } return $row; } } PK!+ 1 1 @libraries/vendor/PhpSpreadsheet/Calculation/LookupRef/Lookup.phpnu[ 1) || (!$hasResultVector && $lookupRows === 2 && $lookupColumns !== 2)) { $lookupVector = Matrix::transpose($lookupVector); $lookupRows = self::rowCount($lookupVector); $lookupColumns = self::columnCount($lookupVector); } $resultVector = self::verifyResultVector($resultVector ?? $lookupVector); //* @phpstan-ignore-line if ($lookupRows === 2 && !$hasResultVector) { $resultVector = array_pop($lookupVector); $lookupVector = array_shift($lookupVector); } /** @var array $lookupVector */ /** @var array $resultVector */ if ($lookupColumns !== 2) { $lookupVector = self::verifyLookupValues($lookupVector, $resultVector); } return VLookup::lookup($lookupValue, $lookupVector, 2); } private static function verifyLookupValues(array $lookupVector, array $resultVector): array { foreach ($lookupVector as &$value) { if (is_array($value)) { $k = array_keys($value); $key1 = $key2 = array_shift($k); ++$key2; $dataValue1 = $value[$key1]; } else { $key1 = 0; $key2 = 1; $dataValue1 = $value; } $dataValue2 = array_shift($resultVector); if (is_array($dataValue2)) { $dataValue2 = array_shift($dataValue2); } $value = [$key1 => $dataValue1, $key2 => $dataValue2]; } unset($value); return $lookupVector; } private static function verifyResultVector(array $resultVector): array { $resultRows = self::rowCount($resultVector); $resultColumns = self::columnCount($resultVector); // we correctly orient our results if ($resultRows === 1 && $resultColumns > 1) { $resultVector = Matrix::transpose($resultVector); } return $resultVector; } private static function rowCount(array $dataArray): int { return count($dataArray); } private static function columnCount(array $dataArray): int { $rowKeys = array_keys($dataArray); $row = array_shift($rowKeys); return count($dataArray[$row]); } } PK!tGlibraries/vendor/PhpSpreadsheet/Calculation/LookupRef/ChooseRowsEtc.phpnu[ [$x]) : null, ...$array)); // @phpstan-ignore-line } /** @return mixed[] * @param mixed $array */ private static function arrayValues($array): array { return is_array($array) ? array_values($array) : [$array]; } /** * CHOOSECOLS. * * @param mixed $input expecting two-dimensional array * * @return mixed[]|string * @param mixed ...$args */ public static function chooseCols($input, ...$args) { if (!is_array($input)) { $input = [[$input]]; } $retval = self::chooseRows(self::transpose($input), ...$args); return is_array($retval) ? self::transpose($retval) : $retval; } /** * CHOOSEROWS. * * @param mixed $input expecting two-dimensional array * * @return mixed[]|string * @param mixed ...$args */ public static function chooseRows($input, ...$args) { if (!is_array($input)) { $input = [[$input]]; } $inputArray = [[]]; // no row 0 $numRows = 0; foreach ($input as $inputRow) { $inputArray[] = self::arrayValues($inputRow); ++$numRows; } $outputArray = []; foreach (Functions::flattenArray2(...$args) as $arg) { if (!is_numeric($arg)) { return ExcelError::VALUE(); } $index = (int) $arg; if ($index < 0) { $index += $numRows + 1; } if ($index <= 0 || $index > $numRows) { return ExcelError::VALUE(); } $outputArray[] = $inputArray[$index]; } return $outputArray; } /** * @return mixed[]|string * @param mixed $offset */ private static function dropRows(array $array, $offset) { if ($offset === null) { return $array; } if (!is_numeric($offset)) { return ExcelError::VALUE(); } $offset = (int) $offset; $count = count($array); if (abs($offset) >= $count) { // In theory, this should be #CALC!, but Excel treats // #CALC! as corrupt, and it's not worth figuring out why return ExcelError::VALUE(); } if ($offset === 0) { return $array; } if ($offset > 0) { return array_slice($array, $offset); } return array_slice($array, 0, $count + $offset); } /** * DROP. * * @param mixed $input expect two-dimensional array * * @return mixed[]|string * @param mixed $rows * @param mixed $columns */ public static function drop($input, $rows = null, $columns = null) { if (!is_array($input)) { $input = [[$input]]; } $inputArray = []; // no row 0 foreach ($input as $inputRow) { $inputArray[] = self::arrayValues($inputRow); } $outputArray1 = self::dropRows($inputArray, $rows); if (is_string($outputArray1)) { return $outputArray1; } $outputArray2 = self::transpose($outputArray1); $outputArray3 = self::dropRows($outputArray2, $columns); if (is_string($outputArray3)) { return $outputArray3; } return self::transpose($outputArray3); } /** * @return mixed[]|string * @param mixed $offset */ private static function takeRows(array $array, $offset) { if ($offset === null) { return $array; } if (!is_numeric($offset)) { return ExcelError::VALUE(); } $offset = (int) $offset; if ($offset === 0) { // should be #CALC! - see above return ExcelError::VALUE(); } $count = count($array); if (abs($offset) >= $count) { return $array; } if ($offset > 0) { return array_slice($array, 0, $offset); } return array_slice($array, $count + $offset); } /** * TAKE. * * @param mixed $input expecting two-dimensional array * * @return mixed[]|string * @param mixed $rows * @param mixed $columns */ public static function take($input, $rows, $columns = null) { if (!is_array($input)) { $input = [[$input]]; } if ($rows === null && $columns === null) { return $input; } $inputArray = []; foreach ($input as $inputRow) { $inputArray[] = self::arrayValues($inputRow); } $outputArray1 = self::takeRows($inputArray, $rows); if (is_string($outputArray1)) { return $outputArray1; } $outputArray2 = self::transpose($outputArray1); $outputArray3 = self::takeRows($outputArray2, $columns); if (is_string($outputArray3)) { return $outputArray3; } return self::transpose($outputArray3); } /** * EXPAND. * * @param mixed $input expecting two-dimensional array * * @return mixed[]|string * @param mixed $rows * @param mixed $columns * @param mixed $pad */ public static function expand($input, $rows, $columns = null, $pad = '#N/A') { if (!is_array($input)) { $input = [[$input]]; } if ($rows === null && $columns === null) { return $input; } $numRows = count($input); $rows ??= $numRows; if (!is_numeric($rows)) { return ExcelError::VALUE(); } $rows = (int) $rows; if ($rows < count($input)) { return ExcelError::VALUE(); } $numCols = 0; foreach ($input as $inputRow) { $numCols = max($numCols, is_array($inputRow) ? count($inputRow) : 1); } $columns ??= $numCols; if (!is_numeric($columns)) { return ExcelError::VALUE(); } $columns = (int) $columns; if ($columns < $numCols) { return ExcelError::VALUE(); } $inputArray = []; foreach ($input as $inputRow) { $inputArray[] = array_pad(self::arrayValues($inputRow), $columns, $pad); } $outputArray = []; $padRow = array_pad([], $columns, $pad); for ($count = 0; $count < $rows; ++$count) { $outputArray[] = ($count >= $numRows) ? $padRow : $inputArray[$count]; } return $outputArray; } } PK!C: Alibraries/vendor/PhpSpreadsheet/Calculation/LookupRef/VLookup.phpnu[getMessage(); } $f = array_keys($lookupArray); $firstRow = array_pop($f); if ((!is_array($lookupArray[$firstRow])) || ($indexNumber > count($lookupArray[$firstRow]))) { return ExcelError::REF(); } $columnKeys = array_keys($lookupArray[$firstRow]); $returnColumn = $columnKeys[--$indexNumber]; $firstColumn = array_shift($columnKeys) ?? 1; if (!$notExactMatch) { /** @var callable $callable */ $callable = [self::class, 'vlookupSort']; uasort($lookupArray, $callable); } $rowNumber = self::vLookupSearch($lookupValue, $lookupArray, $firstColumn, $notExactMatch); if ($rowNumber !== null) { // return the appropriate value return $lookupArray[$rowNumber][$returnColumn]; } return ExcelError::NA(); } private static function vlookupSort(array $a, array $b): int { reset($a); $firstColumn = key($a); $aLower = StringHelper::strToLower((string) $a[$firstColumn]); $bLower = StringHelper::strToLower((string) $b[$firstColumn]); if ($aLower == $bLower) { return 0; } return ($aLower < $bLower) ? -1 : 1; } /** * @param mixed $lookupValue The value that you want to match in lookup_array * @param int|string $column */ private static function vLookupSearch($lookupValue, array $lookupArray, $column, bool $notExactMatch): ?int { $lookupLower = StringHelper::strToLower(StringHelper::convertToString($lookupValue)); $rowNumber = null; foreach ($lookupArray as $rowKey => $rowData) { $bothNumeric = self::numeric($lookupValue) && self::numeric($rowData[$column]); $bothNotNumeric = !self::numeric($lookupValue) && !self::numeric($rowData[$column]); $cellDataLower = StringHelper::strToLower((string) $rowData[$column]); // break if we have passed possible keys if ( $notExactMatch && (($bothNumeric && ($rowData[$column] > $lookupValue)) || ($bothNotNumeric && ($cellDataLower > $lookupLower))) ) { break; } $rowNumber = self::checkMatch( $bothNumeric, $bothNotNumeric, $notExactMatch, $rowKey, $cellDataLower, $lookupLower, $rowNumber ); } return $rowNumber; } /** * @param mixed $value */ private static function numeric($value): bool { return is_int($value) || is_float($value); } } PK!R!!Dlibraries/vendor/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.phpnu[getMessage(); } // MATCH() is not case sensitive, so we convert lookup value to be lower cased if it's a string type. if (is_string($lookupValue)) { $lookupValue = StringHelper::strToLower($lookupValue); } switch ($matchType) { case self::MATCHTYPE_LARGEST_VALUE: $valueKey = self::matchLargestValue($lookupArray, $lookupValue, $keySet); break; case self::MATCHTYPE_FIRST_VALUE: $valueKey = self::matchFirstValue($lookupArray, $lookupValue); break; default: $valueKey = self::matchSmallestValue($lookupArray, $lookupValue); break; } if ($valueKey !== null) { return ++$valueKey; //* @phpstan-ignore-line } // Unsuccessful in finding a match, return #N/A error value return ExcelError::NA(); } /** * @return int|string|null * @param mixed $lookupValue */ private static function matchFirstValue(array $lookupArray, $lookupValue) { if (is_string($lookupValue)) { $valueIsString = true; $wildcard = WildcardMatch::wildcard($lookupValue); } else { $valueIsString = false; $wildcard = ''; } $valueIsNumeric = is_int($lookupValue) || is_float($lookupValue); foreach ($lookupArray as $i => $lookupArrayValue) { if ( $valueIsString && is_string($lookupArrayValue) ) { if (WildcardMatch::compare($lookupArrayValue, $wildcard)) { return $i; // wildcard match } } else { if ($lookupArrayValue === $lookupValue) { return $i; // exact match } if ( $valueIsNumeric && (is_float($lookupArrayValue) || is_int($lookupArrayValue)) && $lookupArrayValue == $lookupValue ) { return $i; // exact match } } } return null; } /** * @param mixed $lookupValue * @return mixed */ private static function matchLargestValue(array $lookupArray, $lookupValue, array $keySet) { if (is_string($lookupValue)) { if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) { $wildcard = WildcardMatch::wildcard($lookupValue); foreach (array_reverse($lookupArray) as $i => $lookupArrayValue) { if (is_string($lookupArrayValue) && WildcardMatch::compare($lookupArrayValue, $wildcard)) { return $i; } } } else { foreach ($lookupArray as $i => $lookupArrayValue) { if ($lookupArrayValue === $lookupValue) { return $keySet[$i]; } } } } $valueIsNumeric = is_int($lookupValue) || is_float($lookupValue); foreach ($lookupArray as $i => $lookupArrayValue) { if ($valueIsNumeric && (is_int($lookupArrayValue) || is_float($lookupArrayValue))) { if ($lookupArrayValue <= $lookupValue) { return array_search($i, $keySet); } } $typeMatch = gettype($lookupValue) === gettype($lookupArrayValue); if ($typeMatch && ($lookupArrayValue <= $lookupValue)) { return array_search($i, $keySet); } } return null; } /** * @return int|string|null * @param mixed $lookupValue */ private static function matchSmallestValue(array $lookupArray, $lookupValue) { $valueKey = null; if (is_string($lookupValue)) { if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) { $wildcard = WildcardMatch::wildcard($lookupValue); foreach ($lookupArray as $i => $lookupArrayValue) { if (is_string($lookupArrayValue) && WildcardMatch::compare($lookupArrayValue, $wildcard)) { return $i; } } } } $valueIsNumeric = is_int($lookupValue) || is_float($lookupValue); // The basic algorithm is: // Iterate and keep the highest match until the next element is smaller than the searched value. // Return immediately if perfect match is found foreach ($lookupArray as $i => $lookupArrayValue) { $typeMatch = gettype($lookupValue) === gettype($lookupArrayValue); $bothNumeric = $valueIsNumeric && (is_int($lookupArrayValue) || is_float($lookupArrayValue)); if ($lookupArrayValue === $lookupValue) { // Another "special" case. If a perfect match is found, // the algorithm gives up immediately return $i; } if ($bothNumeric && $lookupValue == $lookupArrayValue) { return $i; // exact match, as above } if (($typeMatch || $bothNumeric) && $lookupArrayValue >= $lookupValue) { $valueKey = $i; } elseif ($typeMatch && $lookupArrayValue < $lookupValue) { //Excel algorithm gives up immediately if the first element is smaller than the searched value break; } } return $valueKey; } /** * @param mixed $lookupValue */ private static function validateLookupValue($lookupValue): void { // Lookup_value type has to be number, text, or logical values if ((!is_numeric($lookupValue)) && (!is_string($lookupValue)) && (!is_bool($lookupValue))) { throw new Exception(ExcelError::NA()); } } /** * @param mixed $matchType */ private static function validateMatchType($matchType): int { // Match_type is 0, 1 or -1 // However Excel accepts other numeric values, // including numeric strings and floats. // It seems to just be interested in the sign. if (!is_numeric($matchType)) { throw new Exception(ExcelError::Value()); } if ($matchType > 0) { return self::MATCHTYPE_LARGEST_VALUE; } if ($matchType < 0) { return self::MATCHTYPE_SMALLEST_VALUE; } return self::MATCHTYPE_FIRST_VALUE; } private static function validateLookupArray(array $lookupArray): void { // Lookup_array should not be empty $lookupArraySize = count($lookupArray); if ($lookupArraySize <= 0) { throw new Exception(ExcelError::NA()); } } /** * @param mixed $matchType */ private static function prepareLookupArray(array $lookupArray, $matchType): array { // Lookup_array should contain only number, text, or logical values, or empty (null) cells foreach ($lookupArray as $i => $value) { // check the type of the value if ((!is_numeric($value)) && (!is_string($value)) && (!is_bool($value)) && ($value !== null)) { throw new Exception(ExcelError::NA()); } // Convert strings to lowercase for case-insensitive testing if (is_string($value)) { $lookupArray[$i] = StringHelper::strToLower($value); } if ( ($value === null) && (($matchType == self::MATCHTYPE_LARGEST_VALUE) || ($matchType == self::MATCHTYPE_SMALLEST_VALUE)) ) { unset($lookupArray[$i]); } } return $lookupArray; } } PK!?libraries/vendor/PhpSpreadsheet/Calculation/LookupRef/index.phpnu[getCoordinate()); try { $a1 = self::a1Format($a1fmt); $cellAddress = self::validateAddress($cellAddress); } catch (Exception $e) { return $e->getMessage(); } [$cellAddress, $worksheet, $sheetName] = Helpers::extractWorksheet($cellAddress, $cell); if (preg_match('/^' . Calculation::CALCULATION_REGEXP_COLUMNRANGE_RELATIVE . '$/miu', $cellAddress, $matches)) { $cellAddress = self::handleRowColumnRanges($worksheet, ...explode(':', $cellAddress)); } elseif (preg_match('/^' . Calculation::CALCULATION_REGEXP_ROWRANGE_RELATIVE . '$/miu', $cellAddress, $matches)) { $cellAddress = self::handleRowColumnRanges($worksheet, ...explode(':', $cellAddress)); } try { [$cellAddress1, $cellAddress2, $cellAddress] = Helpers::extractCellAddresses($cellAddress, $a1, $cell->getWorkSheet(), $sheetName, $baseRow, $baseCol); } catch (Exception $exception) { return ExcelError::REF(); } if ( (!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/miu', $cellAddress1, $matches)) || (($cellAddress2 !== null) && (!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/miu', $cellAddress2, $matches))) ) { return ExcelError::REF(); } return self::extractRequiredCells($worksheet, $cellAddress); } /** * Extract range values. * * @return array Array of values in range if range contains more than one element. * Otherwise, a single value is returned. */ private static function extractRequiredCells(?Worksheet $worksheet, string $cellAddress): array { return Calculation::getInstance($worksheet !== null ? $worksheet->getParent() : null) ->extractCellRange($cellAddress, $worksheet, false); } private static function handleRowColumnRanges(?Worksheet $worksheet, string $start, string $end): string { // Being lazy, we're only checking a single row/column to get the max if (ctype_digit($start) && $start <= 1048576) { // Max 16,384 columns for Excel2007 $endColRef = ($worksheet !== null) ? $worksheet->getHighestDataColumn((int) $start) : AddressRange::MAX_COLUMN; return "A{$start}:{$endColRef}{$end}"; } elseif (ctype_alpha($start) && strlen($start) <= 3) { // Max 1,048,576 rows for Excel2007 $endRowRef = ($worksheet !== null) ? $worksheet->getHighestDataRow($start) : AddressRange::MAX_ROW; return "{$start}1:{$end}{$endRowRef}"; } return "{$start}:{$end}"; } } PK!!Ό Alibraries/vendor/PhpSpreadsheet/Calculation/LookupRef/Helpers.phpnu[getWorkSheet(); $sheetTitle = ($workSheet === null) ? '' : $workSheet->getTitle(); $value = (string) preg_replace('/^=/', '', $namedRange->getValue()); self::adjustSheetTitle($sheetTitle, $value); $cellAddress1 = $sheetTitle . $value; $cellAddress = $cellAddress1; $a1 = self::CELLADDRESS_USE_A1; } if (str_contains($cellAddress, ':')) { [$cellAddress1, $cellAddress2] = explode(':', $cellAddress); } $cellAddress = self::convertR1C1($cellAddress1, $cellAddress2, $a1, $baseRow, $baseCol); return [$cellAddress1, $cellAddress2, $cellAddress]; } public static function extractWorksheet(string $cellAddress, Cell $cell): array { $sheetName = ''; if (str_contains($cellAddress, '!')) { [$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true, true); } $worksheet = ($sheetName !== '') ? $cell->getWorksheet()->getParentOrThrow()->getSheetByName($sheetName) : $cell->getWorksheet(); return [$cellAddress, $worksheet, $sheetName]; } } PK!x]Dlibraries/vendor/PhpSpreadsheet/Calculation/LookupRef/LookupBase.phpnu[getParent()) ? $nullsafeVariable1->getParent() : null; // worksheet if ($sheet !== null) { $cellAddress = Validations::definedNameToCoordinate($cellAddress, $sheet); } [$cellAddress, $worksheet] = self::extractWorksheet($cellAddress, $cell); $startCell = $endCell = $cellAddress; if (strpos($cellAddress, ':')) { [$startCell, $endCell] = explode(':', $cellAddress); } [$startCellColumn, $startCellRow] = Coordinate::indexesFromString($startCell); [, $endCellRow, $endCellColumn] = Coordinate::indexesFromString($endCell); $startCellRow += $rows; $startCellColumn += $columns - 1; if (($startCellRow <= 0) || ($startCellColumn < 0)) { return ExcelError::REF(); } $endCellColumn = self::adjustEndCellColumnForWidth($endCellColumn, $width, $startCellColumn, $columns); $startCellColumn = Coordinate::stringFromColumnIndex($startCellColumn + 1); $endCellRow = self::adustEndCellRowForHeight($height, $startCellRow, $rows, $endCellRow); if (($endCellRow <= 0) || ($endCellColumn < 0)) { return ExcelError::REF(); } $endCellColumn = Coordinate::stringFromColumnIndex($endCellColumn + 1); $cellAddress = "{$startCellColumn}{$startCellRow}"; if (($startCellColumn != $endCellColumn) || ($startCellRow != $endCellRow)) { $cellAddress .= ":{$endCellColumn}{$endCellRow}"; } return self::extractRequiredCells($worksheet, $cellAddress); } private static function extractRequiredCells(?Worksheet $worksheet, string $cellAddress): array { return Calculation::getInstance($worksheet !== null ? $worksheet->getParent() : null) ->extractCellRange($cellAddress, $worksheet, false); } private static function extractWorksheet(?string $cellAddress, Cell $cell): array { $cellAddress = self::assessCellAddress($cellAddress ?? '', $cell); $sheetName = ''; if (str_contains($cellAddress, '!')) { [$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true, true); } $worksheet = ($sheetName !== '') ? $cell->getWorksheet()->getParentOrThrow()->getSheetByName($sheetName) : $cell->getWorksheet(); return [$cellAddress, $worksheet]; } private static function assessCellAddress(string $cellAddress, Cell $cell): string { if (preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/mui', $cellAddress) !== false) { $cellAddress = Functions::expandDefinedName($cellAddress, $cell); } return $cellAddress; } /** * @param null|object|scalar $width * @param scalar $columns */ private static function adjustEndCellColumnForWidth(string $endCellColumn, $width, int $startCellColumn, $columns): int { $endCellColumn = Coordinate::columnIndexFromString($endCellColumn) - 1; if (($width !== null) && (!is_object($width))) { $endCellColumn = $startCellColumn + (int) $width - 1; } else { $endCellColumn += (int) $columns; } return $endCellColumn; } /** * @param null|object|scalar $height * @param scalar $rows */ private static function adustEndCellRowForHeight($height, int $startCellRow, $rows, int $endCellRow): int { if (($height !== null) && (!is_object($height))) { $endCellRow = $startCellRow + (int) $height - 1; } else { $endCellRow += (int) $rows; } return $endCellRow; } } PK!AVVNlibraries/vendor/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.phpnu[getColumn()) : 1; } /** * COLUMN. * * Returns the column number of the given cell reference * If the cell reference is a range of cells, COLUMN returns the column numbers of each column * in the reference as a horizontal array. * If cell reference is omitted, and the function is being called through the calculation engine, * then it is assumed to be the reference of the cell in which the COLUMN function appears; * otherwise this function returns 1. * * Excel Function: * =COLUMN([cellAddress]) * * @param null|array|string $cellAddress A reference to a range of cells for which you want the column numbers * * @return int|int[] */ public static function COLUMN($cellAddress = null, ?Cell $cell = null) { if (self::cellAddressNullOrWhitespace($cellAddress)) { return self::cellColumn($cell); } if (is_array($cellAddress)) { foreach ($cellAddress as $columnKey => $value) { $columnKey = (string) preg_replace('/[^a-z]/i', '', $columnKey); return Coordinate::columnIndexFromString($columnKey); } return self::cellColumn($cell); } $cellAddress = $cellAddress ?? ''; if ($cell != null) { [,, $sheetName] = Helpers::extractWorksheet($cellAddress, $cell); [,, $cellAddress] = Helpers::extractCellAddresses($cellAddress, true, $cell->getWorksheet(), $sheetName); } [, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true); $cellAddress ??= ''; if (str_contains($cellAddress, ':')) { [$startAddress, $endAddress] = explode(':', $cellAddress); $startAddress = (string) preg_replace('/[^a-z]/i', '', $startAddress); $endAddress = (string) preg_replace('/[^a-z]/i', '', $endAddress); return range( Coordinate::columnIndexFromString($startAddress), Coordinate::columnIndexFromString($endAddress) ); } $cellAddress = (string) preg_replace('/[^a-z]/i', '', $cellAddress); return Coordinate::columnIndexFromString($cellAddress); } /** * COLUMNS. * * Returns the number of columns in an array or reference. * * Excel Function: * =COLUMNS(cellAddress) * * @param null|array|string $cellAddress An array or array formula, or a reference to a range of cells * for which you want the number of columns * * @return int|string The number of columns in cellAddress, or a string if arguments are invalid */ public static function COLUMNS($cellAddress = null) { if (self::cellAddressNullOrWhitespace($cellAddress)) { return 1; } if (!is_array($cellAddress)) { return ExcelError::VALUE(); } reset($cellAddress); $isMatrix = (is_numeric(key($cellAddress))); [$columns, $rows] = Calculation::getMatrixDimensions($cellAddress); if ($isMatrix) { return $rows; } return $columns; } private static function cellRow(?Cell $cell): int { return ($cell !== null) ? $cell->getRow() : 1; } /** * ROW. * * Returns the row number of the given cell reference * If the cell reference is a range of cells, ROW returns the row numbers of each row in the reference * as a vertical array. * If cell reference is omitted, and the function is being called through the calculation engine, * then it is assumed to be the reference of the cell in which the ROW function appears; * otherwise this function returns 1. * * Excel Function: * =ROW([cellAddress]) * * @param null|array|string $cellAddress A reference to a range of cells for which you want the row numbers * * @return int|mixed[] */ public static function ROW($cellAddress = null, ?Cell $cell = null) { if (self::cellAddressNullOrWhitespace($cellAddress)) { return self::cellRow($cell); } if (is_array($cellAddress)) { foreach ($cellAddress as $rowKey => $rowValue) { foreach ($rowValue as $columnKey => $cellValue) { return (int) preg_replace('/\D/', '', $rowKey); } } return self::cellRow($cell); } $cellAddress = $cellAddress ?? ''; if ($cell !== null) { [,, $sheetName] = Helpers::extractWorksheet($cellAddress, $cell); [,, $cellAddress] = Helpers::extractCellAddresses($cellAddress, true, $cell->getWorksheet(), $sheetName); } [, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true); $cellAddress ??= ''; if (str_contains($cellAddress, ':')) { [$startAddress, $endAddress] = explode(':', $cellAddress); $startAddress = (int) (string) preg_replace('/\D/', '', $startAddress); $endAddress = (int) (string) preg_replace('/\D/', '', $endAddress); return array_map( fn ($value): array => [$value], range($startAddress, $endAddress) ); } [$cellAddress] = explode(':', $cellAddress); return (int) preg_replace('/\D/', '', $cellAddress); } /** * ROWS. * * Returns the number of rows in an array or reference. * * Excel Function: * =ROWS(cellAddress) * * @param null|array|string $cellAddress An array or array formula, or a reference to a range of cells * for which you want the number of rows * * @return int|string The number of rows in cellAddress, or a string if arguments are invalid */ public static function ROWS($cellAddress = null) { if (self::cellAddressNullOrWhitespace($cellAddress)) { return 1; } if (!is_array($cellAddress)) { return ExcelError::VALUE(); } reset($cellAddress); $isMatrix = (is_numeric(key($cellAddress))); [$columns, $rows] = Calculation::getMatrixDimensions($cellAddress); if ($isMatrix) { return $columns; } return $rows; } } PK!L""Alibraries/vendor/PhpSpreadsheet/Calculation/LookupRef/Formula.phpnu[getWorksheet()->getParentOrThrow()->getSheetByName($worksheetName) : $cell->getWorksheet(); } if ( $worksheet === null || !$worksheet->cellExists($cellReference) || !$worksheet->getCell($cellReference)->isFormula() ) { return ExcelError::NA(); } return $worksheet->getCell($cellReference)->getValueString(); } } PK!q`@libraries/vendor/PhpSpreadsheet/Calculation/LookupRef/Unique.phpnu[ count($flattenedLookupVector, COUNT_RECURSIVE) + 1) { // We're looking at a full column check (multiple rows) $transpose = Matrix::transpose($lookupVector); $result = self::uniqueByRow($transpose, $exactlyOnce); return (is_array($result)) ? Matrix::transpose($result) : $result; } $result = self::countValuesCaseInsensitive($flattenedLookupVector); if ($exactlyOnce === true) { $result = self::exactlyOnceFilter($result); } if (count($result) === 0) { return ExcelError::CALC(); } $result = array_keys($result); return $result; } private static function countValuesCaseInsensitive(array $caseSensitiveLookupValues): array { $caseInsensitiveCounts = array_count_values( array_map( fn (string $value): string => StringHelper::strToUpper($value), $caseSensitiveLookupValues ) ); $caseSensitiveCounts = []; foreach ($caseInsensitiveCounts as $caseInsensitiveKey => $count) { if (is_numeric($caseInsensitiveKey)) { $caseSensitiveCounts[$caseInsensitiveKey] = $count; } else { foreach ($caseSensitiveLookupValues as $caseSensitiveValue) { if ($caseInsensitiveKey === StringHelper::strToUpper($caseSensitiveValue)) { $caseSensitiveCounts[$caseSensitiveValue] = $count; break; } } } } return $caseSensitiveCounts; } private static function exactlyOnceFilter(array $values): array { return array_filter( $values, fn ($value): bool => $value === 1 ); } } PK!DU@libraries/vendor/PhpSpreadsheet/Calculation/LookupRef/Filter.phpnu[ (bool) $matchArray[$index], ARRAY_FILTER_USE_KEY ); } private static function filterByColumn(array $lookupArray, array $matchArray): array { $lookupArray = Matrix::transpose($lookupArray); if (count($matchArray) === 1) { $matchArray = array_pop($matchArray); } array_walk( $matchArray, function (&$value): void { $value = [$value]; } ); $result = self::filterByRow($lookupArray, $matchArray); return Matrix::transpose($result); } } PK!bKfddClibraries/vendor/PhpSpreadsheet/Calculation/LookupRef/Selection.phpnu[ $entryCount)) { return ExcelError::VALUE(); } if (is_array($chooseArgs[$chosenEntry])) { return Functions::flattenArray($chooseArgs[$chosenEntry]); } return $chooseArgs[$chosenEntry]; } } PK!m@libraries/vendor/PhpSpreadsheet/Calculation/ExceptionHandler.phpnu[value = $value; $this->tokenType = $tokenType; $this->tokenSubType = $tokenSubType; } /** * Get Value. */ public function getValue(): string { return $this->value; } /** * Set Value. */ public function setValue(string $value): void { $this->value = $value; } /** * Get Token Type (represented by TOKEN_TYPE_*). */ public function getTokenType(): string { return $this->tokenType; } /** * Set Token Type (represented by TOKEN_TYPE_*). */ public function setTokenType(string $value): void { $this->tokenType = $value; } /** * Get Token SubType (represented by TOKEN_SUBTYPE_*). */ public function getTokenSubType(): string { return $this->tokenSubType; } /** * Set Token SubType (represented by TOKEN_SUBTYPE_*). */ public function setTokenSubType(string $value): void { $this->tokenSubType = $value; } } PK!= ((9libraries/vendor/PhpSpreadsheet/Calculation/Functions.phpnu[ 0); } /** * @param mixed $idx */ public static function isValue($idx): bool { $idx = StringHelper::convertToString($idx); return substr_count($idx, '.') === 0; } /** * @param mixed $idx */ public static function isCellValue($idx): bool { $idx = StringHelper::convertToString($idx); return substr_count($idx, '.') > 1; } /** * @param mixed $condition */ public static function ifCondition($condition): string { $condition = self::flattenSingleValue($condition); if ($condition === '' || $condition === null) { return '=""'; } if (!is_string($condition) || !in_array($condition[0], ['>', '<', '='], true)) { $condition = self::operandSpecialHandling($condition); if (is_bool($condition)) { return '=' . ($condition ? 'TRUE' : 'FALSE'); } if (!is_numeric($condition)) { if ($condition !== '""') { // Not an empty string // Escape any quotes in the string value $condition = (string) preg_replace('/"/ui', '""', $condition); } $condition = Calculation::wrapResult(strtoupper($condition)); } return str_replace('""""', '""', '=' . StringHelper::convertToString($condition)); } $operator = $operand = ''; if (1 === preg_match('/(=|<[>=]?|>=?)(.*)/', $condition, $matches)) { [, $operator, $operand] = $matches; } $operand = (string) self::operandSpecialHandling($operand); if (is_numeric(trim($operand, '"'))) { $operand = trim($operand, '"'); } elseif (!is_numeric($operand) && $operand !== 'FALSE' && $operand !== 'TRUE') { $operand = str_replace('"', '""', $operand); $operand = Calculation::wrapResult(strtoupper($operand)); $operand = StringHelper::convertToString($operand); } return str_replace('""""', '""', $operator . $operand); } /** * @return bool|float|int|string * @param mixed $operand */ private static function operandSpecialHandling($operand) { if (is_numeric($operand) || is_bool($operand)) { return $operand; } $operand = StringHelper::convertToString($operand); if (strtoupper($operand) === Calculation::getTRUE() || strtoupper($operand) === Calculation::getFALSE()) { return strtoupper($operand); } // Check for percentage if (preg_match('/^\-?\d*\.?\d*\s?\%$/', $operand)) { return ((float) rtrim($operand, '%')) / 100; } // Check for dates if (($dateValueOperand = Date::stringToExcel($operand)) !== false) { return $dateValueOperand; } return $operand; } /** * Convert a multi-dimensional array to a simple 1-dimensional array. * * @param mixed $array Array to be flattened * * @return array Flattened array */ public static function flattenArray($array): array { if (!is_array($array)) { return (array) $array; } $flattened = []; $stack = array_values($array); while (!empty($stack)) { $value = array_shift($stack); if (is_array($value)) { array_unshift($stack, ...array_values($value)); } else { $flattened[] = $value; } } return $flattened; } /** * Convert a multi-dimensional array to a simple 1-dimensional array. * Same as above but argument is specified in ... format. * * @param mixed $array Array to be flattened * * @return array Flattened array */ public static function flattenArray2(...$array): array { $flattened = []; $stack = array_values($array); while (!empty($stack)) { $value = array_shift($stack); if (is_array($value)) { array_unshift($stack, ...array_values($value)); } else { $flattened[] = $value; } } return $flattened; } /** * @param mixed $value * @return mixed */ public static function scalar($value) { if (!is_array($value)) { return $value; } do { $value = array_pop($value); } while (is_array($value)); return $value; } /** * Convert a multi-dimensional array to a simple 1-dimensional array, but retain an element of indexing. * * @param array|mixed $array Array to be flattened * * @return array Flattened array */ public static function flattenArrayIndexed($array): array { if (!is_array($array)) { return (array) $array; } $arrayValues = []; foreach ($array as $k1 => $value) { if (is_array($value)) { foreach ($value as $k2 => $val) { if (is_array($val)) { foreach ($val as $k3 => $v) { $arrayValues[$k1 . '.' . $k2 . '.' . $k3] = $v; } } else { $arrayValues[$k1 . '.' . $k2] = $val; } } } else { $arrayValues[$k1] = $value; } } return $arrayValues; } /** * Convert an array to a single scalar value by extracting the first element. * * @param mixed $value Array or scalar value * @return mixed */ public static function flattenSingleValue($value) { while (is_array($value)) { $value = array_shift($value); } return $value; } public static function expandDefinedName(string $coordinate, Cell $cell): string { $worksheet = $cell->getWorksheet(); $spreadsheet = $worksheet->getParentOrThrow(); // Uppercase coordinate $pCoordinatex = strtoupper($coordinate); // Eliminate leading equal sign $pCoordinatex = (string) preg_replace('/^=/', '', $pCoordinatex); $defined = $spreadsheet->getDefinedName($pCoordinatex, $worksheet); if ($defined !== null) { $worksheet2 = $defined->getWorkSheet(); if (!$defined->isFormula() && $worksheet2 !== null) { $coordinate = "'" . $worksheet2->getTitle() . "'!" . (string) preg_replace('/^=/', '', str_replace('$', '', $defined->getValue())); } } return $coordinate; } public static function trimTrailingRange(string $coordinate): string { return (string) preg_replace('/:[\w\$]+$/', '', $coordinate); } public static function trimSheetFromCellReference(string $coordinate): string { if (str_contains($coordinate, '!')) { $coordinate = substr($coordinate, strrpos($coordinate, '!') + 1); } return $coordinate; } } PK!h;libraries/vendor/PhpSpreadsheet/Calculation/Calculation.phpnu[=:`-]*)|(\'(?:[^\']|\'[^!])+?\')|(\"(?:[^\"]|\"[^!])+?\"))!)?\$?\b([a-z]{1,3})\$?(\d{1,7})(?![\w.])'; // Used only to detect spill operator # const CALCULATION_REGEXP_CELLREF_SPILL = '/' . self::CALCULATION_REGEXP_CELLREF . '#/i'; // Cell reference (with or without a sheet reference) ensuring absolute/relative const CALCULATION_REGEXP_CELLREF_RELATIVE = '((([^\s\(,!&%^\/\*\+<>=:`-]*)|(\'(?:[^\']|\'[^!])+?\')|(\"(?:[^\"]|\"[^!])+?\"))!)?(\$?\b[a-z]{1,3})(\$?\d{1,7})(?![\w.])'; const CALCULATION_REGEXP_COLUMN_RANGE = '(((([^\s\(,!&%^\/\*\+<>=:`-]*)|(\'(?:[^\']|\'[^!])+?\')|(\".(?:[^\"]|\"[^!])?\"))!)?(\$?[a-z]{1,3})):(?![.*])'; const CALCULATION_REGEXP_ROW_RANGE = '(((([^\s\(,!&%^\/\*\+<>=:`-]*)|(\'(?:[^\']|\'[^!])+?\')|(\"(?:[^\"]|\"[^!])+?\"))!)?(\$?[1-9][0-9]{0,6})):(?![.*])'; // Cell reference (with or without a sheet reference) ensuring absolute/relative // Cell ranges ensuring absolute/relative const CALCULATION_REGEXP_COLUMNRANGE_RELATIVE = '(\$?[a-z]{1,3}):(\$?[a-z]{1,3})'; const CALCULATION_REGEXP_ROWRANGE_RELATIVE = '(\$?\d{1,7}):(\$?\d{1,7})'; // Defined Names: Named Range of cells, or Named Formulae const CALCULATION_REGEXP_DEFINEDNAME = '((([^\s,!&%^\/\*\+<>=-]*)|(\'(?:[^\']|\'[^!])+?\')|(\"(?:[^\"]|\"[^!])+?\"))!)?([_\p{L}][_\p{L}\p{N}\.]*)'; // Structured Reference (Fully Qualified and Unqualified) const CALCULATION_REGEXP_STRUCTURED_REFERENCE = '([\p{L}_\\\][\p{L}\p{N}\._]+)?(\[(?:[^\d\]+-])?)'; // Error const CALCULATION_REGEXP_ERROR = '\#[A-Z][A-Z0_\/]*[!\?]?'; /** constants */ const RETURN_ARRAY_AS_ERROR = 'error'; const RETURN_ARRAY_AS_VALUE = 'value'; const RETURN_ARRAY_AS_ARRAY = 'array'; /** Preferable to use instance variable instanceArrayReturnType rather than this static property. */ private static string $returnArrayAsType = self::RETURN_ARRAY_AS_VALUE; /** Preferable to use this instance variable rather than static returnArrayAsType */ private ?string $instanceArrayReturnType = null; /** * Instance of this class. */ private static ?Calculation $instance = null; /** * Instance of the spreadsheet this Calculation Engine is using. */ private ?Spreadsheet $spreadsheet; /** * Calculation cache. */ private array $calculationCache = []; /** * Calculation cache enabled. */ private bool $calculationCacheEnabled = true; private BranchPruner $branchPruner; private bool $branchPruningEnabled = true; /** * List of operators that can be used within formulae * The true/false value indicates whether it is a binary operator or a unary operator. */ private const CALCULATION_OPERATORS = [ '+' => true, '-' => true, '*' => true, '/' => true, '^' => true, '&' => true, '%' => false, '~' => false, '>' => true, '<' => true, '=' => true, '>=' => true, '<=' => true, '<>' => true, '∩' => true, '∪' => true, ':' => true, ]; /** * List of binary operators (those that expect two operands). */ private const BINARY_OPERATORS = [ '+' => true, '-' => true, '*' => true, '/' => true, '^' => true, '&' => true, '>' => true, '<' => true, '=' => true, '>=' => true, '<=' => true, '<>' => true, '∩' => true, '∪' => true, ':' => true, ]; /** * The debug log generated by the calculation engine. */ private Logger $debugLog; private bool $suppressFormulaErrors = false; private bool $processingAnchorArray = false; /** * Error message for any error that was raised/thrown by the calculation engine. */ public ?string $formulaError = null; /** * An array of the nested cell references accessed by the calculation engine, used for the debug log. */ private CyclicReferenceStack $cyclicReferenceStack; private array $cellStack = []; /** * Current iteration counter for cyclic formulae * If the value is 0 (or less) then cyclic formulae will throw an exception, * otherwise they will iterate to the limit defined here before returning a result. */ private int $cyclicFormulaCounter = 1; private string $cyclicFormulaCell = ''; /** * Number of iterations for cyclic formulae. */ public int $cyclicFormulaCount = 1; /** * Excel constant string translations to their PHP equivalents * Constant conversion from text name/value to actual (datatyped) value. */ private const EXCEL_CONSTANTS = [ 'TRUE' => true, 'FALSE' => false, 'NULL' => null, ]; public static function keyInExcelConstants(string $key): bool { return array_key_exists($key, self::EXCEL_CONSTANTS); } public static function getExcelConstants(string $key): ?bool { return self::EXCEL_CONSTANTS[$key]; } /** * Internal functions used for special control purposes. */ private static array $controlFunctions = [ 'MKMATRIX' => [ 'argumentCount' => '*', 'functionCall' => [Internal\MakeMatrix::class, 'make'], ], 'NAME.ERROR' => [ 'argumentCount' => '*', 'functionCall' => [ExcelError::class, 'NAME'], ], 'WILDCARDMATCH' => [ 'argumentCount' => '2', 'functionCall' => [Internal\WildcardMatch::class, 'compare'], ], ]; public function __construct(?Spreadsheet $spreadsheet = null) { // TablePress: Load custom modications to the calculation engine. $this->register_tablepress_aliases_and_custom_functions(); $this->spreadsheet = $spreadsheet; $this->cyclicReferenceStack = new CyclicReferenceStack(); $this->debugLog = new Logger($this->cyclicReferenceStack); $this->branchPruner = new BranchPruner($this->branchPruningEnabled); } /** * Get an instance of this class. * * @param ?Spreadsheet $spreadsheet Injected spreadsheet for working with a PhpSpreadsheet Spreadsheet object, * or NULL to create a standalone calculation engine */ public static function getInstance(?Spreadsheet $spreadsheet = null): self { if ($spreadsheet !== null) { $instance = $spreadsheet->getCalculationEngine(); if (isset($instance)) { return $instance; } } if (!self::$instance) { self::$instance = new self(); } return self::$instance; } /** * Flush the calculation cache for any existing instance of this class * but only if a Calculation instance exists. */ public function flushInstance(): void { $this->clearCalculationCache(); $this->branchPruner->clearBranchStore(); } /** * Get the Logger for this calculation engine instance. */ public function getDebugLog(): Logger { return $this->debugLog; } /** * __clone implementation. Cloning should not be allowed in a Singleton! */ final public function __clone() { throw new Exception('Cloning the calculation engine is not allowed!'); } /** * Set the Array Return Type (Array or Value of first element in the array). * * @param string $returnType Array return type * * @return bool Success or failure */ public static function setArrayReturnType(string $returnType): bool { if ( ($returnType == self::RETURN_ARRAY_AS_VALUE) || ($returnType == self::RETURN_ARRAY_AS_ERROR) || ($returnType == self::RETURN_ARRAY_AS_ARRAY) ) { self::$returnArrayAsType = $returnType; return true; } return false; } /** * Return the Array Return Type (Array or Value of first element in the array). * * @return string $returnType Array return type */ public static function getArrayReturnType(): string { return self::$returnArrayAsType; } /** * Set the Instance Array Return Type (Array or Value of first element in the array). * * @param string $returnType Array return type * * @return bool Success or failure */ public function setInstanceArrayReturnType(string $returnType): bool { if ( ($returnType == self::RETURN_ARRAY_AS_VALUE) || ($returnType == self::RETURN_ARRAY_AS_ERROR) || ($returnType == self::RETURN_ARRAY_AS_ARRAY) ) { $this->instanceArrayReturnType = $returnType; return true; } return false; } /** * Return the Array Return Type (Array or Value of first element in the array). * * @return string $returnType Array return type for instance if non-null, otherwise static property */ public function getInstanceArrayReturnType(): string { return $this->instanceArrayReturnType ?? self::$returnArrayAsType; } /** * Is calculation caching enabled? */ public function getCalculationCacheEnabled(): bool { return $this->calculationCacheEnabled; } /** * Enable/disable calculation cache. */ public function setCalculationCacheEnabled(bool $calculationCacheEnabled): self { $this->calculationCacheEnabled = $calculationCacheEnabled; $this->clearCalculationCache(); return $this; } /** * Enable calculation cache. */ public function enableCalculationCache(): void { $this->setCalculationCacheEnabled(true); } /** * Disable calculation cache. */ public function disableCalculationCache(): void { $this->setCalculationCacheEnabled(false); } /** * Clear calculation cache. */ public function clearCalculationCache(): void { $this->calculationCache = []; } /** * Clear calculation cache for a specified worksheet. */ public function clearCalculationCacheForWorksheet(string $worksheetName): void { if (isset($this->calculationCache[$worksheetName])) { unset($this->calculationCache[$worksheetName]); } } /** * Rename calculation cache for a specified worksheet. */ public function renameCalculationCacheForWorksheet(string $fromWorksheetName, string $toWorksheetName): void { if (isset($this->calculationCache[$fromWorksheetName])) { $this->calculationCache[$toWorksheetName] = &$this->calculationCache[$fromWorksheetName]; unset($this->calculationCache[$fromWorksheetName]); } } public function getBranchPruningEnabled(): bool { return $this->branchPruningEnabled; } /** * @param mixed $enabled */ public function setBranchPruningEnabled($enabled): self { $this->branchPruningEnabled = (bool) $enabled; $this->branchPruner = new BranchPruner($this->branchPruningEnabled); return $this; } public function enableBranchPruning(): void { $this->setBranchPruningEnabled(true); } public function disableBranchPruning(): void { $this->setBranchPruningEnabled(false); } /** * Wrap string values in quotes. * @param mixed $value * @return mixed */ public static function wrapResult($value) { if (is_string($value)) { // Error values cannot be "wrapped" if (preg_match('/^' . self::CALCULATION_REGEXP_ERROR . '$/i', $value, $match)) { // Return Excel errors "as is" return $value; } // Return strings wrapped in quotes return self::FORMULA_STRING_QUOTE . $value . self::FORMULA_STRING_QUOTE; } elseif ((is_float($value)) && ((is_nan($value)) || (is_infinite($value)))) { // Convert numeric errors to NaN error return ExcelError::NAN(); } return $value; } /** * Remove quotes used as a wrapper to identify string values. * @param mixed $value * @return mixed */ public static function unwrapResult($value) { if (is_string($value)) { if ((isset($value[0])) && ($value[0] == self::FORMULA_STRING_QUOTE) && (substr($value, -1) == self::FORMULA_STRING_QUOTE)) { return substr($value, 1, -1); } // Convert numeric errors to NAN error } elseif ((is_float($value)) && ((is_nan($value)) || (is_infinite($value)))) { return ExcelError::NAN(); } return $value; } /** * Calculate cell value (using formula from a cell ID) * Retained for backward compatibility. * * @param ?Cell $cell Cell to calculate * @return mixed */ public function calculate(?Cell $cell = null) { try { return $this->calculateCellValue($cell); } catch (\Exception $e) { throw new Exception($e->getMessage()); } } /** * Calculate the value of a cell formula. * * @param ?Cell $cell Cell to calculate * @param bool $resetLog Flag indicating whether the debug log should be reset or not * @return mixed */ public function calculateCellValue(?Cell $cell = null, bool $resetLog = true) { if ($cell === null) { return null; } if ($resetLog) { // Initialise the logging settings if requested $this->formulaError = null; $this->debugLog->clearLog(); $this->cyclicReferenceStack->clear(); $this->cyclicFormulaCounter = 1; } // Execute the calculation for the cell formula $this->cellStack[] = [ 'sheet' => $cell->getWorksheet()->getTitle(), 'cell' => $cell->getCoordinate(), ]; $cellAddressAttempted = false; $cellAddress = null; try { $value = $cell->getValue(); if (is_string($value) && $cell->getDataType() === DataType::TYPE_FORMULA) { $value = preg_replace_callback( self::CALCULATION_REGEXP_CELLREF_SPILL, fn (array $matches) => 'ANCHORARRAY(' . substr($matches[0], 0, -1) . ')', $value ); } $result = self::unwrapResult($this->_calculateFormulaValue($value, $cell->getCoordinate(), $cell)); //* @phpstan-ignore-line if ($this->spreadsheet === null) { throw new Exception('null spreadsheet in calculateCellValue'); } $cellAddressAttempted = true; $cellAddress = array_pop($this->cellStack); if ($cellAddress === null) { throw new Exception('null cellAddress in calculateCellValue'); } $testSheet = $this->spreadsheet->getSheetByName($cellAddress['sheet']); if ($testSheet === null) { throw new Exception('worksheet not found in calculateCellValue'); } $testSheet->getCell($cellAddress['cell']); } catch (\Exception $e) { if (!$cellAddressAttempted) { $cellAddress = array_pop($this->cellStack); } if ($this->spreadsheet !== null && is_array($cellAddress) && array_key_exists('sheet', $cellAddress)) { $sheetName = $cellAddress['sheet'] ?? null; $testSheet = is_string($sheetName) ? $this->spreadsheet->getSheetByName($sheetName) : null; if ($testSheet !== null && array_key_exists('cell', $cellAddress)) { $testSheet->getCell($cellAddress['cell']); } } throw new Exception($e->getMessage(), $e->getCode(), $e); } if (is_array($result) && $this->getInstanceArrayReturnType() !== self::RETURN_ARRAY_AS_ARRAY) { $testResult = Functions::flattenArray($result); if ($this->getInstanceArrayReturnType() == self::RETURN_ARRAY_AS_ERROR) { return ExcelError::VALUE(); } $result = array_shift($testResult); } if ($result === null && $cell->getWorksheet()->getSheetView()->getShowZeros()) { return 0; } elseif ((is_float($result)) && ((is_nan($result)) || (is_infinite($result)))) { return ExcelError::NAN(); } return $result; } /** * Validate and parse a formula string. * * @param string $formula Formula to parse * @return mixed[]|bool */ public function parseFormula(string $formula) { $formula = preg_replace_callback( self::CALCULATION_REGEXP_CELLREF_SPILL, fn (array $matches) => 'ANCHORARRAY(' . substr($matches[0], 0, -1) . ')', $formula ) ?? $formula; // Basic validation that this is indeed a formula // We return an empty array if not $formula = trim($formula); if ((!isset($formula[0])) || ($formula[0] != '=')) { return []; } $formula = ltrim(substr($formula, 1)); if (!isset($formula[0])) { return []; } // Parse the formula and return the token stack return $this->internalParseFormula($formula); } /** * Calculate the value of a formula. * * @param string $formula Formula to parse * @param ?string $cellID Address of the cell to calculate * @param ?Cell $cell Cell to calculate * @return mixed */ public function calculateFormula(string $formula, ?string $cellID = null, ?Cell $cell = null) { // Initialise the logging settings $this->formulaError = null; $this->debugLog->clearLog(); $this->cyclicReferenceStack->clear(); $resetCache = $this->getCalculationCacheEnabled(); if ($this->spreadsheet !== null && $cellID === null && $cell === null) { $cellID = 'A1'; $cell = $this->spreadsheet->getActiveSheet()->getCell($cellID); } else { // Disable calculation cacheing because it only applies to cell calculations, not straight formulae // But don't actually flush any cache $this->calculationCacheEnabled = false; } // Execute the calculation try { $result = self::unwrapResult($this->_calculateFormulaValue($formula, $cellID, $cell)); } catch (\Exception $e) { throw new Exception($e->getMessage()); } if ($this->spreadsheet === null) { // Reset calculation cacheing to its previous state $this->calculationCacheEnabled = $resetCache; } return $result; } /** * @param mixed $cellValue */ public function getValueFromCache(string $cellReference, &$cellValue): bool { $this->debugLog->writeDebugLog('Testing cache value for cell %s', $cellReference); // Is calculation cacheing enabled? // If so, is the required value present in calculation cache? if (($this->calculationCacheEnabled) && (isset($this->calculationCache[$cellReference]))) { $this->debugLog->writeDebugLog('Retrieving value for cell %s from cache', $cellReference); // Return the cached result $cellValue = $this->calculationCache[$cellReference]; return true; } return false; } /** * @param mixed $cellValue */ public function saveValueToCache(string $cellReference, $cellValue): void { if ($this->calculationCacheEnabled) { $this->calculationCache[$cellReference] = $cellValue; } } /** * Parse a cell formula and calculate its value. * * @param string $formula The formula to parse and calculate * @param ?string $cellID The ID (e.g. A3) of the cell that we are calculating * @param ?Cell $cell Cell to calculate * @param bool $ignoreQuotePrefix If set to true, evaluate the formyla even if the referenced cell is quote prefixed * @return mixed */ public function _calculateFormulaValue(string $formula, ?string $cellID = null, ?Cell $cell = null, bool $ignoreQuotePrefix = false) { $cellValue = null; // Quote-Prefixed cell values cannot be formulae, but are treated as strings if ($cell !== null && $ignoreQuotePrefix === false && $cell->getStyle()->getQuotePrefix() === true) { return self::wrapResult((string) $formula); } if (preg_match('/^=\s*cmd\s*\|/miu', $formula) !== 0) { return self::wrapResult($formula); } // Basic validation that this is indeed a formula // We simply return the cell value if not $formula = trim($formula); if ($formula === '' || $formula[0] !== '=') { return self::wrapResult($formula); } $formula = ltrim(substr($formula, 1)); if (!isset($formula[0])) { return self::wrapResult($formula); } $pCellParent = ($cell !== null) ? $cell->getWorksheet() : null; $wsTitle = ($pCellParent !== null) ? $pCellParent->getTitle() : "\x00Wrk"; $wsCellReference = $wsTitle . '!' . $cellID; if (($cellID !== null) && ($this->getValueFromCache($wsCellReference, $cellValue))) { return $cellValue; } $this->debugLog->writeDebugLog('Evaluating formula for cell %s', $wsCellReference); if (($wsTitle[0] !== "\x00") && ($this->cyclicReferenceStack->onStack($wsCellReference))) { if ($this->cyclicFormulaCount <= 0) { $this->cyclicFormulaCell = ''; return $this->raiseFormulaError('Cyclic Reference in Formula'); } elseif ($this->cyclicFormulaCell === $wsCellReference) { ++$this->cyclicFormulaCounter; if ($this->cyclicFormulaCounter >= $this->cyclicFormulaCount) { $this->cyclicFormulaCell = ''; return $cellValue; } } elseif ($this->cyclicFormulaCell == '') { if ($this->cyclicFormulaCounter >= $this->cyclicFormulaCount) { return $cellValue; } $this->cyclicFormulaCell = $wsCellReference; } } $this->debugLog->writeDebugLog('Formula for cell %s is %s', $wsCellReference, $formula); // Parse the formula onto the token stack and calculate the value $this->cyclicReferenceStack->push($wsCellReference); $cellValue = $this->processTokenStack($this->internalParseFormula($formula, $cell), $cellID, $cell); $this->cyclicReferenceStack->pop(); // Save to calculation cache if ($cellID !== null) { $this->saveValueToCache($wsCellReference, $cellValue); } // Return the calculated value return $cellValue; } /** * Ensure that paired matrix operands are both matrices and of the same size. * * @param mixed $operand1 First matrix operand * * @param-out array $operand1 * * @param mixed $operand2 Second matrix operand * * @param-out array $operand2 * * @param int $resize Flag indicating whether the matrices should be resized to match * and (if so), whether the smaller dimension should grow or the * larger should shrink. * 0 = no resize * 1 = shrink to fit * 2 = extend to fit */ public static function checkMatrixOperands(&$operand1, &$operand2, int $resize = 1): array { // Examine each of the two operands, and turn them into an array if they aren't one already // Note that this function should only be called if one or both of the operand is already an array if (!is_array($operand1)) { if (is_array($operand2)) { [$matrixRows, $matrixColumns] = self::getMatrixDimensions($operand2); $operand1 = array_fill(0, $matrixRows, array_fill(0, $matrixColumns, $operand1)); $resize = 0; } else { $operand1 = [$operand1]; $operand2 = [$operand2]; } } elseif (!is_array($operand2)) { [$matrixRows, $matrixColumns] = self::getMatrixDimensions($operand1); $operand2 = array_fill(0, $matrixRows, array_fill(0, $matrixColumns, $operand2)); $resize = 0; } [$matrix1Rows, $matrix1Columns] = self::getMatrixDimensions($operand1); [$matrix2Rows, $matrix2Columns] = self::getMatrixDimensions($operand2); if ($resize === 3) { $resize = 2; } elseif (($matrix1Rows == $matrix2Columns) && ($matrix2Rows == $matrix1Columns)) { $resize = 1; } if ($resize == 2) { // Given two matrices of (potentially) unequal size, convert the smaller in each dimension to match the larger self::resizeMatricesExtend($operand1, $operand2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns); } elseif ($resize == 1) { // Given two matrices of (potentially) unequal size, convert the larger in each dimension to match the smaller self::resizeMatricesShrink($operand1, $operand2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns); } [$matrix1Rows, $matrix1Columns] = self::getMatrixDimensions($operand1); [$matrix2Rows, $matrix2Columns] = self::getMatrixDimensions($operand2); return [$matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns]; } /** * Read the dimensions of a matrix, and re-index it with straight numeric keys starting from row 0, column 0. * * @param array $matrix matrix operand * * @return int[] An array comprising the number of rows, and number of columns */ public static function getMatrixDimensions(array &$matrix): array { $matrixRows = count($matrix); $matrixColumns = 0; foreach ($matrix as $rowKey => $rowValue) { if (!is_array($rowValue)) { $matrix[$rowKey] = [$rowValue]; $matrixColumns = max(1, $matrixColumns); } else { $matrix[$rowKey] = array_values($rowValue); $matrixColumns = max(count($rowValue), $matrixColumns); } } $matrix = array_values($matrix); return [$matrixRows, $matrixColumns]; } /** * Ensure that paired matrix operands are both matrices of the same size. * * @param array $matrix1 First matrix operand * @param array $matrix2 Second matrix operand * @param int $matrix1Rows Row size of first matrix operand * @param int $matrix1Columns Column size of first matrix operand * @param int $matrix2Rows Row size of second matrix operand * @param int $matrix2Columns Column size of second matrix operand */ private static function resizeMatricesShrink(array &$matrix1, array &$matrix2, int $matrix1Rows, int $matrix1Columns, int $matrix2Rows, int $matrix2Columns): void { if (($matrix2Columns < $matrix1Columns) || ($matrix2Rows < $matrix1Rows)) { if ($matrix2Rows < $matrix1Rows) { for ($i = $matrix2Rows; $i < $matrix1Rows; ++$i) { unset($matrix1[$i]); } } if ($matrix2Columns < $matrix1Columns) { for ($i = 0; $i < $matrix1Rows; ++$i) { for ($j = $matrix2Columns; $j < $matrix1Columns; ++$j) { unset($matrix1[$i][$j]); } } } } if (($matrix1Columns < $matrix2Columns) || ($matrix1Rows < $matrix2Rows)) { if ($matrix1Rows < $matrix2Rows) { for ($i = $matrix1Rows; $i < $matrix2Rows; ++$i) { unset($matrix2[$i]); } } if ($matrix1Columns < $matrix2Columns) { for ($i = 0; $i < $matrix2Rows; ++$i) { for ($j = $matrix1Columns; $j < $matrix2Columns; ++$j) { unset($matrix2[$i][$j]); } } } } } /** * Ensure that paired matrix operands are both matrices of the same size. * * @param array $matrix1 First matrix operand * @param array $matrix2 Second matrix operand * @param int $matrix1Rows Row size of first matrix operand * @param int $matrix1Columns Column size of first matrix operand * @param int $matrix2Rows Row size of second matrix operand * @param int $matrix2Columns Column size of second matrix operand */ private static function resizeMatricesExtend(array &$matrix1, array &$matrix2, int $matrix1Rows, int $matrix1Columns, int $matrix2Rows, int $matrix2Columns): void { if (($matrix2Columns < $matrix1Columns) || ($matrix2Rows < $matrix1Rows)) { if ($matrix2Columns < $matrix1Columns) { for ($i = 0; $i < $matrix2Rows; ++$i) { $x = $matrix2[$i][$matrix2Columns - 1]; for ($j = $matrix2Columns; $j < $matrix1Columns; ++$j) { $matrix2[$i][$j] = $x; } } } if ($matrix2Rows < $matrix1Rows) { $x = $matrix2[$matrix2Rows - 1]; for ($i = 0; $i < $matrix1Rows; ++$i) { $matrix2[$i] = $x; } } } if (($matrix1Columns < $matrix2Columns) || ($matrix1Rows < $matrix2Rows)) { if ($matrix1Columns < $matrix2Columns) { for ($i = 0; $i < $matrix1Rows; ++$i) { $x = $matrix1[$i][$matrix1Columns - 1]; for ($j = $matrix1Columns; $j < $matrix2Columns; ++$j) { $matrix1[$i][$j] = $x; } } } if ($matrix1Rows < $matrix2Rows) { $x = $matrix1[$matrix1Rows - 1]; for ($i = 0; $i < $matrix2Rows; ++$i) { $matrix1[$i] = $x; } } } } /** * Format details of an operand for display in the log (based on operand type). * * @param mixed $value First matrix operand * @return mixed */ private function showValue($value) { if ($this->debugLog->getWriteDebugLog()) { $testArray = Functions::flattenArray($value); if (count($testArray) == 1) { $value = array_pop($testArray); } if (is_array($value)) { $returnMatrix = []; $pad = $rpad = ', '; foreach ($value as $row) { if (is_array($row)) { $returnMatrix[] = implode($pad, array_map([$this, 'showValue'], $row)); $rpad = '; '; } else { $returnMatrix[] = $this->showValue($row); } } return '{ ' . implode($rpad, $returnMatrix) . ' }'; } elseif (is_string($value) && (trim($value, self::FORMULA_STRING_QUOTE) == $value)) { return self::FORMULA_STRING_QUOTE . $value . self::FORMULA_STRING_QUOTE; } elseif (is_bool($value)) { return ($value) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE']; } elseif ($value === null) { return self::$localeBoolean['NULL']; } } return Functions::flattenSingleValue($value); } /** * Format type and details of an operand for display in the log (based on operand type). * * @param mixed $value First matrix operand */ private function showTypeDetails($value): ?string { if ($this->debugLog->getWriteDebugLog()) { $testArray = Functions::flattenArray($value); if (count($testArray) == 1) { $value = array_pop($testArray); } if ($value === null) { return 'a NULL value'; } elseif (is_float($value)) { $typeString = 'a floating point number'; } elseif (is_int($value)) { $typeString = 'an integer number'; } elseif (is_bool($value)) { $typeString = 'a boolean'; } elseif (is_array($value)) { $typeString = 'a matrix'; } else { /** @var string $value */ if ($value == '') { return 'an empty string'; } elseif ($value[0] == '#') { return 'a ' . $value . ' error'; } $typeString = 'a string'; } return $typeString . ' with a value of ' . StringHelper::convertToString($this->showValue($value)); } return null; } /** * @return false|string False indicates an error */ private function convertMatrixReferences(string $formula) { static $matrixReplaceFrom = [self::FORMULA_OPEN_MATRIX_BRACE, ';', self::FORMULA_CLOSE_MATRIX_BRACE]; static $matrixReplaceTo = ['MKMATRIX(MKMATRIX(', '),MKMATRIX(', '))']; // Convert any Excel matrix references to the MKMATRIX() function if (str_contains($formula, self::FORMULA_OPEN_MATRIX_BRACE)) { // If there is the possibility of braces within a quoted string, then we don't treat those as matrix indicators if (str_contains($formula, self::FORMULA_STRING_QUOTE)) { // So instead we skip replacing in any quoted strings by only replacing in every other array element after we've exploded // the formula $temp = explode(self::FORMULA_STRING_QUOTE, $formula); // Open and Closed counts used for trapping mismatched braces in the formula $openCount = $closeCount = 0; $notWithinQuotes = false; foreach ($temp as &$value) { // Only count/replace in alternating array entries $notWithinQuotes = $notWithinQuotes === false; if ($notWithinQuotes === true) { $openCount += substr_count($value, self::FORMULA_OPEN_MATRIX_BRACE); $closeCount += substr_count($value, self::FORMULA_CLOSE_MATRIX_BRACE); $value = str_replace($matrixReplaceFrom, $matrixReplaceTo, $value); } } unset($value); // Then rebuild the formula string $formula = implode(self::FORMULA_STRING_QUOTE, $temp); } else { // If there's no quoted strings, then we do a simple count/replace $openCount = substr_count($formula, self::FORMULA_OPEN_MATRIX_BRACE); $closeCount = substr_count($formula, self::FORMULA_CLOSE_MATRIX_BRACE); $formula = str_replace($matrixReplaceFrom, $matrixReplaceTo, $formula); } // Trap for mismatched braces and trigger an appropriate error if ($openCount < $closeCount) { if ($openCount > 0) { return $this->raiseFormulaError("Formula Error: Mismatched matrix braces '}'"); } return $this->raiseFormulaError("Formula Error: Unexpected '}' encountered"); } elseif ($openCount > $closeCount) { if ($closeCount > 0) { return $this->raiseFormulaError("Formula Error: Mismatched matrix braces '{'"); } return $this->raiseFormulaError("Formula Error: Unexpected '{' encountered"); } } return $formula; } /** * Comparison (Boolean) Operators. * These operators work on two values, but always return a boolean result. */ private const COMPARISON_OPERATORS = ['>' => true, '<' => true, '=' => true, '>=' => true, '<=' => true, '<>' => true]; /** * Operator Precedence. * This list includes all valid operators, whether binary (including boolean) or unary (such as %). * Array key is the operator, the value is its precedence. */ private const OPERATOR_PRECEDENCE = [ ':' => 9, // Range '∩' => 8, // Intersect '∪' => 7, // Union '~' => 6, // Negation '%' => 5, // Percentage '^' => 4, // Exponentiation '*' => 3, '/' => 3, // Multiplication and Division '+' => 2, '-' => 2, // Addition and Subtraction '&' => 1, // Concatenation '>' => 0, '<' => 0, '=' => 0, '>=' => 0, '<=' => 0, '<>' => 0, // Comparison ]; /** * @return array|false */ private function internalParseFormula(string $formula, ?Cell $cell = null) { if (($formula = $this->convertMatrixReferences(trim($formula))) === false) { return false; } $phpSpreadsheetFunctions = &self::getFunctionsAddress(); // If we're using cell caching, then $pCell may well be flushed back to the cache (which detaches the parent worksheet), // so we store the parent worksheet so that we can re-attach it when necessary $pCellParent = ($cell !== null) ? $cell->getWorksheet() : null; $regexpMatchString = '/^((?' . self::CALCULATION_REGEXP_STRING . ')|(?' . self::CALCULATION_REGEXP_FUNCTION . ')|(?' . self::CALCULATION_REGEXP_CELLREF . ')|(?' . self::CALCULATION_REGEXP_COLUMN_RANGE . ')|(?' . self::CALCULATION_REGEXP_ROW_RANGE . ')|(?' . self::CALCULATION_REGEXP_NUMBER . ')|(?' . self::CALCULATION_REGEXP_OPENBRACE . ')|(?' . self::CALCULATION_REGEXP_STRUCTURED_REFERENCE . ')|(?' . self::CALCULATION_REGEXP_DEFINEDNAME . ')|(?' . self::CALCULATION_REGEXP_ERROR . '))/sui'; // Start with initialisation $index = 0; $stack = new Stack($this->branchPruner); $output = []; $expectingOperator = false; // We use this test in syntax-checking the expression to determine when a // - is a negation or + is a positive operator rather than an operation $expectingOperand = false; // We use this test in syntax-checking the expression to determine whether an operand // should be null in a function call // The guts of the lexical parser // Loop through the formula extracting each operator and operand in turn while (true) { // Branch pruning: we adapt the output item to the context (it will // be used to limit its computation) $this->branchPruner->initialiseForLoop(); $opCharacter = $formula[$index]; // Get the first character of the value at the current index position // Check for two-character operators (e.g. >=, <=, <>) if ((isset(self::COMPARISON_OPERATORS[$opCharacter])) && (strlen($formula) > $index) && isset($formula[$index + 1], self::COMPARISON_OPERATORS[$formula[$index + 1]])) { $opCharacter .= $formula[++$index]; } // Find out if we're currently at the beginning of a number, variable, cell/row/column reference, // function, defined name, structured reference, parenthesis, error or operand $isOperandOrFunction = (bool) preg_match($regexpMatchString, substr($formula, $index), $match); $expectingOperatorCopy = $expectingOperator; if ($opCharacter === '-' && !$expectingOperator) { // Is it a negation instead of a minus? // Put a negation on the stack $stack->push('Unary Operator', '~'); ++$index; // and drop the negation symbol } elseif ($opCharacter === '%' && $expectingOperator) { // Put a percentage on the stack $stack->push('Unary Operator', '%'); ++$index; } elseif ($opCharacter === '+' && !$expectingOperator) { // Positive (unary plus rather than binary operator plus) can be discarded? ++$index; // Drop the redundant plus symbol } elseif ((($opCharacter === '~') || ($opCharacter === '∩') || ($opCharacter === '∪')) && (!$isOperandOrFunction)) { // We have to explicitly deny a tilde, union or intersect because they are legal return $this->raiseFormulaError("Formula Error: Illegal character '~'"); // on the stack but not in the input expression } elseif ((isset(self::CALCULATION_OPERATORS[$opCharacter]) || $isOperandOrFunction) && $expectingOperator) { // Are we putting an operator on the stack? while (self::swapOperands($stack, $opCharacter)) { $output[] = $stack->pop(); // Swap operands and higher precedence operators from the stack to the output } // Finally put our current operator onto the stack $stack->push('Binary Operator', $opCharacter); ++$index; $expectingOperator = false; } elseif ($opCharacter === ')' && $expectingOperator) { // Are we expecting to close a parenthesis? $expectingOperand = false; while (($o2 = $stack->pop()) && $o2['value'] !== '(') { // Pop off the stack back to the last ( $output[] = $o2; } $d = $stack->last(2); // Branch pruning we decrease the depth whether is it a function // call or a parenthesis $this->branchPruner->decrementDepth(); if (is_array($d) && preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/miu', $d['value'], $matches)) { // Did this parenthesis just close a function? try { $this->branchPruner->closingBrace($d['value']); } catch (Exception $e) { return $this->raiseFormulaError($e->getMessage(), $e->getCode(), $e); } $functionName = $matches[1]; // Get the function name $d = $stack->pop(); $argumentCount = $d['value'] ?? 0; // See how many arguments there were (argument count is the next value stored on the stack) $output[] = $d; // Dump the argument count on the output $output[] = $stack->pop(); // Pop the function and push onto the output if (isset(self::$controlFunctions[$functionName])) { $expectedArgumentCount = self::$controlFunctions[$functionName]['argumentCount']; } elseif (isset($phpSpreadsheetFunctions[$functionName])) { $expectedArgumentCount = $phpSpreadsheetFunctions[$functionName]['argumentCount']; } else { // did we somehow push a non-function on the stack? this should never happen return $this->raiseFormulaError('Formula Error: Internal error, non-function on stack'); } // Check the argument count $argumentCountError = false; $expectedArgumentCountString = null; if (is_numeric($expectedArgumentCount)) { if ($expectedArgumentCount < 0) { if ($argumentCount > abs($expectedArgumentCount + 0)) { $argumentCountError = true; $expectedArgumentCountString = 'no more than ' . abs($expectedArgumentCount + 0); } } else { if ($argumentCount != $expectedArgumentCount) { $argumentCountError = true; $expectedArgumentCountString = $expectedArgumentCount; } } } elseif (is_string($expectedArgumentCount) && $expectedArgumentCount !== '*') { if (1 !== preg_match('/(\d*)([-+,])(\d*)/', $expectedArgumentCount, $argMatch)) { $argMatch = ['', '', '', '']; } switch ($argMatch[2]) { case '+': if ($argumentCount < $argMatch[1]) { $argumentCountError = true; $expectedArgumentCountString = $argMatch[1] . ' or more '; } break; case '-': if (($argumentCount < $argMatch[1]) || ($argumentCount > $argMatch[3])) { $argumentCountError = true; $expectedArgumentCountString = 'between ' . $argMatch[1] . ' and ' . $argMatch[3]; } break; case ',': if (($argumentCount != $argMatch[1]) && ($argumentCount != $argMatch[3])) { $argumentCountError = true; $expectedArgumentCountString = 'either ' . $argMatch[1] . ' or ' . $argMatch[3]; } break; } } if ($argumentCountError) { return $this->raiseFormulaError("Formula Error: Wrong number of arguments for $functionName() function: $argumentCount given, " . $expectedArgumentCountString . ' expected'); } } ++$index; } elseif ($opCharacter === ',') { // Is this the separator for function arguments? try { $this->branchPruner->argumentSeparator(); } catch (Exception $e) { return $this->raiseFormulaError($e->getMessage(), $e->getCode(), $e); } while (($o2 = $stack->pop()) && $o2['value'] !== '(') { // Pop off the stack back to the last ( $output[] = $o2; // pop the argument expression stuff and push onto the output } // If we've a comma when we're expecting an operand, then what we actually have is a null operand; // so push a null onto the stack if (($expectingOperand) || (!$expectingOperator)) { $output[] = $stack->getStackItem('Empty Argument', null, 'NULL'); } // make sure there was a function $d = $stack->last(2); if (!preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/miu', $d['value'] ?? '', $matches)) { // Can we inject a dummy function at this point so that the braces at least have some context // because at least the braces are paired up (at this stage in the formula) // MS Excel allows this if the content is cell references; but doesn't allow actual values, // but at this point, we can't differentiate (so allow both) return $this->raiseFormulaError('Formula Error: Unexpected ,'); } /** @var array $d */ $d = $stack->pop(); ++$d['value']; // increment the argument count $stack->pushStackItem($d); $stack->push('Brace', '('); // put the ( back on, we'll need to pop back to it again $expectingOperator = false; $expectingOperand = true; ++$index; } elseif ($opCharacter === '(' && !$expectingOperator) { // Branch pruning: we go deeper $this->branchPruner->incrementDepth(); $stack->push('Brace', '(', null); ++$index; } elseif ($isOperandOrFunction && !$expectingOperatorCopy) { // do we now have a function/variable/number? $expectingOperator = true; $expectingOperand = false; $val = $match[1] ?? ''; //* @phpstan-ignore-line $length = strlen($val); if (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/miu', $val, $matches)) { $val = (string) preg_replace('/\s/u', '', $val); if (isset($phpSpreadsheetFunctions[strtoupper($matches[1])]) || isset(self::$controlFunctions[strtoupper($matches[1])])) { // it's a function $valToUpper = strtoupper($val); } else { $valToUpper = 'NAME.ERROR('; } // here $matches[1] will contain values like "IF" // and $val "IF(" $this->branchPruner->functionCall($valToUpper); $stack->push('Function', $valToUpper); // tests if the function is closed right after opening $ax = preg_match('/^\s*\)/u', substr($formula, $index + $length)); if ($ax) { $stack->push('Operand Count for Function ' . $valToUpper . ')', 0); $expectingOperator = true; } else { $stack->push('Operand Count for Function ' . $valToUpper . ')', 1); $expectingOperator = false; } $stack->push('Brace', '('); } elseif (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/miu', $val, $matches)) { // Watch for this case-change when modifying to allow cell references in different worksheets... // Should only be applied to the actual cell column, not the worksheet name // If the last entry on the stack was a : operator, then we have a cell range reference $testPrevOp = $stack->last(1); if ($testPrevOp !== null && $testPrevOp['value'] === ':') { // If we have a worksheet reference, then we're playing with a 3D reference if ($matches[2] === '') { // Otherwise, we 'inherit' the worksheet reference from the start cell reference // The start of the cell range reference should be the last entry in $output $rangeStartCellRef = $output[count($output) - 1]['value'] ?? ''; if ($rangeStartCellRef === ':') { // Do we have chained range operators? $rangeStartCellRef = $output[count($output) - 2]['value'] ?? ''; } preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/miu', $rangeStartCellRef, $rangeStartMatches); if (array_key_exists(2, $rangeStartMatches)) { if ($rangeStartMatches[2] > '') { $val = $rangeStartMatches[2] . '!' . $val; } } else { $val = ExcelError::REF(); } } else { $rangeStartCellRef = $output[count($output) - 1]['value'] ?? ''; if ($rangeStartCellRef === ':') { // Do we have chained range operators? $rangeStartCellRef = $output[count($output) - 2]['value'] ?? ''; } preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/miu', $rangeStartCellRef, $rangeStartMatches); if (isset($rangeStartMatches[2]) && $rangeStartMatches[2] !== $matches[2]) { return $this->raiseFormulaError('3D Range references are not yet supported'); } } } elseif (!str_contains($val, '!') && $pCellParent !== null) { $worksheet = $pCellParent->getTitle(); $val = "'{$worksheet}'!{$val}"; } // unescape any apostrophes or double quotes in worksheet name $val = str_replace(["''", '""'], ["'", '"'], $val); $outputItem = $stack->getStackItem('Cell Reference', $val, $val); $output[] = $outputItem; } elseif (preg_match('/^' . self::CALCULATION_REGEXP_STRUCTURED_REFERENCE . '$/miu', $val, $matches)) { try { $structuredReference = Operands\StructuredReference::fromParser($formula, $index, $matches); } catch (Exception $e) { return $this->raiseFormulaError($e->getMessage(), $e->getCode(), $e); } $val = $structuredReference->value(); $length = strlen($val); $outputItem = $stack->getStackItem(Operands\StructuredReference::NAME, $structuredReference, null); $output[] = $outputItem; $expectingOperator = true; } else { // it's a variable, constant, string, number or boolean $localeConstant = false; $stackItemType = 'Value'; $stackItemReference = null; // If the last entry on the stack was a : operator, then we may have a row or column range reference $testPrevOp = $stack->last(1); if ($testPrevOp !== null && $testPrevOp['value'] === ':') { $stackItemType = 'Cell Reference'; if ( !is_numeric($val) && ((ctype_alpha($val) === false || strlen($val) > 3)) && (preg_match('/^' . self::CALCULATION_REGEXP_DEFINEDNAME . '$/mui', $val) !== false) && ($this->spreadsheet === null || $this->spreadsheet->getNamedRange($val) !== null) ) { $namedRange = ($this->spreadsheet === null) ? null : $this->spreadsheet->getNamedRange($val); if ($namedRange !== null) { $stackItemType = 'Defined Name'; $address = str_replace('$', '', $namedRange->getValue()); $stackItemReference = $val; if (str_contains($address, ':')) { // We'll need to manipulate the stack for an actual named range rather than a named cell $fromTo = explode(':', $address); $to = array_pop($fromTo); foreach ($fromTo as $from) { $output[] = $stack->getStackItem($stackItemType, $from, $stackItemReference); $output[] = $stack->getStackItem('Binary Operator', ':'); } $address = $to; } $val = $address; } } elseif ($val === ExcelError::REF()) { $stackItemReference = $val; } else { /** @var non-empty-string $startRowColRef */ $startRowColRef = $output[count($output) - 1]['value'] ?? ''; [$rangeWS1, $startRowColRef] = Worksheet::extractSheetTitle($startRowColRef, true); $rangeSheetRef = $rangeWS1; if ($rangeWS1 !== '') { $rangeWS1 .= '!'; } if (str_starts_with($rangeSheetRef, "'")) { $rangeSheetRef = Worksheet::unApostrophizeTitle($rangeSheetRef); } [$rangeWS2, $val] = Worksheet::extractSheetTitle($val, true); if ($rangeWS2 !== '') { $rangeWS2 .= '!'; } else { $rangeWS2 = $rangeWS1; } $refSheet = $pCellParent; if ($pCellParent !== null && $rangeSheetRef !== '' && $rangeSheetRef !== $pCellParent->getTitle()) { $refSheet = $pCellParent->getParentOrThrow()->getSheetByName($rangeSheetRef); } if (ctype_digit($val) && $val <= 1048576) { // Row range $stackItemType = 'Row Reference'; $valx = $val; $endRowColRef = ($refSheet !== null) ? $refSheet->getHighestDataColumn($valx) : AddressRange::MAX_COLUMN; // Max 16,384 columns for Excel2007 $val = "{$rangeWS2}{$endRowColRef}{$val}"; } elseif (ctype_alpha($val) && strlen($val) <= 3) { // Column range $stackItemType = 'Column Reference'; $endRowColRef = ($refSheet !== null) ? $refSheet->getHighestDataRow($val) : AddressRange::MAX_ROW; // Max 1,048,576 rows for Excel2007 $val = "{$rangeWS2}{$val}{$endRowColRef}"; } $stackItemReference = $val; } } elseif ($opCharacter === self::FORMULA_STRING_QUOTE) { // UnEscape any quotes within the string $val = self::wrapResult(str_replace('""', self::FORMULA_STRING_QUOTE, StringHelper::convertToString(self::unwrapResult($val)))); } elseif (isset(self::EXCEL_CONSTANTS[trim(strtoupper($val))])) { $stackItemType = 'Constant'; $excelConstant = trim(strtoupper($val)); $val = self::EXCEL_CONSTANTS[$excelConstant]; $stackItemReference = $excelConstant; } elseif (($localeConstant = array_search(trim(strtoupper($val)), self::$localeBoolean)) !== false) { $stackItemType = 'Constant'; $val = self::EXCEL_CONSTANTS[$localeConstant]; $stackItemReference = $localeConstant; } elseif ( preg_match('/^' . self::CALCULATION_REGEXP_ROW_RANGE . '/miu', substr($formula, $index), $rowRangeReference) ) { $val = $rowRangeReference[1]; $length = strlen($rowRangeReference[1]); $stackItemType = 'Row Reference'; // unescape any apostrophes or double quotes in worksheet name $val = str_replace(["''", '""'], ["'", '"'], $val); $column = 'A'; if (($testPrevOp !== null && $testPrevOp['value'] === ':') && $pCellParent !== null) { $column = $pCellParent->getHighestDataColumn($val); } $val = "{$rowRangeReference[2]}{$column}{$rowRangeReference[7]}"; $stackItemReference = $val; } elseif ( preg_match('/^' . self::CALCULATION_REGEXP_COLUMN_RANGE . '/miu', substr($formula, $index), $columnRangeReference) ) { $val = $columnRangeReference[1]; $length = strlen($val); $stackItemType = 'Column Reference'; // unescape any apostrophes or double quotes in worksheet name $val = str_replace(["''", '""'], ["'", '"'], $val); $row = '1'; if (($testPrevOp !== null && $testPrevOp['value'] === ':') && $pCellParent !== null) { $row = $pCellParent->getHighestDataRow($val); } $val = "{$val}{$row}"; $stackItemReference = $val; } elseif (preg_match('/^' . self::CALCULATION_REGEXP_DEFINEDNAME . '.*/miu', $val, $match)) { $stackItemType = 'Defined Name'; $stackItemReference = $val; } elseif (is_numeric($val)) { if ((str_contains((string) $val, '.')) || (stripos((string) $val, 'e') !== false) || ($val > PHP_INT_MAX) || ($val < -PHP_INT_MAX)) { $val = (float) $val; } else { $val = (int) $val; } } $details = $stack->getStackItem($stackItemType, $val, $stackItemReference); if ($localeConstant) { $details['localeValue'] = $localeConstant; } $output[] = $details; } $index += $length; } elseif ($opCharacter === '$') { // absolute row or column range ++$index; } elseif ($opCharacter === ')') { // miscellaneous error checking if ($expectingOperand) { $output[] = $stack->getStackItem('Empty Argument', null, 'NULL'); $expectingOperand = false; $expectingOperator = true; } else { return $this->raiseFormulaError("Formula Error: Unexpected ')'"); } } elseif (isset(self::CALCULATION_OPERATORS[$opCharacter]) && !$expectingOperator) { return $this->raiseFormulaError("Formula Error: Unexpected operator '$opCharacter'"); } else { // I don't even want to know what you did to get here return $this->raiseFormulaError('Formula Error: An unexpected error occurred'); } // Test for end of formula string if ($index == strlen($formula)) { // Did we end with an operator?. // Only valid for the % unary operator if ((isset(self::CALCULATION_OPERATORS[$opCharacter])) && ($opCharacter != '%')) { return $this->raiseFormulaError("Formula Error: Operator '$opCharacter' has no operands"); } break; } // Ignore white space while (($formula[$index] === "\n") || ($formula[$index] === "\r")) { ++$index; } if ($formula[$index] === ' ') { while ($formula[$index] === ' ') { ++$index; } // If we're expecting an operator, but only have a space between the previous and next operands (and both are // Cell References, Defined Names or Structured References) then we have an INTERSECTION operator $countOutputMinus1 = count($output) - 1; if ( ($expectingOperator) && array_key_exists($countOutputMinus1, $output) && is_array($output[$countOutputMinus1]) && array_key_exists('type', $output[$countOutputMinus1]) && ( (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '.*/miu', substr($formula, $index), $match)) && ($output[$countOutputMinus1]['type'] === 'Cell Reference') || (preg_match('/^' . self::CALCULATION_REGEXP_DEFINEDNAME . '.*/miu', substr($formula, $index), $match)) && ($output[$countOutputMinus1]['type'] === 'Defined Name' || $output[$countOutputMinus1]['type'] === 'Value') || (preg_match('/^' . self::CALCULATION_REGEXP_STRUCTURED_REFERENCE . '.*/miu', substr($formula, $index), $match)) && ($output[$countOutputMinus1]['type'] === Operands\StructuredReference::NAME || $output[$countOutputMinus1]['type'] === 'Value') ) ) { while (self::swapOperands($stack, $opCharacter)) { $output[] = $stack->pop(); // Swap operands and higher precedence operators from the stack to the output } $stack->push('Binary Operator', '∩'); // Put an Intersect Operator on the stack $expectingOperator = false; } } } while (($op = $stack->pop()) !== null) { // pop everything off the stack and push onto output if ($op['value'] == '(') { return $this->raiseFormulaError("Formula Error: Expecting ')'"); // if there are any opening braces on the stack, then braces were unbalanced } $output[] = $op; } return $output; } /** * @return mixed */ private static function dataTestReference(array &$operandData) { $operand = $operandData['value']; if (($operandData['reference'] === null) && (is_array($operand))) { $rKeys = array_keys($operand); $rowKey = array_shift($rKeys); if (is_array($operand[$rowKey]) === false) { $operandData['value'] = $operand[$rowKey]; return $operand[$rowKey]; } $cKeys = array_keys(array_keys($operand[$rowKey])); $colKey = array_shift($cKeys); if (ctype_upper("$colKey")) { $operandData['reference'] = $colKey . $rowKey; } } return $operand; } private static int $matchIndex8 = 8; private static int $matchIndex9 = 9; private static int $matchIndex10 = 10; /** * @return array|false|string * @param false|mixed[] $tokens */ private function processTokenStack($tokens, ?string $cellID = null, ?Cell $cell = null) { if ($tokens === false) { return false; } $phpSpreadsheetFunctions = &self::getFunctionsAddress(); // If we're using cell caching, then $pCell may well be flushed back to the cache (which detaches the parent cell collection), // so we store the parent cell collection so that we can re-attach it when necessary $pCellWorksheet = ($cell !== null) ? $cell->getWorksheet() : null; $originalCoordinate = ($nullsafeVariable1 = $cell) ? $nullsafeVariable1->getCoordinate() : null; $pCellParent = ($cell !== null) ? $cell->getParent() : null; $stack = new Stack($this->branchPruner); // Stores branches that have been pruned $fakedForBranchPruning = []; // help us to know when pruning ['branchTestId' => true/false] $branchStore = []; // Loop through each token in turn foreach ($tokens as $tokenIdx => $tokenData) { $this->processingAnchorArray = false; if ($tokenData['type'] === 'Cell Reference' && isset($tokens[$tokenIdx + 1]) && $tokens[$tokenIdx + 1]['type'] === 'Operand Count for Function ANCHORARRAY()') { $this->processingAnchorArray = true; } $token = $tokenData['value']; // Branch pruning: skip useless resolutions $storeKey = $tokenData['storeKey'] ?? null; if ($this->branchPruningEnabled && isset($tokenData['onlyIf'])) { $onlyIfStoreKey = $tokenData['onlyIf']; $storeValue = $branchStore[$onlyIfStoreKey] ?? null; $storeValueAsBool = ($storeValue === null) ? true : (bool) Functions::flattenSingleValue($storeValue); if (is_array($storeValue)) { $wrappedItem = end($storeValue); $storeValue = is_array($wrappedItem) ? end($wrappedItem) : $wrappedItem; } if ( (isset($storeValue) || $tokenData['reference'] === 'NULL') && (!$storeValueAsBool || Information\ErrorValue::isError($storeValue) || ($storeValue === 'Pruned branch')) ) { // If branching value is not true, we don't need to compute if (!isset($fakedForBranchPruning['onlyIf-' . $onlyIfStoreKey])) { $stack->push('Value', 'Pruned branch (only if ' . $onlyIfStoreKey . ') ' . $token); $fakedForBranchPruning['onlyIf-' . $onlyIfStoreKey] = true; } if (isset($storeKey)) { // We are processing an if condition // We cascade the pruning to the depending branches $branchStore[$storeKey] = 'Pruned branch'; $fakedForBranchPruning['onlyIfNot-' . $storeKey] = true; $fakedForBranchPruning['onlyIf-' . $storeKey] = true; } continue; } } if ($this->branchPruningEnabled && isset($tokenData['onlyIfNot'])) { $onlyIfNotStoreKey = $tokenData['onlyIfNot']; $storeValue = $branchStore[$onlyIfNotStoreKey] ?? null; $storeValueAsBool = ($storeValue === null) ? true : (bool) Functions::flattenSingleValue($storeValue); if (is_array($storeValue)) { $wrappedItem = end($storeValue); $storeValue = is_array($wrappedItem) ? end($wrappedItem) : $wrappedItem; } if ( (isset($storeValue) || $tokenData['reference'] === 'NULL') && ($storeValueAsBool || Information\ErrorValue::isError($storeValue) || ($storeValue === 'Pruned branch')) ) { // If branching value is true, we don't need to compute if (!isset($fakedForBranchPruning['onlyIfNot-' . $onlyIfNotStoreKey])) { $stack->push('Value', 'Pruned branch (only if not ' . $onlyIfNotStoreKey . ') ' . $token); $fakedForBranchPruning['onlyIfNot-' . $onlyIfNotStoreKey] = true; } if (isset($storeKey)) { // We are processing an if condition // We cascade the pruning to the depending branches $branchStore[$storeKey] = 'Pruned branch'; $fakedForBranchPruning['onlyIfNot-' . $storeKey] = true; $fakedForBranchPruning['onlyIf-' . $storeKey] = true; } continue; } } if ($token instanceof Operands\StructuredReference) { if ($cell === null) { return $this->raiseFormulaError('Structured References must exist in a Cell context'); } try { $cellRange = $token->parse($cell); if (str_contains($cellRange, ':')) { $this->debugLog->writeDebugLog('Evaluating Structured Reference %s as Cell Range %s', $token->value(), $cellRange); $rangeValue = self::getInstance($cell->getWorksheet()->getParent())->_calculateFormulaValue("={$cellRange}", $cellRange, $cell); $stack->push('Value', $rangeValue); $this->debugLog->writeDebugLog('Evaluated Structured Reference %s as value %s', $token->value(), $this->showValue($rangeValue)); } else { $this->debugLog->writeDebugLog('Evaluating Structured Reference %s as Cell %s', $token->value(), $cellRange); $cellValue = $cell->getWorksheet()->getCell($cellRange)->getCalculatedValue(false); $stack->push('Cell Reference', $cellValue, $cellRange); $this->debugLog->writeDebugLog('Evaluated Structured Reference %s as value %s', $token->value(), $this->showValue($cellValue)); } } catch (Exception $e) { if ($e->getCode() === Exception::CALCULATION_ENGINE_PUSH_TO_STACK) { $stack->push('Error', ExcelError::REF(), null); $this->debugLog->writeDebugLog('Evaluated Structured Reference %s as error value %s', $token->value(), ExcelError::REF()); } else { return $this->raiseFormulaError($e->getMessage(), $e->getCode(), $e); } } } elseif (!is_numeric($token) && !is_object($token) && isset(self::BINARY_OPERATORS[$token])) { // if the token is a binary operator, pop the top two values off the stack, do the operation, and push the result back on the stack // We must have two operands, error if we don't $operand2Data = $stack->pop(); if ($operand2Data === null) { return $this->raiseFormulaError('Internal error - Operand value missing from stack'); } $operand1Data = $stack->pop(); if ($operand1Data === null) { return $this->raiseFormulaError('Internal error - Operand value missing from stack'); } $operand1 = self::dataTestReference($operand1Data); $operand2 = self::dataTestReference($operand2Data); // Log what we're doing if ($token == ':') { $this->debugLog->writeDebugLog('Evaluating Range %s %s %s', $this->showValue($operand1Data['reference']), $token, $this->showValue($operand2Data['reference'])); } else { $this->debugLog->writeDebugLog('Evaluating %s %s %s', $this->showValue($operand1), $token, $this->showValue($operand2)); } // Process the operation in the appropriate manner switch ($token) { // Comparison (Boolean) Operators case '>': // Greater than case '<': // Less than case '>=': // Greater than or Equal to case '<=': // Less than or Equal to case '=': // Equality case '<>': // Inequality $result = $this->executeBinaryComparisonOperation($operand1, $operand2, (string) $token, $stack); if (isset($storeKey)) { $branchStore[$storeKey] = $result; } break; // Binary Operators case ':': // Range if ($operand1Data['type'] === 'Defined Name') { if (preg_match('/$' . self::CALCULATION_REGEXP_DEFINEDNAME . '^/mui', $operand1Data['reference']) !== false && $this->spreadsheet !== null) { $definedName = $this->spreadsheet->getNamedRange($operand1Data['reference']); if ($definedName !== null) { $operand1Data['reference'] = $operand1Data['value'] = str_replace('$', '', $definedName->getValue()); } } } if (str_contains($operand1Data['reference'] ?? '', '!')) { [$sheet1, $operand1Data['reference']] = Worksheet::extractSheetTitle($operand1Data['reference'], true, true); } else { $sheet1 = ($pCellWorksheet !== null) ? $pCellWorksheet->getTitle() : ''; } $sheet1 ??= ''; [$sheet2, $operand2Data['reference']] = Worksheet::extractSheetTitle($operand2Data['reference'], true, true); if (empty($sheet2)) { $sheet2 = $sheet1; } if ($sheet1 === $sheet2) { if ($operand1Data['reference'] === null && $cell !== null) { if (is_array($operand1Data['value'])) { $operand1Data['reference'] = $cell->getCoordinate(); } elseif ((trim($operand1Data['value']) != '') && (is_numeric($operand1Data['value']))) { $operand1Data['reference'] = $cell->getColumn() . $operand1Data['value']; } elseif (trim($operand1Data['value']) == '') { $operand1Data['reference'] = $cell->getCoordinate(); } else { $operand1Data['reference'] = $operand1Data['value'] . $cell->getRow(); } } if ($operand2Data['reference'] === null && $cell !== null) { if (is_array($operand2Data['value'])) { $operand2Data['reference'] = $cell->getCoordinate(); } elseif ((trim($operand2Data['value']) != '') && (is_numeric($operand2Data['value']))) { $operand2Data['reference'] = $cell->getColumn() . $operand2Data['value']; } elseif (trim($operand2Data['value']) == '') { $operand2Data['reference'] = $cell->getCoordinate(); } else { $operand2Data['reference'] = $operand2Data['value'] . $cell->getRow(); } } $oData = array_merge(explode(':', $operand1Data['reference'] ?? ''), explode(':', $operand2Data['reference'] ?? '')); $oCol = $oRow = []; $breakNeeded = false; foreach ($oData as $oDatum) { try { $oCR = Coordinate::coordinateFromString($oDatum); $oCol[] = Coordinate::columnIndexFromString($oCR[0]) - 1; $oRow[] = $oCR[1]; } catch (\Exception $exception) { $stack->push('Error', ExcelError::REF(), null); $breakNeeded = true; break; } } if ($breakNeeded) { break; } $cellRef = Coordinate::stringFromColumnIndex(min($oCol) + 1) . min($oRow) . ':' . Coordinate::stringFromColumnIndex(max($oCol) + 1) . max($oRow); // @phpstan-ignore-line if ($pCellParent !== null && $this->spreadsheet !== null) { $cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($sheet1), false); } else { return $this->raiseFormulaError('Unable to access Cell Reference'); } $this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails($cellValue)); $stack->push('Cell Reference', $cellValue, $cellRef); } else { $this->debugLog->writeDebugLog('Evaluation Result is a #REF! Error'); $stack->push('Error', ExcelError::REF(), null); } break; case '+': // Addition case '-': // Subtraction case '*': // Multiplication case '/': // Division case '^': // Exponential $result = $this->executeNumericBinaryOperation($operand1, $operand2, $token, $stack); if (isset($storeKey)) { $branchStore[$storeKey] = $result; } break; case '&': // Concatenation // If either of the operands is a matrix, we need to treat them both as matrices // (converting the other operand to a matrix if need be); then perform the required // matrix operation $operand1 = self::boolToString($operand1); $operand2 = self::boolToString($operand2); if (is_array($operand1) || is_array($operand2)) { if (is_string($operand1)) { $operand1 = self::unwrapResult($operand1); } if (is_string($operand2)) { $operand2 = self::unwrapResult($operand2); } // Ensure that both operands are arrays/matrices [$rows, $columns] = self::checkMatrixOperands($operand1, $operand2, 2); for ($row = 0; $row < $rows; ++$row) { for ($column = 0; $column < $columns; ++$column) { $op1x = self::boolToString($operand1[$row][$column]); $op2x = self::boolToString($operand2[$row][$column]); if (Information\ErrorValue::isError($op1x)) { // no need to do anything } elseif (Information\ErrorValue::isError($op2x)) { $operand1[$row][$column] = $op2x; } else { /** @var string $op1x */ /** @var string $op2x */ $operand1[$row][$column] = StringHelper::substring( $op1x . $op2x, 0, DataType::MAX_STRING_LENGTH ); } } } $result = $operand1; } else { if (Information\ErrorValue::isError($operand1)) { $result = $operand1; } elseif (Information\ErrorValue::isError($operand2)) { $result = $operand2; } else { $result = str_replace('""', self::FORMULA_STRING_QUOTE, self::unwrapResult($operand1) . self::unwrapResult($operand2)); //* @phpstan-ignore-line $result = StringHelper::substring( $result, 0, DataType::MAX_STRING_LENGTH ); $result = self::FORMULA_STRING_QUOTE . $result . self::FORMULA_STRING_QUOTE; } } $this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails($result)); $stack->push('Value', $result); if (isset($storeKey)) { $branchStore[$storeKey] = $result; } break; case '∩': // Intersect /** @var array $operand1 */ /** @var array $operand2 */ $rowIntersect = array_intersect_key($operand1, $operand2); $cellIntersect = $oCol = $oRow = []; foreach (array_keys($rowIntersect) as $row) { $oRow[] = $row; foreach ($rowIntersect[$row] as $col => $data) { $oCol[] = Coordinate::columnIndexFromString($col) - 1; $cellIntersect[$row] = array_intersect_key($operand1[$row], $operand2[$row]); } } if (count(Functions::flattenArray($cellIntersect)) === 0) { $this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails($cellIntersect)); $stack->push('Error', ExcelError::null(), null); } else { $cellRef = Coordinate::stringFromColumnIndex(min($oCol) + 1) . min($oRow) . ':' // @phpstan-ignore-line . Coordinate::stringFromColumnIndex(max($oCol) + 1) . max($oRow); // @phpstan-ignore-line $this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails($cellIntersect)); $stack->push('Value', $cellIntersect, $cellRef); } break; } } elseif (($token === '~') || ($token === '%')) { // if the token is a unary operator, pop one value off the stack, do the operation, and push it back on if (($arg = $stack->pop()) === null) { return $this->raiseFormulaError('Internal error - Operand value missing from stack'); } $arg = $arg['value']; if ($token === '~') { $this->debugLog->writeDebugLog('Evaluating Negation of %s', $this->showValue($arg)); $multiplier = -1; } else { $this->debugLog->writeDebugLog('Evaluating Percentile of %s', $this->showValue($arg)); $multiplier = 0.01; } if (is_array($arg)) { $operand2 = $multiplier; $result = $arg; [$rows, $columns] = self::checkMatrixOperands($result, $operand2, 0); for ($row = 0; $row < $rows; ++$row) { for ($column = 0; $column < $columns; ++$column) { if (self::isNumericOrBool($result[$row][$column])) { $result[$row][$column] *= $multiplier; } else { $result[$row][$column] = self::makeError($result[$row][$column]); } } } $this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails($result)); $stack->push('Value', $result); if (isset($storeKey)) { $branchStore[$storeKey] = $result; } } else { $this->executeNumericBinaryOperation($multiplier, $arg, '*', $stack); } } elseif (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $token ?? '', $matches)) { $cellRef = null; /* Phpstan says matches[8/9/10] is never set, and code coverage report seems to confirm. Appease PhpStan for now; probably delete this block later. */ if (isset($matches[self::$matchIndex8])) { if ($cell === null) { // We can't access the range, so return a REF error $cellValue = ExcelError::REF(); } else { $cellRef = $matches[6] . $matches[7] . ':' . $matches[self::$matchIndex9] . $matches[self::$matchIndex10]; if ($matches[2] > '') { $matches[2] = trim($matches[2], "\"'"); if ((str_contains($matches[2], '[')) || (str_contains($matches[2], ']'))) { // It's a Reference to an external spreadsheet (not currently supported) return $this->raiseFormulaError('Unable to access External Workbook'); } $matches[2] = trim($matches[2], "\"'"); $this->debugLog->writeDebugLog('Evaluating Cell Range %s in worksheet %s', $cellRef, $matches[2]); if ($pCellParent !== null && $this->spreadsheet !== null) { $cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($matches[2]), false); } else { return $this->raiseFormulaError('Unable to access Cell Reference'); } $this->debugLog->writeDebugLog('Evaluation Result for cells %s in worksheet %s is %s', $cellRef, $matches[2], $this->showTypeDetails($cellValue)); } else { $this->debugLog->writeDebugLog('Evaluating Cell Range %s in current worksheet', $cellRef); if ($pCellParent !== null) { $cellValue = $this->extractCellRange($cellRef, $pCellWorksheet, false); } else { return $this->raiseFormulaError('Unable to access Cell Reference'); } $this->debugLog->writeDebugLog('Evaluation Result for cells %s is %s', $cellRef, $this->showTypeDetails($cellValue)); } } } else { if ($cell === null) { // We can't access the cell, so return a REF error $cellValue = ExcelError::REF(); } else { $cellRef = $matches[6] . $matches[7]; if ($matches[2] > '') { $matches[2] = trim($matches[2], "\"'"); if ((str_contains($matches[2], '[')) || (str_contains($matches[2], ']'))) { // It's a Reference to an external spreadsheet (not currently supported) return $this->raiseFormulaError('Unable to access External Workbook'); } $this->debugLog->writeDebugLog('Evaluating Cell %s in worksheet %s', $cellRef, $matches[2]); if ($pCellParent !== null && $this->spreadsheet !== null) { $cellSheet = $this->spreadsheet->getSheetByName($matches[2]); if ($cellSheet && $cellSheet->cellExists($cellRef)) { $cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($matches[2]), false); $cell->attach($pCellParent); } else { $cellRef = ($cellSheet !== null) ? "'{$matches[2]}'!{$cellRef}" : $cellRef; $cellValue = ($cellSheet !== null) ? null : ExcelError::REF(); } } else { return $this->raiseFormulaError('Unable to access Cell Reference'); } $this->debugLog->writeDebugLog('Evaluation Result for cell %s in worksheet %s is %s', $cellRef, $matches[2], $this->showTypeDetails($cellValue)); } else { $this->debugLog->writeDebugLog('Evaluating Cell %s in current worksheet', $cellRef); if ($pCellParent !== null && $pCellParent->has($cellRef)) { $cellValue = $this->extractCellRange($cellRef, $pCellWorksheet, false); $cell->attach($pCellParent); } else { $cellValue = null; } $this->debugLog->writeDebugLog('Evaluation Result for cell %s is %s', $cellRef, $this->showTypeDetails($cellValue)); } } } if ($this->getInstanceArrayReturnType() === self::RETURN_ARRAY_AS_ARRAY && !$this->processingAnchorArray && is_array($cellValue)) { while (is_array($cellValue)) { $cellValue = array_shift($cellValue); } if (is_string($cellValue)) { $cellValue = preg_replace('/"/', '""', $cellValue); } $this->debugLog->writeDebugLog('Scalar Result for cell %s is %s', $cellRef, $this->showTypeDetails($cellValue)); } $this->processingAnchorArray = false; $stack->push('Cell Value', $cellValue, $cellRef); if (isset($storeKey)) { $branchStore[$storeKey] = $cellValue; } } elseif (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/miu', $token ?? '', $matches)) { // if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on if ($cell !== null && $pCellParent !== null) { $cell->attach($pCellParent); } $functionName = $matches[1]; /** @var array $argCount */ $argCount = $stack->pop(); $argCount = $argCount['value']; if ($functionName !== 'MKMATRIX') { $this->debugLog->writeDebugLog('Evaluating Function %s() with %s argument%s', self::localeFunc($functionName), (($argCount == 0) ? 'no' : $argCount), (($argCount == 1) ? '' : 's')); } if ((isset($phpSpreadsheetFunctions[$functionName])) || (isset(self::$controlFunctions[$functionName]))) { // function $passByReference = false; $passCellReference = false; $functionCall = null; if (isset($phpSpreadsheetFunctions[$functionName])) { $functionCall = $phpSpreadsheetFunctions[$functionName]['functionCall']; $passByReference = isset($phpSpreadsheetFunctions[$functionName]['passByReference']); $passCellReference = isset($phpSpreadsheetFunctions[$functionName]['passCellReference']); } elseif (isset(self::$controlFunctions[$functionName])) { $functionCall = self::$controlFunctions[$functionName]['functionCall']; $passByReference = isset(self::$controlFunctions[$functionName]['passByReference']); $passCellReference = isset(self::$controlFunctions[$functionName]['passCellReference']); } // get the arguments for this function $args = $argArrayVals = []; $emptyArguments = []; for ($i = 0; $i < $argCount; ++$i) { $arg = $stack->pop(); $a = $argCount - $i - 1; if ( ($passByReference) && (isset($phpSpreadsheetFunctions[$functionName]['passByReference'][$a])) //* @phpstan-ignore-line && ($phpSpreadsheetFunctions[$functionName]['passByReference'][$a]) ) { /** @var array $arg */ if ($arg['reference'] === null) { $nextArg = $cellID; if ($functionName === 'ISREF' && ($arg['type'] ?? '') === 'Value') { if (array_key_exists('value', $arg)) { $argValue = $arg['value']; if (is_scalar($argValue)) { $nextArg = $argValue; } elseif (empty($argValue)) { $nextArg = ''; } } } $args[] = $nextArg; if ($functionName !== 'MKMATRIX') { $argArrayVals[] = $this->showValue($cellID); } } else { $args[] = $arg['reference']; if ($functionName !== 'MKMATRIX') { $argArrayVals[] = $this->showValue($arg['reference']); } } } else { /** @var array $arg */ if ($arg['type'] === 'Empty Argument' && in_array($functionName, ['MIN', 'MINA', 'MAX', 'MAXA', 'IF'], true)) { $emptyArguments[] = false; $args[] = $arg['value'] = 0; $this->debugLog->writeDebugLog('Empty Argument reevaluated as 0'); } else { $emptyArguments[] = $arg['type'] === 'Empty Argument'; $args[] = self::unwrapResult($arg['value']); } if ($functionName !== 'MKMATRIX') { $argArrayVals[] = $this->showValue($arg['value']); } } } // Reverse the order of the arguments krsort($args); krsort($emptyArguments); if ($argCount > 0 && is_array($functionCall)) { $args = $this->addDefaultArgumentValues($functionCall, $args, $emptyArguments); } if (($passByReference) && ($argCount == 0)) { $args[] = $cellID; $argArrayVals[] = $this->showValue($cellID); } if ($functionName !== 'MKMATRIX') { if ($this->debugLog->getWriteDebugLog()) { krsort($argArrayVals); $this->debugLog->writeDebugLog('Evaluating %s ( %s )', self::localeFunc($functionName), implode(self::$localeArgumentSeparator . ' ', Functions::flattenArray($argArrayVals))); } } // Process the argument with the appropriate function call if ($pCellWorksheet !== null && $originalCoordinate !== null) { $pCellWorksheet->getCell($originalCoordinate); } $args = $this->addCellReference($args, $passCellReference, $functionCall, $cell); if (!is_array($functionCall)) { foreach ($args as &$arg) { $arg = Functions::flattenSingleValue($arg); } unset($arg); } $result = call_user_func_array($functionCall, $args); if ($functionName !== 'MKMATRIX') { $this->debugLog->writeDebugLog('Evaluation Result for %s() function call is %s', self::localeFunc($functionName), $this->showTypeDetails($result)); } $stack->push('Value', self::wrapResult($result)); if (isset($storeKey)) { $branchStore[$storeKey] = $result; } } } else { // if the token is a number, boolean, string or an Excel error, push it onto the stack if (isset(self::EXCEL_CONSTANTS[strtoupper($token ?? '')])) { $excelConstant = strtoupper($token); $stack->push('Constant Value', self::EXCEL_CONSTANTS[$excelConstant]); if (isset($storeKey)) { $branchStore[$storeKey] = self::EXCEL_CONSTANTS[$excelConstant]; } $this->debugLog->writeDebugLog('Evaluating Constant %s as %s', $excelConstant, $this->showTypeDetails(self::EXCEL_CONSTANTS[$excelConstant])); } elseif ((is_numeric($token)) || ($token === null) || (is_bool($token)) || ($token == '') || ($token[0] == self::FORMULA_STRING_QUOTE) || ($token[0] == '#')) { $stack->push($tokenData['type'], $token, $tokenData['reference']); if (isset($storeKey)) { $branchStore[$storeKey] = $token; } } elseif (preg_match('/^' . self::CALCULATION_REGEXP_DEFINEDNAME . '$/miu', $token, $matches)) { // if the token is a named range or formula, evaluate it and push the result onto the stack $definedName = $matches[6]; if (str_starts_with($definedName, '_xleta')) { return Functions::NOT_YET_IMPLEMENTED; } if ($cell === null || $pCellWorksheet === null) { return $this->raiseFormulaError("undefined name '$token'"); } $specifiedWorksheet = trim($matches[2], "'"); $this->debugLog->writeDebugLog('Evaluating Defined Name %s', $definedName); $namedRange = DefinedName::resolveName($definedName, $pCellWorksheet, $specifiedWorksheet); // If not Defined Name, try as Table. if ($namedRange === null && $this->spreadsheet !== null) { $table = $this->spreadsheet->getTableByName($definedName); if ($table !== null) { $tableRange = Coordinate::getRangeBoundaries($table->getRange()); if ($table->getShowHeaderRow()) { ++$tableRange[0][1]; } if ($table->getShowTotalsRow()) { --$tableRange[1][1]; } $tableRangeString = '$' . $tableRange[0][0] . '$' . $tableRange[0][1] . ':' . '$' . $tableRange[1][0] . '$' . $tableRange[1][1]; $namedRange = new NamedRange($definedName, $table->getWorksheet(), $tableRangeString); } } if ($namedRange === null) { return $this->raiseFormulaError("undefined name '$definedName'"); } $result = $this->evaluateDefinedName($cell, $namedRange, $pCellWorksheet, $stack, $specifiedWorksheet !== ''); if (isset($storeKey)) { $branchStore[$storeKey] = $result; } } else { return $this->raiseFormulaError("undefined name '$token'"); } } } // when we're out of tokens, the stack should have a single element, the final result if ($stack->count() != 1) { return $this->raiseFormulaError('internal error'); } /** @var array $output */ $output = $stack->pop(); $output = $output['value']; return $output; } /** * @param mixed $operand */ private function validateBinaryOperand(&$operand, Stack &$stack): bool { if (is_array($operand)) { if ((count($operand, COUNT_RECURSIVE) - count($operand)) == 1) { do { $operand = array_pop($operand); } while (is_array($operand)); } } // Numbers, matrices and booleans can pass straight through, as they're already valid if (is_string($operand)) { // We only need special validations for the operand if it is a string // Start by stripping off the quotation marks we use to identify true excel string values internally if ($operand > '' && $operand[0] == self::FORMULA_STRING_QUOTE) { $operand = StringHelper::convertToString(self::unwrapResult($operand)); } // If the string is a numeric value, we treat it as a numeric, so no further testing if (!is_numeric($operand)) { // If not a numeric, test to see if the value is an Excel error, and so can't be used in normal binary operations if ($operand > '' && $operand[0] == '#') { $stack->push('Value', $operand); $this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails($operand)); return false; } elseif (Engine\FormattedNumber::convertToNumberIfFormatted($operand) === false) { // If not a numeric, a fraction or a percentage, then it's a text string, and so can't be used in mathematical binary operations $stack->push('Error', '#VALUE!'); $this->debugLog->writeDebugLog('Evaluation Result is a %s', $this->showTypeDetails('#VALUE!')); return false; } } } // return a true if the value of the operand is one that we can use in normal binary mathematical operations return true; } /** * @param mixed $operand1 * @param mixed $operand2 */ private function executeArrayComparison($operand1, $operand2, string $operation, Stack &$stack, bool $recursingArrays): array { $result = []; if (!is_array($operand2) && is_array($operand1)) { // Operand 1 is an array, Operand 2 is a scalar foreach ($operand1 as $x => $operandData) { $this->debugLog->writeDebugLog('Evaluating Comparison %s %s %s', $this->showValue($operandData), $operation, $this->showValue($operand2)); $this->executeBinaryComparisonOperation($operandData, $operand2, $operation, $stack); /** @var array $r */ $r = $stack->pop(); $result[$x] = $r['value']; } } elseif (is_array($operand2) && !is_array($operand1)) { // Operand 1 is a scalar, Operand 2 is an array foreach ($operand2 as $x => $operandData) { $this->debugLog->writeDebugLog('Evaluating Comparison %s %s %s', $this->showValue($operand1), $operation, $this->showValue($operandData)); $this->executeBinaryComparisonOperation($operand1, $operandData, $operation, $stack); /** @var array $r */ $r = $stack->pop(); $result[$x] = $r['value']; } } elseif (is_array($operand2) && is_array($operand1)) { // Operand 1 and Operand 2 are both arrays if (!$recursingArrays) { self::checkMatrixOperands($operand1, $operand2, 2); } foreach ($operand1 as $x => $operandData) { $this->debugLog->writeDebugLog('Evaluating Comparison %s %s %s', $this->showValue($operandData), $operation, $this->showValue($operand2[$x])); $this->executeBinaryComparisonOperation($operandData, $operand2[$x], $operation, $stack, true); /** @var array $r */ $r = $stack->pop(); $result[$x] = $r['value']; } } else { throw new Exception('Neither operand is an arra'); } // Log the result details $this->debugLog->writeDebugLog('Comparison Evaluation Result is %s', $this->showTypeDetails($result)); // And push the result onto the stack $stack->push('Array', $result); return $result; } /** * @return mixed[]|bool * @param mixed $operand1 * @param mixed $operand2 */ private function executeBinaryComparisonOperation($operand1, $operand2, string $operation, Stack &$stack, bool $recursingArrays = false) { // If we're dealing with matrix operations, we want a matrix result if ((is_array($operand1)) || (is_array($operand2))) { return $this->executeArrayComparison($operand1, $operand2, $operation, $stack, $recursingArrays); } $result = BinaryComparison::compare($operand1, $operand2, $operation); // Log the result details $this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails($result)); // And push the result onto the stack $stack->push('Value', $result); return $result; } /** * @param mixed $operand1 * @param mixed $operand2 * @return mixed */ private function executeNumericBinaryOperation($operand1, $operand2, string $operation, Stack &$stack) { // Validate the two operands if ( ($this->validateBinaryOperand($operand1, $stack) === false) || ($this->validateBinaryOperand($operand2, $stack) === false) ) { return false; } if ( (Functions::getCompatibilityMode() != Functions::COMPATIBILITY_OPENOFFICE) && ((is_string($operand1) && !is_numeric($operand1) && $operand1 !== '') || (is_string($operand2) && !is_numeric($operand2) && $operand2 !== '')) ) { $result = ExcelError::VALUE(); } elseif (is_array($operand1) || is_array($operand2)) { // Ensure that both operands are arrays/matrices if (is_array($operand1)) { foreach ($operand1 as $key => $value) { $operand1[$key] = Functions::flattenArray($value); } } if (is_array($operand2)) { foreach ($operand2 as $key => $value) { $operand2[$key] = Functions::flattenArray($value); } } [$rows, $columns] = self::checkMatrixOperands($operand1, $operand2, 3); for ($row = 0; $row < $rows; ++$row) { for ($column = 0; $column < $columns; ++$column) { if ($operand1[$row][$column] === null) { $operand1[$row][$column] = 0; } elseif (!self::isNumericOrBool($operand1[$row][$column])) { $operand1[$row][$column] = self::makeError($operand1[$row][$column]); continue; } if ($operand2[$row][$column] === null) { $operand2[$row][$column] = 0; } elseif (!self::isNumericOrBool($operand2[$row][$column])) { $operand1[$row][$column] = self::makeError($operand2[$row][$column]); continue; } /** @var float|int */ $operand1Val = $operand1[$row][$column]; /** @var float|int */ $operand2Val = $operand2[$row][$column]; switch ($operation) { case '+': $operand1[$row][$column] = $operand1Val + $operand2Val; break; case '-': $operand1[$row][$column] = $operand1Val - $operand2Val; break; case '*': $operand1[$row][$column] = $operand1Val * $operand2Val; break; case '/': if ($operand2Val == 0) { $operand1[$row][$column] = ExcelError::DIV0(); } else { $operand1[$row][$column] = $operand1Val / $operand2Val; } break; case '^': $operand1[$row][$column] = $operand1Val ** $operand2Val; break; default: throw new Exception('Unsupported numeric binary operation'); } } } $result = $operand1; } else { // If we're dealing with non-matrix operations, execute the necessary operation /** @var float|int $operand1 */ /** @var float|int $operand2 */ switch ($operation) { // Addition case '+': $result = $operand1 + $operand2; break; // Subtraction case '-': $result = $operand1 - $operand2; break; // Multiplication case '*': $result = $operand1 * $operand2; break; // Division case '/': if ($operand2 == 0) { // Trap for Divide by Zero error $stack->push('Error', ExcelError::DIV0()); $this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails(ExcelError::DIV0())); return false; } $result = $operand1 / $operand2; break; // Power case '^': $result = $operand1 ** $operand2; break; default: throw new Exception('Unsupported numeric binary operation'); } } // Log the result details $this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails($result)); // And push the result onto the stack $stack->push('Value', $result); return $result; } /** * Trigger an error, but nicely, if need be. * * @return false */ protected function raiseFormulaError(string $errorMessage, int $code = 0, ?Throwable $exception = null): bool { $this->formulaError = $errorMessage; $this->cyclicReferenceStack->clear(); $suppress = $this->suppressFormulaErrors; if (!$suppress) { throw new Exception($errorMessage, $code, $exception); } return false; } /** * Extract range values. * * @param string $range String based range representation * @param ?Worksheet $worksheet Worksheet * @param bool $resetLog Flag indicating whether calculation log should be reset or not * * @return array Array of values in range if range contains more than one element. Otherwise, a single value is returned. */ public function extractCellRange(string &$range = 'A1', ?Worksheet $worksheet = null, bool $resetLog = true): array { // Return value $returnValue = []; if ($worksheet !== null) { $worksheetName = $worksheet->getTitle(); if (str_contains($range, '!')) { [$worksheetName, $range] = Worksheet::extractSheetTitle($range, true, true); $worksheet = ($this->spreadsheet === null) ? null : $this->spreadsheet->getSheetByName($worksheetName); } // Extract range $aReferences = Coordinate::extractAllCellReferencesInRange($range); $range = "'" . $worksheetName . "'" . '!' . $range; $currentCol = ''; $currentRow = 0; if (!isset($aReferences[1])) { // Single cell in range sscanf($aReferences[0], '%[A-Z]%d', $currentCol, $currentRow); if ($worksheet !== null && $worksheet->cellExists($aReferences[0])) { $temp = $worksheet->getCell($aReferences[0])->getCalculatedValue($resetLog); if ($this->getInstanceArrayReturnType() === self::RETURN_ARRAY_AS_ARRAY) { while (is_array($temp)) { $temp = array_shift($temp); } } $returnValue[$currentRow][$currentCol] = $temp; } else { $returnValue[$currentRow][$currentCol] = null; } } else { // Extract cell data for all cells in the range foreach ($aReferences as $reference) { // Extract range sscanf($reference, '%[A-Z]%d', $currentCol, $currentRow); if ($worksheet !== null && $worksheet->cellExists($reference)) { $temp = $worksheet->getCell($reference)->getCalculatedValue($resetLog); if ($this->getInstanceArrayReturnType() === self::RETURN_ARRAY_AS_ARRAY) { while (is_array($temp)) { $temp = array_shift($temp); } } $returnValue[$currentRow][$currentCol] = $temp; } else { $returnValue[$currentRow][$currentCol] = null; } } } } return $returnValue; } /** * Extract range values. * * @param string $range String based range representation * @param null|Worksheet $worksheet Worksheet * @param bool $resetLog Flag indicating whether calculation log should be reset or not * * @return array|string Array of values in range if range contains more than one element. Otherwise, a single value is returned. */ public function extractNamedRange(string &$range = 'A1', ?Worksheet $worksheet = null, bool $resetLog = true) { // Return value $returnValue = []; if ($worksheet !== null) { if (str_contains($range, '!')) { [$worksheetName, $range] = Worksheet::extractSheetTitle($range, true, true); $worksheet = ($this->spreadsheet === null) ? null : $this->spreadsheet->getSheetByName($worksheetName); } // Named range? $namedRange = ($worksheet === null) ? null : DefinedName::resolveName($range, $worksheet); if ($namedRange === null) { return ExcelError::REF(); } $worksheet = $namedRange->getWorksheet(); $range = $namedRange->getValue(); $splitRange = Coordinate::splitRange($range); // Convert row and column references if ($worksheet !== null && ctype_alpha($splitRange[0][0])) { $range = $splitRange[0][0] . '1:' . $splitRange[0][1] . $worksheet->getHighestRow(); } elseif ($worksheet !== null && ctype_digit($splitRange[0][0])) { $range = 'A' . $splitRange[0][0] . ':' . $worksheet->getHighestColumn() . $splitRange[0][1]; } // Extract range $aReferences = Coordinate::extractAllCellReferencesInRange($range); if (!isset($aReferences[1])) { // Single cell (or single column or row) in range [$currentCol, $currentRow] = Coordinate::coordinateFromString($aReferences[0]); if ($worksheet !== null && $worksheet->cellExists($aReferences[0])) { $returnValue[$currentRow][$currentCol] = $worksheet->getCell($aReferences[0])->getCalculatedValue($resetLog); } else { $returnValue[$currentRow][$currentCol] = null; } } else { // Extract cell data for all cells in the range foreach ($aReferences as $reference) { // Extract range [$currentCol, $currentRow] = Coordinate::coordinateFromString($reference); if ($worksheet !== null && $worksheet->cellExists($reference)) { $returnValue[$currentRow][$currentCol] = $worksheet->getCell($reference)->getCalculatedValue($resetLog); } else { $returnValue[$currentRow][$currentCol] = null; } } } } return $returnValue; } /** * Is a specific function implemented? * * @param string $function Function Name */ public function isImplemented(string $function): bool { $function = strtoupper($function); $phpSpreadsheetFunctions = &self::getFunctionsAddress(); $notImplemented = !isset($phpSpreadsheetFunctions[$function]) || (is_array($phpSpreadsheetFunctions[$function]['functionCall']) && $phpSpreadsheetFunctions[$function]['functionCall'][1] === 'DUMMY'); return !$notImplemented; } /** * Get a list of implemented Excel function names. */ public function getImplementedFunctionNames(): array { $returnValue = []; $phpSpreadsheetFunctions = &self::getFunctionsAddress(); foreach ($phpSpreadsheetFunctions as $functionName => $function) { if ($this->isImplemented($functionName)) { $returnValue[] = $functionName; } } return $returnValue; } private function addDefaultArgumentValues(array $functionCall, array $args, array $emptyArguments): array { $reflector = new ReflectionMethod($functionCall[0], $functionCall[1]); $methodArguments = $reflector->getParameters(); if (count($methodArguments) > 0) { // Apply any defaults for empty argument values foreach ($emptyArguments as $argumentId => $isArgumentEmpty) { if ($isArgumentEmpty === true) { $reflectedArgumentId = count($args) - (int) $argumentId - 1; if ( !array_key_exists($reflectedArgumentId, $methodArguments) || $methodArguments[$reflectedArgumentId]->isVariadic() ) { break; } $args[$argumentId] = $this->getArgumentDefaultValue($methodArguments[$reflectedArgumentId]); } } } return $args; } /** * @return mixed */ private function getArgumentDefaultValue(ReflectionParameter $methodArgument) { $defaultValue = null; if ($methodArgument->isDefaultValueAvailable()) { $defaultValue = $methodArgument->getDefaultValue(); if ($methodArgument->isDefaultValueConstant()) { $constantName = $methodArgument->getDefaultValueConstantName() ?? ''; // read constant value if (str_contains($constantName, '::')) { [$className, $constantName] = explode('::', $constantName); $constantReflector = new ReflectionClassConstant($className, $constantName); return $constantReflector->getValue(); } return constant($constantName); } } return $defaultValue; } /** * Add cell reference if needed while making sure that it is the last argument. * @param mixed[]|string $functionCall */ private function addCellReference(array $args, bool $passCellReference, $functionCall, ?Cell $cell = null): array { if ($passCellReference) { if (is_array($functionCall)) { $className = $functionCall[0]; $methodName = $functionCall[1]; $reflectionMethod = new ReflectionMethod($className, $methodName); $argumentCount = count($reflectionMethod->getParameters()); while (count($args) < $argumentCount - 1) { $args[] = null; } } $args[] = $cell; } return $args; } /** * @return mixed */ private function evaluateDefinedName(Cell $cell, DefinedName $namedRange, Worksheet $cellWorksheet, Stack $stack, bool $ignoreScope = false) { $definedNameScope = $namedRange->getScope(); if ($definedNameScope !== null && $definedNameScope !== $cellWorksheet && !$ignoreScope) { // The defined name isn't in our current scope, so #REF $result = ExcelError::REF(); $stack->push('Error', $result, $namedRange->getName()); return $result; } $definedNameValue = $namedRange->getValue(); $definedNameType = $namedRange->isFormula() ? 'Formula' : 'Range'; $definedNameWorksheet = $namedRange->getWorksheet(); if ($definedNameValue[0] !== '=') { $definedNameValue = '=' . $definedNameValue; } $this->debugLog->writeDebugLog('Defined Name is a %s with a value of %s', $definedNameType, $definedNameValue); $originalCoordinate = $cell->getCoordinate(); $recursiveCalculationCell = ($definedNameType !== 'Formula' && $definedNameWorksheet !== null && $definedNameWorksheet !== $cellWorksheet) ? $definedNameWorksheet->getCell('A1') : $cell; $recursiveCalculationCellAddress = $recursiveCalculationCell->getCoordinate(); // Adjust relative references in ranges and formulae so that we execute the calculation for the correct rows and columns $definedNameValue = ReferenceHelper::getInstance() ->updateFormulaReferencesAnyWorksheet( $definedNameValue, Coordinate::columnIndexFromString( $cell->getColumn() ) - 1, $cell->getRow() - 1 ); $this->debugLog->writeDebugLog('Value adjusted for relative references is %s', $definedNameValue); $recursiveCalculator = new self($this->spreadsheet); $recursiveCalculator->getDebugLog()->setWriteDebugLog($this->getDebugLog()->getWriteDebugLog()); $recursiveCalculator->getDebugLog()->setEchoDebugLog($this->getDebugLog()->getEchoDebugLog()); $result = $recursiveCalculator->_calculateFormulaValue($definedNameValue, $recursiveCalculationCellAddress, $recursiveCalculationCell, true); $cellWorksheet->getCell($originalCoordinate); if ($this->getDebugLog()->getWriteDebugLog()) { $this->debugLog->mergeDebugLog(array_slice($recursiveCalculator->getDebugLog()->getLog(), 3)); $this->debugLog->writeDebugLog('Evaluation Result for Named %s %s is %s', $definedNameType, $namedRange->getName(), $this->showTypeDetails($result)); } $stack->push('Defined Name', $result, $namedRange->getName()); return $result; } public function setSuppressFormulaErrors(bool $suppressFormulaErrors): self { $this->suppressFormulaErrors = $suppressFormulaErrors; return $this; } public function getSuppressFormulaErrors(): bool { return $this->suppressFormulaErrors; } /** * @param mixed $operand1 * @return mixed */ public static function boolToString($operand1) { if (is_bool($operand1)) { $operand1 = ($operand1) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE']; } elseif ($operand1 === null) { $operand1 = ''; } return $operand1; } /** * @param mixed $operand */ private static function isNumericOrBool($operand): bool { return is_numeric($operand) || is_bool($operand); } /** * @param mixed $operand */ private static function makeError($operand = ''): string { return (is_string($operand) && Information\ErrorValue::isError($operand)) ? $operand : ExcelError::VALUE(); } private static function swapOperands(Stack $stack, string $opCharacter): bool { $retVal = false; if ($stack->count() > 0) { $o2 = $stack->last(); if ($o2) { if (isset(self::CALCULATION_OPERATORS[$o2['value']])) { $retVal = (self::OPERATOR_PRECEDENCE[$opCharacter] ?? 0) <= self::OPERATOR_PRECEDENCE[$o2['value']]; } } } return $retVal; } public function getSpreadsheet(): ?Spreadsheet { return $this->spreadsheet; } } PK!Њ8libraries/vendor/PhpSpreadsheet/Calculation/Category.phpnu[eo ;libraries/vendor/PhpSpreadsheet/Calculation/Token/Stack.phpnu[ */ private array $stack = []; /** * Count of entries in the parser stack. */ private int $count = 0; public function __construct(BranchPruner $branchPruner) { $this->branchPruner = $branchPruner; } /** * Return the number of entries on the stack. */ public function count(): int { return $this->count; } /** * Push a new entry onto the stack. * @param mixed $value */ public function push(string $type, $value, ?string $reference = null): void { $stackItem = $this->getStackItem($type, $value, $reference); $this->stack[$this->count++] = $stackItem; if ($type === 'Function') { $localeFunction = Calculation::localeFunc(StringHelper::convertToString($value)); if ($localeFunction != $value) { $this->stack[($this->count - 1)]['localeValue'] = $localeFunction; } } } public function pushStackItem(array $stackItem): void { $this->stack[$this->count++] = $stackItem; } /** * @param mixed $value */ public function getStackItem(string $type, $value, ?string $reference = null): array { $stackItem = [ 'type' => $type, 'value' => $value, 'reference' => $reference, ]; // will store the result under this alias $storeKey = $this->branchPruner->currentCondition(); if (isset($storeKey) || $reference === 'NULL') { $stackItem['storeKey'] = $storeKey; } // will only run computation if the matching store key is true $onlyIf = $this->branchPruner->currentOnlyIf(); if (isset($onlyIf) || $reference === 'NULL') { $stackItem['onlyIf'] = $onlyIf; } // will only run computation if the matching store key is false $onlyIfNot = $this->branchPruner->currentOnlyIfNot(); if (isset($onlyIfNot) || $reference === 'NULL') { $stackItem['onlyIfNot'] = $onlyIfNot; } return $stackItem; } /** * Pop the last entry from the stack. */ public function pop(): ?array { if ($this->count > 0) { return $this->stack[--$this->count]; } return null; } /** * Return an entry from the stack without removing it. */ public function last(int $n = 1): ?array { if ($this->count - $n < 0) { return null; } return $this->stack[$this->count - $n]; } /** * Clear the stack. */ public function clear(): void { $this->stack = []; $this->count = 0; } } PK!;libraries/vendor/PhpSpreadsheet/Calculation/Token/index.phpnu[?libraries/vendor/PhpSpreadsheet/Calculation/CalculationBase.phpnu[> */ public static function getFunctions(): array { return FunctionArray::$phpSpreadsheetFunctions; } /** * Get address of list of all implemented functions as an array of function objects. * * @return array> */ protected static function &getFunctionsAddress(): array { return FunctionArray::$phpSpreadsheetFunctions; } /** * @param array> $value */ public static function addFunction(string $key, array $value): bool { $key = strtoupper($key); if (array_key_exists($key, FunctionArray::$phpSpreadsheetFunctions)) { return false; } $value['custom'] = true; FunctionArray::$phpSpreadsheetFunctions[$key] = $value; return true; } public static function removeFunction(string $key): bool { $key = strtoupper($key); if (array_key_exists($key, FunctionArray::$phpSpreadsheetFunctions)) { if (FunctionArray::$phpSpreadsheetFunctions[$key]['custom'] ?? false) { unset(FunctionArray::$phpSpreadsheetFunctions[$key]); return true; } } return false; } /** * Registers custom functions and aliases that TablePress uses. * * These functions don't exist in Excel and need to be aliases or replaced by a custom implementation, to maintain backward compatibility. * These functions are deprecated in TablePress and should be replaced with their corresponding function! * * For functions with an underscore (_) in their name, the Calculation::CALCULATION_REGEXP_FUNCTION regexp has been adjusted. */ protected function register_tablepress_aliases_and_custom_functions() { // Trigonometric ARC functions only have an A prefix in Excel. FunctionArray::$phpSpreadsheetFunctions['ARCCOS'] = FunctionArray::$phpSpreadsheetFunctions['ACOS']; FunctionArray::$phpSpreadsheetFunctions['ARCCOSH'] = FunctionArray::$phpSpreadsheetFunctions['ACOSH']; FunctionArray::$phpSpreadsheetFunctions['ARCCOT'] = FunctionArray::$phpSpreadsheetFunctions['ACOT']; FunctionArray::$phpSpreadsheetFunctions['ARCCOTH'] = FunctionArray::$phpSpreadsheetFunctions['ACOTH']; FunctionArray::$phpSpreadsheetFunctions['ARCSIN'] = FunctionArray::$phpSpreadsheetFunctions['ASIN']; FunctionArray::$phpSpreadsheetFunctions['ARCSINH'] = FunctionArray::$phpSpreadsheetFunctions['ASINH']; FunctionArray::$phpSpreadsheetFunctions['ARCTAN'] = FunctionArray::$phpSpreadsheetFunctions['ATAN']; FunctionArray::$phpSpreadsheetFunctions['ARCTAN2'] = FunctionArray::$phpSpreadsheetFunctions['ATAN2']; FunctionArray::$phpSpreadsheetFunctions['ARCTANH'] = FunctionArray::$phpSpreadsheetFunctions['ATANH']; // Aliases for functions with different names in Excel. FunctionArray::$phpSpreadsheetFunctions['MEAN'] = FunctionArray::$phpSpreadsheetFunctions['AVERAGE']; FunctionArray::$phpSpreadsheetFunctions['CEIL'] = FunctionArray::$phpSpreadsheetFunctions['CEILING']; FunctionArray::$phpSpreadsheetFunctions['RAND_INT'] = FunctionArray::$phpSpreadsheetFunctions['RANDBETWEEN']; FunctionArray::$phpSpreadsheetFunctions['RAND_FLOAT'] = FunctionArray::$phpSpreadsheetFunctions['RAND']; // Custom functions for which there is no corresponding function in Excel. FunctionArray::$phpSpreadsheetFunctions['NUMBER_FORMAT'] = array( 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Format::class, 'NUMBER_FORMAT'], 'argumentCount' => '1,2', ); FunctionArray::$phpSpreadsheetFunctions['NUMBER_FORMAT_EU'] = array( 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [TextData\Format::class, 'NUMBER_FORMAT_EU'], 'argumentCount' => '1,2', ); FunctionArray::$phpSpreadsheetFunctions['RANGE'] = array( 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Range::class, 'RANGE'], 'argumentCount' => '1+', ); } } PK!5libraries/vendor/PhpSpreadsheet/Calculation/index.phpnu[getMessage(); } // Execute function $PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue); if ($method == Constants::STARTWEEK_MONDAY_ISO) { Helpers::silly1900($PHPDateObject); return (int) $PHPDateObject->format('W'); } if (self::buggyWeekNum1904($method, $origDateValueNull, $PHPDateObject)) { return 0; } Helpers::silly1900($PHPDateObject, '+ 5 years'); // 1905 calendar matches $dayOfYear = (int) $PHPDateObject->format('z'); $PHPDateObject->modify('-' . $dayOfYear . ' days'); $firstDayOfFirstWeek = (int) $PHPDateObject->format('w'); $daysInFirstWeek = (6 - $firstDayOfFirstWeek + $method) % 7; $daysInFirstWeek += 7 * !$daysInFirstWeek; $endFirstWeek = $daysInFirstWeek - 1; $weekOfYear = floor(($dayOfYear - $endFirstWeek + 13) / 7); return (int) $weekOfYear; } /** * ISOWEEKNUM. * * Returns the ISO 8601 week number of the year for a specified date. * * Excel Function: * ISOWEEKNUM(dateValue) * * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string * Or can be an array of date values * * @return array|int|string Week Number * If an array of numbers is passed as the argument, then the returned result will also be an array * with the same dimensions */ public static function isoWeekNumber($dateValue) { if (is_array($dateValue)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $dateValue); } if (self::apparentBug($dateValue)) { return 52; } try { $dateValue = Helpers::getDateValue($dateValue); } catch (Exception $e) { return $e->getMessage(); } // Execute function $PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue); Helpers::silly1900($PHPDateObject); return (int) $PHPDateObject->format('W'); } /** * WEEKDAY. * * Returns the day of the week for a specified date. The day is given as an integer * ranging from 0 to 7 (dependent on the requested style). * * Excel Function: * WEEKDAY(dateValue[,style]) * * @param null|array|bool|float|int|string $dateValue Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string * Or can be an array of date values * @param mixed $style A number that determines the type of return value * 1 or omitted Numbers 1 (Sunday) through 7 (Saturday). * 2 Numbers 1 (Monday) through 7 (Sunday). * 3 Numbers 0 (Monday) through 6 (Sunday). * Or can be an array of styles * * @return array|int|string Day of the week value * If an array of values is passed as the argument, then the returned result will also be an array * with the same dimensions */ public static function day($dateValue, $style = 1) { if (is_array($dateValue) || is_array($style)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $dateValue, $style); } try { $dateValue = Helpers::getDateValue($dateValue); $style = self::validateStyle($style); } catch (Exception $e) { return $e->getMessage(); } // Execute function $PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue); Helpers::silly1900($PHPDateObject); $DoW = (int) $PHPDateObject->format('w'); switch ($style) { case 1: ++$DoW; break; case 2: $DoW = self::dow0Becomes7($DoW); break; case 3: $DoW = self::dow0Becomes7($DoW) - 1; break; } return $DoW; } /** * @param mixed $style expect int */ private static function validateStyle($style): int { if (!is_numeric($style)) { throw new Exception(ExcelError::VALUE()); } $style = (int) $style; if (($style < 1) || ($style > 3)) { throw new Exception(ExcelError::NAN()); } return $style; } private static function dow0Becomes7(int $DoW): int { return ($DoW === 0) ? 7 : $DoW; } /** * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string */ private static function apparentBug($dateValue): bool { if (SharedDateHelper::getExcelCalendar() !== SharedDateHelper::CALENDAR_MAC_1904) { if (is_bool($dateValue)) { return true; } if (is_numeric($dateValue) && !((int) $dateValue)) { return true; } } return false; } /** * Validate dateValue parameter. * @param mixed $dateValue */ private static function validateDateValue($dateValue): float { if (is_bool($dateValue)) { throw new Exception(ExcelError::VALUE()); } return Helpers::getDateValue($dateValue); } /** * Validate method parameter. * @param mixed $method */ private static function validateMethod($method): int { if ($method === null) { $method = Constants::STARTWEEK_SUNDAY; } if (!is_numeric($method)) { throw new Exception(ExcelError::VALUE()); } $method = (int) $method; if (!array_key_exists($method, Constants::METHODARR)) { throw new Exception(ExcelError::NAN()); } $method = Constants::METHODARR[$method]; return $method; } private static function buggyWeekNum1900(int $method): bool { return $method === Constants::DOW_SUNDAY && SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_WINDOWS_1900; } private static function buggyWeekNum1904(int $method, bool $origNull, DateTime $dateObject): bool { // This appears to be another Excel bug. return $method === Constants::DOW_SUNDAY && SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_MAC_1904 && !$origNull && $dateObject->format('Y-m-d') === '1904-01-01'; } } PK!c(qqGlibraries/vendor/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.phpnu[ 31)) { if ($yearFound) { return ExcelError::VALUE(); } if ($t < 100) { $t += 1900; } $yearFound = true; } } if (count($t1) === 1) { // We've been fed a time value without any date return ((!str_contains((string) $t, ':'))) ? ExcelError::Value() : 0.0; } unset($t); $dateValue = self::t1ToString($t1, $dti, $yearFound); $PHPDateArray = self::setUpArray($dateValue, $dti); return self::finalResults($PHPDateArray, $dti, $baseYear); } private static function t1ToString(array $t1, DateTimeImmutable $dti, bool $yearFound): string { if (count($t1) == 2) { // We only have two parts of the date: either day/month or month/year if ($yearFound) { array_unshift($t1, 1); } else { if (is_numeric($t1[1]) && $t1[1] > 29) { $t1[1] += 1900; array_unshift($t1, 1); } else { $t1[] = $dti->format('Y'); } } } $dateValue = implode(' ', $t1); return $dateValue; } /** * Parse date. */ private static function setUpArray(string $dateValue, DateTimeImmutable $dti): array { $PHPDateArray = Helpers::dateParse($dateValue); if (!Helpers::dateParseSucceeded($PHPDateArray)) { // If original count was 1, we've already returned. // If it was 2, we added another. // Therefore, neither of the first 2 stroks below can fail. $testVal1 = strtok($dateValue, '- '); $testVal2 = strtok('- '); $testVal3 = strtok('- ') ?: $dti->format('Y'); Helpers::adjustYear((string) $testVal1, (string) $testVal2, $testVal3); $PHPDateArray = Helpers::dateParse($testVal1 . '-' . $testVal2 . '-' . $testVal3); if (!Helpers::dateParseSucceeded($PHPDateArray)) { $PHPDateArray = Helpers::dateParse($testVal2 . '-' . $testVal1 . '-' . $testVal3); } } return $PHPDateArray; } /** * Final results. * * @return DateTime|float|int|string Excel date/time serial value, PHP date/time serial value or PHP date/time object, * depending on the value of the ReturnDateType flag */ private static function finalResults(array $PHPDateArray, DateTimeImmutable $dti, int $baseYear) { $retValue = ExcelError::Value(); if (Helpers::dateParseSucceeded($PHPDateArray)) { // Execute function Helpers::replaceIfEmpty($PHPDateArray['year'], $dti->format('Y')); if ($PHPDateArray['year'] < $baseYear) { return ExcelError::VALUE(); } Helpers::replaceIfEmpty($PHPDateArray['month'], $dti->format('m')); Helpers::replaceIfEmpty($PHPDateArray['day'], $dti->format('d')); $PHPDateArray['hour'] = 0; $PHPDateArray['minute'] = 0; $PHPDateArray['second'] = 0; $month = self::getInt($PHPDateArray, 'month'); $day = self::getInt($PHPDateArray, 'day'); $year = self::getInt($PHPDateArray, 'year'); if (!checkdate($month, $day, $year)) { return ($year === 1900 && $month === 2 && $day === 29) ? Helpers::returnIn3FormatsFloat(60.0) : ExcelError::VALUE(); } $retValue = Helpers::returnIn3FormatsArray($PHPDateArray, true); } return $retValue; } private static function getInt(array $array, string $index): int { return (array_key_exists($index, $array) && is_numeric($array[$index])) ? (int) $array[$index] : 0; } } PK!O IVVHlibraries/vendor/PhpSpreadsheet/Calculation/DateTimeExcel/Difference.phpnu[getMessage(); } // Execute function $PHPStartDateObject = SharedDateHelper::excelToDateTimeObject($startDate); $startDays = (int) $PHPStartDateObject->format('j'); //$startMonths = (int) $PHPStartDateObject->format('n'); $startYears = (int) $PHPStartDateObject->format('Y'); $PHPEndDateObject = SharedDateHelper::excelToDateTimeObject($endDate); $endDays = (int) $PHPEndDateObject->format('j'); //$endMonths = (int) $PHPEndDateObject->format('n'); $endYears = (int) $PHPEndDateObject->format('Y'); $PHPDiffDateObject = $PHPEndDateObject->diff($PHPStartDateObject); $retVal = false; $retVal = self::replaceRetValue($retVal, $unit, 'D') ?? self::datedifD($difference); $retVal = self::replaceRetValue($retVal, $unit, 'M') ?? self::datedifM($PHPDiffDateObject); $retVal = self::replaceRetValue($retVal, $unit, 'MD') ?? self::datedifMD($startDays, $endDays, $PHPEndDateObject, $PHPDiffDateObject); $retVal = self::replaceRetValue($retVal, $unit, 'Y') ?? self::datedifY($PHPDiffDateObject); $retVal = self::replaceRetValue($retVal, $unit, 'YD') ?? self::datedifYD($difference, $startYears, $endYears, $PHPStartDateObject, $PHPEndDateObject); $retVal = self::replaceRetValue($retVal, $unit, 'YM') ?? self::datedifYM($PHPDiffDateObject); return is_bool($retVal) ? ExcelError::VALUE() : $retVal; } private static function initialDiff(float $startDate, float $endDate): float { // Validate parameters if ($startDate > $endDate) { throw new Exception(ExcelError::NAN()); } return $endDate - $startDate; } /** * Decide whether it's time to set retVal. * @param bool|int $retVal * @return bool|int|null */ private static function replaceRetValue($retVal, string $unit, string $compare) { if ($retVal !== false || $unit !== $compare) { return $retVal; } return null; } private static function datedifD(float $difference): int { return (int) $difference; } private static function datedifM(DateInterval $PHPDiffDateObject): int { return 12 * (int) $PHPDiffDateObject->format('%y') + (int) $PHPDiffDateObject->format('%m'); } private static function datedifMD(int $startDays, int $endDays, DateTime $PHPEndDateObject, DateInterval $PHPDiffDateObject): int { if ($endDays < $startDays) { $retVal = $endDays; $PHPEndDateObject->modify('-' . $endDays . ' days'); $adjustDays = (int) $PHPEndDateObject->format('j'); $retVal += ($adjustDays - $startDays); } else { $retVal = (int) $PHPDiffDateObject->format('%d'); } return $retVal; } private static function datedifY(DateInterval $PHPDiffDateObject): int { return (int) $PHPDiffDateObject->format('%y'); } private static function datedifYD(float $difference, int $startYears, int $endYears, DateTime $PHPStartDateObject, DateTime $PHPEndDateObject): int { $retVal = (int) $difference; if ($endYears > $startYears) { $isLeapStartYear = $PHPStartDateObject->format('L'); $wasLeapEndYear = $PHPEndDateObject->format('L'); // Adjust end year to be as close as possible as start year while ($PHPEndDateObject >= $PHPStartDateObject) { $PHPEndDateObject->modify('-1 year'); //$endYears = $PHPEndDateObject->format('Y'); } $PHPEndDateObject->modify('+1 year'); // Get the result $retVal = (int) $PHPEndDateObject->diff($PHPStartDateObject)->days; // Adjust for leap years cases $isLeapEndYear = $PHPEndDateObject->format('L'); $limit = new DateTime($PHPEndDateObject->format('Y-02-29')); if (!$isLeapStartYear && !$wasLeapEndYear && $isLeapEndYear && $PHPEndDateObject >= $limit) { --$retVal; } } return (int) $retVal; } private static function datedifYM(DateInterval $PHPDiffDateObject): int { return (int) $PHPDiffDateObject->format('%m'); } } PK!)(Ilibraries/vendor/PhpSpreadsheet/Calculation/DateTimeExcel/NetworkDays.phpnu[getMessage(); } // Execute function $startDow = self::calcStartDow($startDate); $endDow = self::calcEndDow($endDate); $wholeWeekDays = (int) floor(($endDate - $startDate) / 7) * 5; $partWeekDays = self::calcPartWeekDays($startDow, $endDow); // Test any extra holiday parameters $holidayCountedArray = []; foreach ($holidayArray as $holidayDate) { if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) { if ((Week::day($holidayDate, 2) < 6) && (!in_array($holidayDate, $holidayCountedArray))) { --$partWeekDays; $holidayCountedArray[] = $holidayDate; } } } return self::applySign($wholeWeekDays + $partWeekDays, $sDate, $eDate); } private static function calcStartDow(float $startDate): int { $startDow = 6 - (int) Week::day($startDate, 2); if ($startDow < 0) { $startDow = 5; } return $startDow; } private static function calcEndDow(float $endDate): int { $endDow = (int) Week::day($endDate, 2); if ($endDow >= 6) { $endDow = 0; } return $endDow; } private static function calcPartWeekDays(int $startDow, int $endDow): int { $partWeekDays = $endDow + $startDow; if ($partWeekDays > 5) { $partWeekDays -= 5; } return $partWeekDays; } private static function applySign(int $result, float $sDate, float $eDate): int { return ($sDate > $eDate) ? -$result : $result; } } PK![n\Clibraries/vendor/PhpSpreadsheet/Calculation/DateTimeExcel/Month.phpnu[getMessage(); } $dateValue = floor($dateValue); $adjustmentMonths = floor($adjustmentMonths); // Execute function $PHPDateObject = Helpers::adjustDateByMonths($dateValue, $adjustmentMonths); return Helpers::returnIn3FormatsObject($PHPDateObject); } /** * EOMONTH. * * Returns the date value for the last day of the month that is the indicated number of months * before or after start_date. * Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. * * Excel Function: * EOMONTH(dateValue,adjustmentMonths) * * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string * Or can be an array of date values * @param array|int $adjustmentMonths The number of months before or after start_date. * A positive value for months yields a future date; * a negative value yields a past date. * Or can be an array of adjustment values * * @return array|DateTime|float|int|string Excel date/time serial value, PHP date/time serial value or PHP date/time object, * depending on the value of the ReturnDateType flag * If an array of values is passed as the argument, then the returned result will also be an array * with the same dimensions */ public static function lastDay($dateValue, $adjustmentMonths) { if (is_array($dateValue) || is_array($adjustmentMonths)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $dateValue, $adjustmentMonths); } try { $dateValue = Helpers::getDateValue($dateValue, false); $adjustmentMonths = Helpers::validateNumericNull($adjustmentMonths); } catch (Exception $e) { return $e->getMessage(); } $dateValue = floor($dateValue); $adjustmentMonths = floor($adjustmentMonths); // Execute function $PHPDateObject = Helpers::adjustDateByMonths($dateValue, $adjustmentMonths + 1); $adjustDays = (int) $PHPDateObject->format('d'); $adjustDaysString = '-' . $adjustDays . ' days'; $PHPDateObject->modify($adjustDaysString); return Helpers::returnIn3FormatsObject($PHPDateObject); } } PK!Clibraries/vendor/PhpSpreadsheet/Calculation/DateTimeExcel/index.phpnu[ self::DOW_SUNDAY, self::DOW_MONDAY, self::STARTWEEK_MONDAY_ALT => self::DOW_MONDAY, self::DOW_TUESDAY, self::DOW_WEDNESDAY, self::DOW_THURSDAY, self::DOW_FRIDAY, self::DOW_SATURDAY, self::DOW_SUNDAY, self::STARTWEEK_MONDAY_ISO => self::STARTWEEK_MONDAY_ISO, ]; } PK!,H77Glibraries/vendor/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.phpnu[format('c')); return Helpers::dateParseSucceeded($dateArray) ? Helpers::returnIn3FormatsArray($dateArray, true) : ExcelError::VALUE(); } /** * DATETIMENOW. * * Returns the current date and time. * The NOW function is useful when you need to display the current date and time on a worksheet or * calculate a value based on the current date and time, and have that value updated each time you * open the worksheet. * * NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date * and time format of your regional settings. PhpSpreadsheet does not change cell formatting in this way. * * Excel Function: * NOW() * * @return DateTime|float|int|string Excel date/time serial value, PHP date/time serial value or PHP date/time object, * depending on the value of the ReturnDateType flag */ public static function now() { $dti = new DateTimeImmutable(); $dateArray = Helpers::dateParse($dti->format('c')); return Helpers::dateParseSucceeded($dateArray) ? Helpers::returnIn3FormatsArray($dateArray) : ExcelError::VALUE(); } } PK!﹥ Elibraries/vendor/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.phpnu[format('m'); $oYear = (int) $PHPDateObject->format('Y'); $adjustmentMonthsString = (string) $adjustmentMonths; if ($adjustmentMonths > 0) { $adjustmentMonthsString = '+' . $adjustmentMonths; } if ($adjustmentMonths != 0) { $PHPDateObject->modify($adjustmentMonthsString . ' months'); } $nMonth = (int) $PHPDateObject->format('m'); $nYear = (int) $PHPDateObject->format('Y'); $monthDiff = ($nMonth - $oMonth) + (($nYear - $oYear) * 12); if ($monthDiff != $adjustmentMonths) { $adjustDays = (int) $PHPDateObject->format('d'); $adjustDaysString = '-' . $adjustDays . ' days'; $PHPDateObject->modify($adjustDaysString); } return $PHPDateObject; } /** * Help reduce perceived complexity of some tests. * @param mixed $value * @param mixed $altValue */ public static function replaceIfEmpty(&$value, $altValue): void { $value = $value ?: $altValue; } /** * Adjust year in ambiguous situations. */ public static function adjustYear(string $testVal1, string $testVal2, string &$testVal3): void { if (!is_numeric($testVal1) || $testVal1 < 31) { if (!is_numeric($testVal2) || $testVal2 < 12) { if (is_numeric($testVal3) && $testVal3 < 12) { $testVal3 = (string) ($testVal3 + 2000); } } } } /** * Return result in one of three formats. * @return \DateTime|float|int */ public static function returnIn3FormatsArray(array $dateArray, bool $noFrac = false) { $retType = Functions::getReturnDateType(); if ($retType === Functions::RETURNDATE_PHP_DATETIME_OBJECT) { return new DateTime( $dateArray['year'] . '-' . $dateArray['month'] . '-' . $dateArray['day'] . ' ' . $dateArray['hour'] . ':' . $dateArray['minute'] . ':' . $dateArray['second'] ); } $excelDateValue = SharedDateHelper::formattedPHPToExcel( $dateArray['year'], $dateArray['month'], $dateArray['day'], $dateArray['hour'], $dateArray['minute'], $dateArray['second'] ); if ($retType === Functions::RETURNDATE_EXCEL) { return $noFrac ? floor($excelDateValue) : $excelDateValue; } // RETURNDATE_UNIX_TIMESTAMP) return SharedDateHelper::excelToTimestamp($excelDateValue); } /** * Return result in one of three formats. * @return float|int|\DateTime */ public static function returnIn3FormatsFloat(float $excelDateValue) { $retType = Functions::getReturnDateType(); if ($retType === Functions::RETURNDATE_EXCEL) { return $excelDateValue; } if ($retType === Functions::RETURNDATE_UNIX_TIMESTAMP) { return SharedDateHelper::excelToTimestamp($excelDateValue); } // RETURNDATE_PHP_DATETIME_OBJECT return SharedDateHelper::excelToDateTimeObject($excelDateValue); } /** * Return result in one of three formats. * @return \DateTime|float|int */ public static function returnIn3FormatsObject(DateTime $PHPDateObject) { $retType = Functions::getReturnDateType(); if ($retType === Functions::RETURNDATE_PHP_DATETIME_OBJECT) { return $PHPDateObject; } if ($retType === Functions::RETURNDATE_EXCEL) { return (float) SharedDateHelper::PHPToExcel($PHPDateObject); } // RETURNDATE_UNIX_TIMESTAMP $stamp = SharedDateHelper::PHPToExcel($PHPDateObject); $stamp = is_bool($stamp) ? ((int) $stamp) : $stamp; return SharedDateHelper::excelToTimestamp($stamp); } private static function baseDate(): int { if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) { return 0; } if (SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_MAC_1904) { return 0; } return 1; } /** * Many functions accept null/false/true argument treated as 0/0/1. * @param mixed $number */ public static function nullFalseTrueToNumber(&$number, bool $allowBool = true): void { $number = Functions::flattenSingleValue($number); $nullVal = self::baseDate(); if ($number === null) { $number = $nullVal; } elseif ($allowBool && is_bool($number)) { $number = $nullVal + (int) $number; } } /** * Many functions accept null argument treated as 0. * @return float|int * @param mixed $number */ public static function validateNumericNull($number) { $number = Functions::flattenSingleValue($number); if ($number === null) { return 0; } if (is_int($number)) { return $number; } if (is_numeric($number)) { return (float) $number; } throw new Exception(ExcelError::VALUE()); } /** * Many functions accept null/false/true argument treated as 0/0/1. * * @phpstan-assert float $number * @param mixed $number */ public static function validateNotNegative($number): float { if (!is_numeric($number)) { throw new Exception(ExcelError::VALUE()); } if ($number >= 0) { return (float) $number; } throw new Exception(ExcelError::NAN()); } public static function silly1900(DateTime $PHPDateObject, string $mod = '-1 day'): void { $isoDate = $PHPDateObject->format('c'); if ($isoDate < '1900-03-01') { $PHPDateObject->modify($mod); } } public static function dateParse(string $string): array { return self::forceArray(date_parse($string)); } public static function dateParseSucceeded(array $dateArray): bool { return $dateArray['error_count'] === 0; } /** * Despite documentation, date_parse probably never returns false. * Just in case, this routine helps guarantee it. * * @param array|false $dateArray */ private static function forceArray($dateArray): array { return is_array($dateArray) ? $dateArray : ['error_count' => 1]; } /** * @return float|int * @param mixed $value */ public static function floatOrInt($value) { $result = Functions::scalar($value); return is_numeric($result) ? ($result + 0) : 0; } } PK!Blibraries/vendor/PhpSpreadsheet/Calculation/DateTimeExcel/Days.phpnu[getMessage(); } // Execute function $PHPStartDateObject = SharedDateHelper::excelToDateTimeObject($startDate); $PHPEndDateObject = SharedDateHelper::excelToDateTimeObject($endDate); $days = ExcelError::VALUE(); $diff = $PHPStartDateObject->diff($PHPEndDateObject); if (!is_bool($diff->days)) { $days = $diff->days; if ($diff->invert) { $days = -$days; } } return $days; } } PK!55Elibraries/vendor/PhpSpreadsheet/Calculation/DateTimeExcel/Days360.phpnu[getMessage(); } if (!is_bool($method)) { return ExcelError::VALUE(); } // Execute function $PHPStartDateObject = SharedDateHelper::excelToDateTimeObject($startDate); $startDay = $PHPStartDateObject->format('j'); $startMonth = $PHPStartDateObject->format('n'); $startYear = $PHPStartDateObject->format('Y'); $PHPEndDateObject = SharedDateHelper::excelToDateTimeObject($endDate); $endDay = $PHPEndDateObject->format('j'); $endMonth = $PHPEndDateObject->format('n'); $endYear = $PHPEndDateObject->format('Y'); return self::dateDiff360((int) $startDay, (int) $startMonth, (int) $startYear, (int) $endDay, (int) $endMonth, (int) $endYear, !$method); } /** * Return the number of days between two dates based on a 360 day calendar. */ private static function dateDiff360(int $startDay, int $startMonth, int $startYear, int $endDay, int $endMonth, int $endYear, bool $methodUS): int { $startDay = self::getStartDay($startDay, $startMonth, $startYear, $methodUS); $endDay = self::getEndDay($endDay, $endMonth, $endYear, $startDay, $methodUS); return $endDay + $endMonth * 30 + $endYear * 360 - $startDay - $startMonth * 30 - $startYear * 360; } private static function getStartDay(int $startDay, int $startMonth, int $startYear, bool $methodUS): int { if ($startDay == 31) { --$startDay; } elseif ($methodUS && ($startMonth == 2 && ($startDay == 29 || ($startDay == 28 && !Helpers::isLeapYear($startYear))))) { $startDay = 30; } return $startDay; } private static function getEndDay(int $endDay, int &$endMonth, int &$endYear, int $startDay, bool $methodUS): int { if ($endDay == 31) { if ($methodUS && $startDay != 30) { $endDay = 1; if ($endMonth == 12) { ++$endYear; $endMonth = 1; } else { ++$endMonth; } } else { $endDay = 30; } } return $endDay; } } PK!2booBlibraries/vendor/PhpSpreadsheet/Calculation/DateTimeExcel/Time.phpnu[getMessage(); } self::adjustSecond($second, $minute); self::adjustMinute($minute, $hour); if ($hour > 23) { $hour = $hour % 24; } elseif ($hour < 0) { return ExcelError::NAN(); } // Execute function $retType = Functions::getReturnDateType(); if ($retType === Functions::RETURNDATE_EXCEL) { $calendar = SharedDateHelper::getExcelCalendar(); $date = (int) ($calendar !== SharedDateHelper::CALENDAR_WINDOWS_1900); return (float) SharedDateHelper::formattedPHPToExcel($calendar, 1, $date, $hour, $minute, $second); } if ($retType === Functions::RETURNDATE_UNIX_TIMESTAMP) { return (int) SharedDateHelper::excelToTimestamp(SharedDateHelper::formattedPHPToExcel(1970, 1, 1, $hour, $minute, $second)); // -2147468400; // -2147472000 + 3600 } // RETURNDATE_PHP_DATETIME_OBJECT // Hour has already been normalized (0-23) above $phpDateObject = new DateTime('1900-01-01 ' . $hour . ':' . $minute . ':' . $second); return $phpDateObject; } private static function adjustSecond(int &$second, int &$minute): void { if ($second < 0) { $minute += (int) floor($second / 60); $second = 60 - abs($second % 60); if ($second == 60) { $second = 0; } } elseif ($second >= 60) { $minute += intdiv($second, 60); $second = $second % 60; } } private static function adjustMinute(int &$minute, int &$hour): void { if ($minute < 0) { $hour += (int) floor($minute / 60); $minute = 60 - abs($minute % 60); if ($minute == 60) { $minute = 0; } } elseif ($minute >= 60) { $hour += intdiv($minute, 60); $minute = $minute % 60; } } /** * @param mixed $value expect int */ private static function toIntWithNullBool($value): int { $value = $value ?? 0; if (is_bool($value)) { $value = (int) $value; } if (!is_numeric($value)) { throw new Exception(ExcelError::VALUE()); } return (int) $value; } } PK!`qJaaBlibraries/vendor/PhpSpreadsheet/Calculation/DateTimeExcel/Date.phpnu[getMessage(); } // Execute function $excelDateValue = SharedDateHelper::formattedPHPToExcel($year, $month, $day); return Helpers::returnIn3FormatsFloat($excelDateValue); } /** * Convert year from multiple formats to int. * @param mixed $year */ private static function getYear($year, int $baseYear): int { if ($year === null) { $year = 0; } elseif (is_scalar($year)) { $year = StringHelper::testStringAsNumeric((string) $year); } if (!is_numeric($year)) { throw new Exception(ExcelError::VALUE()); } $year = (int) $year; if ($year < ($baseYear - 1900)) { throw new Exception(ExcelError::NAN()); } if ((($baseYear - 1900) !== 0) && ($year < $baseYear) && ($year >= 1900)) { throw new Exception(ExcelError::NAN()); } if (($year < $baseYear) && ($year >= ($baseYear - 1900))) { $year += 1900; } return (int) $year; } /** * Convert month from multiple formats to int. * @param mixed $month */ private static function getMonth($month): int { if (is_string($month)) { if (!is_numeric($month)) { $month = SharedDateHelper::monthStringToNumber($month); } } elseif ($month === null) { $month = 0; } elseif (is_bool($month)) { $month = (int) $month; } if (!is_numeric($month)) { throw new Exception(ExcelError::VALUE()); } return (int) $month; } /** * Convert day from multiple formats to int. * @param mixed $day */ private static function getDay($day): int { if (is_string($day) && !is_numeric($day)) { $day = SharedDateHelper::dayStringToNumber($day); } if ($day === null) { $day = 0; } elseif (is_scalar($day)) { $day = StringHelper::testStringAsNumeric((string) $day); } if (!is_numeric($day)) { throw new Exception(ExcelError::VALUE()); } return (int) $day; } private static function adjustYearMonth(int &$year, int &$month, int $baseYear): void { if ($month < 1) { // Handle year/month adjustment if month < 1 --$month; $year += (int) (ceil($month / 12) - 1); $month = 13 - abs($month % 12); } elseif ($month > 12) { // Handle year/month adjustment if month > 12 $year += intdiv($month, 12); $month = ($month % 12); } // Re-validate the year parameter after adjustments if (($year < $baseYear) || ($year >= 10000)) { throw new Exception(ExcelError::NAN()); } } } PK!!^^Glibraries/vendor/PhpSpreadsheet/Calculation/DateTimeExcel/TimeParts.phpnu[getMessage(); } // Execute function $timeValue = fmod($timeValue, 1); $timeValue = SharedDateHelper::excelToDateTimeObject($timeValue); SharedDateHelper::roundMicroseconds($timeValue); return (int) $timeValue->format('H'); } /** * MINUTE. * * Returns the minutes of a time value. * The minute is given as an integer, ranging from 0 to 59. * * Excel Function: * MINUTE(timeValue) * * @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard time string * Or can be an array of date/time values * * @return array|int|string Minute * If an array of numbers is passed as the argument, then the returned result will also be an array * with the same dimensions */ public static function minute($timeValue) { if (is_array($timeValue)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $timeValue); } try { Helpers::nullFalseTrueToNumber($timeValue); if (is_string($timeValue) && !is_numeric($timeValue)) { $timeValue = Helpers::getTimeValue($timeValue); } Helpers::validateNotNegative($timeValue); } catch (Exception $e) { return $e->getMessage(); } // Execute function $timeValue = fmod($timeValue, 1); $timeValue = SharedDateHelper::excelToDateTimeObject($timeValue); SharedDateHelper::roundMicroseconds($timeValue); return (int) $timeValue->format('i'); } /** * SECOND. * * Returns the seconds of a time value. * The minute is given as an integer, ranging from 0 to 59. * * Excel Function: * SECOND(timeValue) * * @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard time string * Or can be an array of date/time values * * @return array|int|string Second * If an array of numbers is passed as the argument, then the returned result will also be an array * with the same dimensions */ public static function second($timeValue) { if (is_array($timeValue)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $timeValue); } try { Helpers::nullFalseTrueToNumber($timeValue); if (is_string($timeValue) && !is_numeric($timeValue)) { $timeValue = Helpers::getTimeValue($timeValue); } Helpers::validateNotNegative($timeValue); } catch (Exception $e) { return $e->getMessage(); } // Execute function $timeValue = fmod($timeValue, 1); $timeValue = SharedDateHelper::excelToDateTimeObject($timeValue); SharedDateHelper::roundMicroseconds($timeValue); return (int) $timeValue->format('s'); } } PK! Kgk88Elibraries/vendor/PhpSpreadsheet/Calculation/DateTimeExcel/WorkDay.phpnu[getMessage(); } $startDate = (float) floor($startDate); $endDays = (int) floor($endDays); // If endDays is 0, we always return startDate if ($endDays == 0) { return $startDate; } if ($endDays < 0) { return self::decrementing($startDate, $endDays, $holidayArray); } return self::incrementing($startDate, $endDays, $holidayArray); } /** * Use incrementing logic to determine Workday. * @return float|int|\DateTime */ private static function incrementing(float $startDate, int $endDays, array $holidayArray) { // Adjust the start date if it falls over a weekend $startDoW = self::getWeekDay($startDate, 3); if ($startDoW >= 5) { $startDate += 7 - $startDoW; --$endDays; } // Add endDays $endDate = (float) $startDate + ((int) ($endDays / 5) * 7); $endDays = $endDays % 5; while ($endDays > 0) { ++$endDate; // Adjust the calculated end date if it falls over a weekend $endDow = self::getWeekDay($endDate, 3); if ($endDow >= 5) { $endDate += 7 - $endDow; } --$endDays; } // Test any extra holiday parameters if (!empty($holidayArray)) { $endDate = self::incrementingArray($startDate, $endDate, $holidayArray); } return Helpers::returnIn3FormatsFloat($endDate); } private static function incrementingArray(float $startDate, float $endDate, array $holidayArray): float { $holidayCountedArray = $holidayDates = []; foreach ($holidayArray as $holidayDate) { if (self::getWeekDay($holidayDate, 3) < 5) { $holidayDates[] = $holidayDate; } } sort($holidayDates, SORT_NUMERIC); foreach ($holidayDates as $holidayDate) { if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) { if (!in_array($holidayDate, $holidayCountedArray)) { ++$endDate; $holidayCountedArray[] = $holidayDate; } } // Adjust the calculated end date if it falls over a weekend $endDoW = self::getWeekDay($endDate, 3); if ($endDoW >= 5) { $endDate += 7 - $endDoW; } } return $endDate; } /** * Use decrementing logic to determine Workday. * @return float|int|\DateTime */ private static function decrementing(float $startDate, int $endDays, array $holidayArray) { // Adjust the start date if it falls over a weekend $startDoW = self::getWeekDay($startDate, 3); if ($startDoW >= 5) { $startDate += -$startDoW + 4; ++$endDays; } // Add endDays $endDate = (float) $startDate + ((int) ($endDays / 5) * 7); $endDays = $endDays % 5; while ($endDays < 0) { --$endDate; // Adjust the calculated end date if it falls over a weekend $endDow = self::getWeekDay($endDate, 3); if ($endDow >= 5) { $endDate += 4 - $endDow; } ++$endDays; } // Test any extra holiday parameters if (!empty($holidayArray)) { $endDate = self::decrementingArray($startDate, $endDate, $holidayArray); } return Helpers::returnIn3FormatsFloat($endDate); } private static function decrementingArray(float $startDate, float $endDate, array $holidayArray): float { $holidayCountedArray = $holidayDates = []; foreach ($holidayArray as $holidayDate) { if (self::getWeekDay($holidayDate, 3) < 5) { $holidayDates[] = $holidayDate; } } rsort($holidayDates, SORT_NUMERIC); foreach ($holidayDates as $holidayDate) { if (($holidayDate <= $startDate) && ($holidayDate >= $endDate)) { if (!in_array($holidayDate, $holidayCountedArray)) { --$endDate; $holidayCountedArray[] = $holidayDate; } } // Adjust the calculated end date if it falls over a weekend $endDoW = self::getWeekDay($endDate, 3); /** int $endDoW */ if ($endDoW >= 5) { $endDate += -$endDoW + 4; } } return $endDate; } private static function getWeekDay(float $date, int $wd): int { $result = Functions::scalar(Week::day($date, $wd)); return is_int($result) ? $result : -1; } } PK!0Glibraries/vendor/PhpSpreadsheet/Calculation/DateTimeExcel/DateParts.phpnu[= 0) { return $weirdResult; } try { $dateValue = Helpers::getDateValue($dateValue); } catch (Exception $e) { return $e->getMessage(); } // Execute function $PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue); SharedDateHelper::roundMicroseconds($PHPDateObject); return (int) $PHPDateObject->format('j'); } /** * MONTHOFYEAR. * * Returns the month of a date represented by a serial number. * The month is given as an integer, ranging from 1 (January) to 12 (December). * * Excel Function: * MONTH(dateValue) * * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string * Or can be an array of date values * * @return array|int|string Month of the year * If an array of numbers is passed as the argument, then the returned result will also be an array * with the same dimensions */ public static function month($dateValue) { if (is_array($dateValue)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $dateValue); } try { $dateValue = Helpers::getDateValue($dateValue); } catch (Exception $e) { return $e->getMessage(); } if ($dateValue < 1 && SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_WINDOWS_1900) { return 1; } // Execute function $PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue); SharedDateHelper::roundMicroseconds($PHPDateObject); return (int) $PHPDateObject->format('n'); } /** * YEAR. * * Returns the year corresponding to a date. * The year is returned as an integer in the range 1900-9999. * * Excel Function: * YEAR(dateValue) * * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string * Or can be an array of date values * * @return array|int|string Year * If an array of numbers is passed as the argument, then the returned result will also be an array * with the same dimensions */ public static function year($dateValue) { if (is_array($dateValue)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $dateValue); } try { $dateValue = Helpers::getDateValue($dateValue); } catch (Exception $e) { return $e->getMessage(); } if ($dateValue < 1 && SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_WINDOWS_1900) { return 1900; } // Execute function $PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue); SharedDateHelper::roundMicroseconds($PHPDateObject); return (int) $PHPDateObject->format('Y'); } /** * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string */ private static function weirdCondition($dateValue): int { // Excel does not treat 0 consistently for DAY vs. (MONTH or YEAR) if (SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_WINDOWS_1900 && Functions::getCompatibilityMode() == Functions::COMPATIBILITY_EXCEL) { if (is_bool($dateValue)) { return (int) $dateValue; } if ($dateValue === null) { return 0; } if (is_numeric($dateValue) && $dateValue < 1 && $dateValue >= 0) { return 0; } } return -1; } } PK!_T{Flibraries/vendor/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.phpnu[getMessage(); } switch ($method) { case 0: return Helpers::floatOrInt(Days360::between($startDate, $endDate)) / 360; case 1: return self::method1($startDate, $endDate); case 2: return Helpers::floatOrInt(Difference::interval($startDate, $endDate)) / 360; case 3: return Helpers::floatOrInt(Difference::interval($startDate, $endDate)) / 365; case 4: return Helpers::floatOrInt(Days360::between($startDate, $endDate, true)) / 360; default: return ExcelError::NAN(); } } /** * Excel 1900 calendar treats date argument of null as 1900-01-00. Really. * @param mixed $startDate * @param mixed $endDate */ private static function excelBug(float $sDate, $startDate, $endDate, int $method): float { if (Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_OPENOFFICE && SharedDateHelper::getExcelCalendar() !== SharedDateHelper::CALENDAR_MAC_1904) { if ($endDate === null && $startDate !== null) { if (DateParts::month($sDate) == 12 && DateParts::day($sDate) === 31 && $method === 0) { $sDate += 2; } else { ++$sDate; } } } return $sDate; } private static function method1(float $startDate, float $endDate): float { $days = Helpers::floatOrInt(Difference::interval($startDate, $endDate)); $startYear = (int) DateParts::year($startDate); $endYear = (int) DateParts::year($endDate); $years = $endYear - $startYear + 1; $startMonth = (int) DateParts::month($startDate); $startDay = (int) DateParts::day($startDate); $endMonth = (int) DateParts::month($endDate); $endDay = (int) DateParts::day($endDate); $startMonthDay = 100 * $startMonth + $startDay; $endMonthDay = 100 * $endMonth + $endDay; if ($years == 1) { $tmpCalcAnnualBasis = 365 + (int) Helpers::isLeapYear($endYear); } elseif ($years == 2 && $startMonthDay >= $endMonthDay) { if (Helpers::isLeapYear($startYear)) { $tmpCalcAnnualBasis = 365 + (int) ($startMonthDay <= 229); } elseif (Helpers::isLeapYear($endYear)) { $tmpCalcAnnualBasis = 365 + (int) ($endMonthDay >= 229); } else { $tmpCalcAnnualBasis = 365; } } else { $tmpCalcAnnualBasis = 0; for ($year = $startYear; $year <= $endYear; ++$year) { $tmpCalcAnnualBasis += 365 + (int) Helpers::isLeapYear($year); } $tmpCalcAnnualBasis /= $years; } return $days / $tmpCalcAnnualBasis; } } PK!In}--Clibraries/vendor/PhpSpreadsheet/Calculation/Engineering/Complex.phpnu[getMessage(); } if (($suffix === 'i') || ($suffix === 'j') || ($suffix === '')) { $complex = new ComplexObject($realNumber, $imaginary, $suffix); return (string) $complex; } return ExcelError::VALUE(); } /** * IMAGINARY. * * Returns the imaginary coefficient of a complex number in x + yi or x + yj text format. * * Excel Function: * IMAGINARY(complexNumber) * * @param array|string $complexNumber the complex number for which you want the imaginary * coefficient * Or can be an array of values * * @return array|float|string (string if an error) * If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function IMAGINARY($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $exception) { return ExcelError::NAN(); } return $complex->getImaginary(); } /** * IMREAL. * * Returns the real coefficient of a complex number in x + yi or x + yj text format. * * Excel Function: * IMREAL(complexNumber) * * @param array|string $complexNumber the complex number for which you want the real coefficient * Or can be an array of values * * @return array|float|string (string if an error) * If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function IMREAL($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $exception) { return ExcelError::NAN(); } return $complex->getReal(); } } PK!>=vzz@libraries/vendor/PhpSpreadsheet/Calculation/Engineering/ErfC.phpnu[ Functions::PRECISION); return self::ONE_SQRT_PI * exp(-$value * $value) * $q2; } } PK!K*IIHlibraries/vendor/PhpSpreadsheet/Calculation/Engineering/ConvertOctal.phpnu[getMessage(); } return ConvertDecimal::toBinary(self::toDecimal($value), $places); } /** * toDecimal. * * Return an octal value as decimal. * * Excel Function: * OCT2DEC(x) * * @param array|bool|float|int|string $value The octal number you want to convert. Number may not contain * more than 10 octal characters (30 bits). The most significant * bit of number is the sign bit. The remaining 29 bits are * magnitude bits. Negative numbers are represented using * two's-complement notation. * If number is not a valid octal number, OCT2DEC returns the * #NUM! error value. * Or can be an array of values * * @return array|float|int|string Result, or an error * If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function toDecimal($value) { if (is_array($value)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value); } try { $value = self::validateValue($value); $value = self::validateOctal($value); } catch (Exception $e) { return $e->getMessage(); } $binX = ''; foreach (mb_str_split($value, 1, 'UTF-8') as $char) { $binX .= str_pad(decbin((int) $char), 3, '0', STR_PAD_LEFT); } if (strlen($binX) == 30 && $binX[0] == '1') { for ($i = 0; $i < 30; ++$i) { $binX[$i] = ($binX[$i] == '1' ? '0' : '1'); } return (bindec($binX) + 1) * -1; } return bindec($binX); } /** * toHex. * * Return an octal value as hex. * * Excel Function: * OCT2HEX(x[,places]) * * @param array|bool|float|int|string $value The octal number you want to convert. Number may not contain * more than 10 octal characters (30 bits). The most significant * bit of number is the sign bit. The remaining 29 bits are * magnitude bits. Negative numbers are represented using * two's-complement notation. * If number is negative, OCT2HEX ignores places and returns a * 10-character hexadecimal number. * If number is not a valid octal number, OCT2HEX returns the * #NUM! error value. * If OCT2HEX requires more than places characters, it returns * the #NUM! error value. * Or can be an array of values * @param array|int $places The number of characters to use. If places is omitted, OCT2HEX * uses the minimum number of characters necessary. Places is useful * for padding the return value with leading 0s (zeros). * If places is not an integer, it is truncated. * If places is nonnumeric, OCT2HEX returns the #VALUE! error value. * If places is negative, OCT2HEX returns the #NUM! error value. * Or can be an array of values * * @return array|string Result, or an error * If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function toHex($value, $places = null) { if (is_array($value) || is_array($places)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places); } try { $value = self::validateValue($value); $value = self::validateOctal($value); $places = self::validatePlaces($places); } catch (Exception $e) { return $e->getMessage(); } $hexVal = strtoupper(dechex((int) self::toDecimal($value))); $hexVal = (PHP_INT_SIZE === 4 && strlen($value) === 10 && $value[0] >= '4') ? "FF{$hexVal}" : $hexVal; return self::nbrConversionFormat($hexVal, $places); } protected static function validateOctal(string $value): string { $numDigits = (int) preg_match_all('/[01234567]/', $value); if (strlen($value) > $numDigits || $numDigits > 10) { throw new Exception(ExcelError::NAN()); } return $value; } } PK!*+BBLlibraries/vendor/PhpSpreadsheet/Calculation/Engineering/ComplexFunctions.phpnu[abs(); } /** * IMARGUMENT. * * Returns the argument theta of a complex number, i.e. the angle in radians from the real * axis to the representation of the number in polar coordinates. * * Excel Function: * IMARGUMENT(complexNumber) * * @param array|string $complexNumber the complex number for which you want the argument theta * Or can be an array of values * * @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function IMARGUMENT($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $exception) { return ExcelError::NAN(); } if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { return ExcelError::DIV0(); } return $complex->argument(); } /** * IMCONJUGATE. * * Returns the complex conjugate of a complex number in x + yi or x + yj text format. * * Excel Function: * IMCONJUGATE(complexNumber) * * @param array|string $complexNumber the complex number for which you want the conjugate * Or can be an array of values * * @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function IMCONJUGATE($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $exception) { return ExcelError::NAN(); } return (string) $complex->conjugate(); } /** * IMCOS. * * Returns the cosine of a complex number in x + yi or x + yj text format. * * Excel Function: * IMCOS(complexNumber) * * @param array|string $complexNumber the complex number for which you want the cosine * Or can be an array of values * * @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function IMCOS($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $exception) { return ExcelError::NAN(); } return (string) $complex->cos(); } /** * IMCOSH. * * Returns the hyperbolic cosine of a complex number in x + yi or x + yj text format. * * Excel Function: * IMCOSH(complexNumber) * * @param array|string $complexNumber the complex number for which you want the hyperbolic cosine * Or can be an array of values * * @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function IMCOSH($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $exception) { return ExcelError::NAN(); } return (string) $complex->cosh(); } /** * IMCOT. * * Returns the cotangent of a complex number in x + yi or x + yj text format. * * Excel Function: * IMCOT(complexNumber) * * @param array|string $complexNumber the complex number for which you want the cotangent * Or can be an array of values * * @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function IMCOT($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $exception) { return ExcelError::NAN(); } return (string) $complex->cot(); } /** * IMCSC. * * Returns the cosecant of a complex number in x + yi or x + yj text format. * * Excel Function: * IMCSC(complexNumber) * * @param array|string $complexNumber the complex number for which you want the cosecant * Or can be an array of values * * @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function IMCSC($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $exception) { return ExcelError::NAN(); } return (string) $complex->csc(); } /** * IMCSCH. * * Returns the hyperbolic cosecant of a complex number in x + yi or x + yj text format. * * Excel Function: * IMCSCH(complexNumber) * * @param array|string $complexNumber the complex number for which you want the hyperbolic cosecant * Or can be an array of values * * @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function IMCSCH($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $exception) { return ExcelError::NAN(); } return (string) $complex->csch(); } /** * IMSIN. * * Returns the sine of a complex number in x + yi or x + yj text format. * * Excel Function: * IMSIN(complexNumber) * * @param array|string $complexNumber the complex number for which you want the sine * Or can be an array of values * * @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function IMSIN($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $exception) { return ExcelError::NAN(); } return (string) $complex->sin(); } /** * IMSINH. * * Returns the hyperbolic sine of a complex number in x + yi or x + yj text format. * * Excel Function: * IMSINH(complexNumber) * * @param array|string $complexNumber the complex number for which you want the hyperbolic sine * Or can be an array of values * * @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function IMSINH($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $exception) { return ExcelError::NAN(); } return (string) $complex->sinh(); } /** * IMSEC. * * Returns the secant of a complex number in x + yi or x + yj text format. * * Excel Function: * IMSEC(complexNumber) * * @param array|string $complexNumber the complex number for which you want the secant * Or can be an array of values * * @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function IMSEC($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $exception) { return ExcelError::NAN(); } return (string) $complex->sec(); } /** * IMSECH. * * Returns the hyperbolic secant of a complex number in x + yi or x + yj text format. * * Excel Function: * IMSECH(complexNumber) * * @param array|string $complexNumber the complex number for which you want the hyperbolic secant * Or can be an array of values * * @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function IMSECH($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $exception) { return ExcelError::NAN(); } return (string) $complex->sech(); } /** * IMTAN. * * Returns the tangent of a complex number in x + yi or x + yj text format. * * Excel Function: * IMTAN(complexNumber) * * @param array|string $complexNumber the complex number for which you want the tangent * Or can be an array of values * * @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function IMTAN($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $exception) { return ExcelError::NAN(); } return (string) $complex->tan(); } /** * IMSQRT. * * Returns the square root of a complex number in x + yi or x + yj text format. * * Excel Function: * IMSQRT(complexNumber) * * @param array|string $complexNumber the complex number for which you want the square root * Or can be an array of values * * @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function IMSQRT($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $exception) { return ExcelError::NAN(); } $theta = self::IMARGUMENT($complexNumber); if ($theta === ExcelError::DIV0()) { return '0'; } return (string) $complex->sqrt(); } /** * IMLN. * * Returns the natural logarithm of a complex number in x + yi or x + yj text format. * * Excel Function: * IMLN(complexNumber) * * @param array|string $complexNumber the complex number for which you want the natural logarithm * Or can be an array of values * * @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function IMLN($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $exception) { return ExcelError::NAN(); } if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { return ExcelError::NAN(); } return (string) $complex->ln(); } /** * IMLOG10. * * Returns the common logarithm (base 10) of a complex number in x + yi or x + yj text format. * * Excel Function: * IMLOG10(complexNumber) * * @param array|string $complexNumber the complex number for which you want the common logarithm * Or can be an array of values * * @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function IMLOG10($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $exception) { return ExcelError::NAN(); } if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { return ExcelError::NAN(); } return (string) $complex->log10(); } /** * IMLOG2. * * Returns the base-2 logarithm of a complex number in x + yi or x + yj text format. * * Excel Function: * IMLOG2(complexNumber) * * @param array|string $complexNumber the complex number for which you want the base-2 logarithm * Or can be an array of values * * @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function IMLOG2($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $exception) { return ExcelError::NAN(); } if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { return ExcelError::NAN(); } return (string) $complex->log2(); } /** * IMEXP. * * Returns the exponential of a complex number in x + yi or x + yj text format. * * Excel Function: * IMEXP(complexNumber) * * @param array|string $complexNumber the complex number for which you want the exponential * Or can be an array of values * * @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function IMEXP($complexNumber) { if (is_array($complexNumber)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $exception) { return ExcelError::NAN(); } return (string) $complex->exp(); } /** * IMPOWER. * * Returns a complex number in x + yi or x + yj text format raised to a power. * * Excel Function: * IMPOWER(complexNumber,realNumber) * * @param array|string $complexNumber the complex number you want to raise to a power * Or can be an array of values * @param array|float|int|string $realNumber the power to which you want to raise the complex number * Or can be an array of values * * @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function IMPOWER($complexNumber, $realNumber) { if (is_array($complexNumber) || is_array($realNumber)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $complexNumber, $realNumber); } try { $complex = new ComplexObject($complexNumber); } catch (ComplexException $exception) { return ExcelError::NAN(); } if (!is_numeric($realNumber)) { return ExcelError::VALUE(); } return (string) $complex->pow((float) $realNumber); } } PK!$ ?libraries/vendor/PhpSpreadsheet/Calculation/Engineering/Erf.phpnu[ 2.2) { return 1 - self::makeFloat(ErfC::ERFC($value)); } $sum = $term = $value; $xsqr = ($value * $value); $j = 1; do { $term *= $xsqr / $j; $sum -= $term / (2 * $j + 1); ++$j; $term *= $xsqr / $j; $sum += $term / (2 * $j + 1); ++$j; if ($sum == 0.0) { break; } } while (abs($term / $sum) > Functions::PRECISION); return self::TWO_SQRT_PI * $sum; } } PK!a9Clibraries/vendor/PhpSpreadsheet/Calculation/Engineering/BesselY.phpnu[getMessage(); } if (($ord < 0) || ($x <= 0.0)) { return ExcelError::NAN(); } $fBy = self::calculate($x, $ord); return (is_nan($fBy)) ? ExcelError::NAN() : $fBy; } private static function calculate(float $x, int $ord): float { switch ($ord) { case 0: return self::besselY0($x); case 1: return self::besselY1($x); default: return self::besselY2($x, $ord); } } /** * Mollify Phpstan. * * @codeCoverageIgnore */ private static function callBesselJ(float $x, int $ord): float { $rslt = BesselJ::BESSELJ($x, $ord); if (!is_float($rslt)) { throw new Exception('Unexpected array or string'); } return $rslt; } private static function besselY0(float $x): float { if ($x < 8.0) { $y = ($x * $x); $ans1 = -2957821389.0 + $y * (7062834065.0 + $y * (-512359803.6 + $y * (10879881.29 + $y * (-86327.92757 + $y * 228.4622733)))); $ans2 = 40076544269.0 + $y * (745249964.8 + $y * (7189466.438 + $y * (47447.26470 + $y * (226.1030244 + $y)))); return $ans1 / $ans2 + 0.636619772 * self::callBesselJ($x, 0) * log($x); } $z = 8.0 / $x; $y = ($z * $z); $xx = $x - 0.785398164; $ans1 = 1 + $y * (-0.1098628627e-2 + $y * (0.2734510407e-4 + $y * (-0.2073370639e-5 + $y * 0.2093887211e-6))); $ans2 = -0.1562499995e-1 + $y * (0.1430488765e-3 + $y * (-0.6911147651e-5 + $y * (0.7621095161e-6 + $y * (-0.934945152e-7)))); return sqrt(0.636619772 / $x) * (sin($xx) * $ans1 + $z * cos($xx) * $ans2); } private static function besselY1(float $x): float { if ($x < 8.0) { $y = ($x * $x); $ans1 = $x * (-0.4900604943e13 + $y * (0.1275274390e13 + $y * (-0.5153438139e11 + $y * (0.7349264551e9 + $y * (-0.4237922726e7 + $y * 0.8511937935e4))))); $ans2 = 0.2499580570e14 + $y * (0.4244419664e12 + $y * (0.3733650367e10 + $y * (0.2245904002e8 + $y * (0.1020426050e6 + $y * (0.3549632885e3 + $y))))); return ($ans1 / $ans2) + 0.636619772 * (self::callBesselJ($x, 1) * log($x) - 1 / $x); } $z = 8.0 / $x; $y = $z * $z; $xx = $x - 2.356194491; $ans1 = 1.0 + $y * (0.183105e-2 + $y * (-0.3516396496e-4 + $y * (0.2457520174e-5 + $y * (-0.240337019e-6)))); $ans2 = 0.04687499995 + $y * (-0.2002690873e-3 + $y * (0.8449199096e-5 + $y * (-0.88228987e-6 + $y * 0.105787412e-6))); return sqrt(0.636619772 / $x) * (sin($xx) * $ans1 + $z * cos($xx) * $ans2); } private static function besselY2(float $x, int $ord): float { $fTox = 2.0 / $x; $fBym = self::besselY0($x); $fBy = self::besselY1($x); for ($n = 1; $n < $ord; ++$n) { $fByp = $n * $fTox * $fBy - $fBym; $fBym = $fBy; $fBy = $fByp; } return $fBy; } } PK!2Flibraries/vendor/PhpSpreadsheet/Calculation/Engineering/ConvertHex.phpnu[getMessage(); } $dec = self::toDecimal($value); return ConvertDecimal::toBinary($dec, $places); } /** * toDecimal. * * Return a hex value as decimal. * * Excel Function: * HEX2DEC(x) * * @param array|bool|float|int|string $value The hexadecimal number you want to convert. This number cannot * contain more than 10 characters (40 bits). The most significant * bit of number is the sign bit. The remaining 39 bits are magnitude * bits. Negative numbers are represented using two's-complement * notation. * If number is not a valid hexadecimal number, HEX2DEC returns the * #NUM! error value. * Or can be an array of values * * @return array|float|int|string Result, or an error * If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function toDecimal($value) { if (is_array($value)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value); } try { $value = self::validateValue($value); $value = self::validateHex($value); } catch (Exception $e) { return $e->getMessage(); } if (strlen($value) > 10) { return ExcelError::NAN(); } $binX = ''; foreach (mb_str_split($value, 1, 'UTF-8') as $char) { $binX .= str_pad(base_convert($char, 16, 2), 4, '0', STR_PAD_LEFT); } if (strlen($binX) == 40 && $binX[0] == '1') { for ($i = 0; $i < 40; ++$i) { $binX[$i] = ($binX[$i] == '1' ? '0' : '1'); } return (bindec($binX) + 1) * -1; } return bindec($binX); } /** * toOctal. * * Return a hex value as octal. * * Excel Function: * HEX2OCT(x[,places]) * * @param array|bool|float|int|string $value The hexadecimal number you want to convert. Number cannot * contain more than 10 characters. The most significant bit of * number is the sign bit. The remaining 39 bits are magnitude * bits. Negative numbers are represented using two's-complement * notation. * If number is negative, HEX2OCT ignores places and returns a * 10-character octal number. * If number is negative, it cannot be less than FFE0000000, and * if number is positive, it cannot be greater than 1FFFFFFF. * If number is not a valid hexadecimal number, HEX2OCT returns * the #NUM! error value. * If HEX2OCT requires more than places characters, it returns * the #NUM! error value. * Or can be an array of values * @param array|int $places The number of characters to use. If places is omitted, HEX2OCT * uses the minimum number of characters necessary. Places is * useful for padding the return value with leading 0s (zeros). * If places is not an integer, it is truncated. * If places is nonnumeric, HEX2OCT returns the #VALUE! error * value. * If places is negative, HEX2OCT returns the #NUM! error value. * Or can be an array of values * * @return array|string Result, or an error * If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function toOctal($value, $places = null) { if (is_array($value) || is_array($places)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places); } try { $value = self::validateValue($value); $value = self::validateHex($value); $places = self::validatePlaces($places); } catch (Exception $e) { return $e->getMessage(); } $decimal = self::toDecimal($value); return ConvertDecimal::toOctal($decimal, $places); } protected static function validateHex(string $value): string { if (strlen($value) > preg_match_all('/[0123456789ABCDEF]/', $value)) { throw new Exception(ExcelError::NAN()); } return $value; } } PK!88Glibraries/vendor/PhpSpreadsheet/Calculation/Engineering/ConvertBase.phpnu[ 10) { throw new Exception(ExcelError::NAN()); } return (int) $places; } throw new Exception(ExcelError::VALUE()); } /** * Formats a number base string value with leading zeroes. * * @param string $value The "number" to pad * @param ?int $places The length that we want to pad this value * * @return string The padded "number" */ protected static function nbrConversionFormat(string $value, ?int $places): string { if ($places !== null) { if (strlen($value) <= $places) { return substr(str_pad($value, $places, '0', STR_PAD_LEFT), -10); } return ExcelError::NAN(); } return substr($value, -10); } } PK!LRlibraries/vendor/PhpSpreadsheet/Calculation/Engineering/EngineeringValidations.phpnu[ 511, DEC2BIN returns the #NUM! error * value. * If number is nonnumeric, DEC2BIN returns the #VALUE! error value. * If DEC2BIN requires more than places characters, it returns the #NUM! * error value. * Or can be an array of values * @param null|array|float|int|string $places The number of characters to use. If places is omitted, DEC2BIN uses * the minimum number of characters necessary. Places is useful for * padding the return value with leading 0s (zeros). * If places is not an integer, it is truncated. * If places is nonnumeric, DEC2BIN returns the #VALUE! error value. * If places is zero or negative, DEC2BIN returns the #NUM! error value. * Or can be an array of values * * @return array|string Result, or an error * If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function toBinary($value, $places = null) { if (is_array($value) || is_array($places)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places); } try { $value = self::validateValue($value); $value = self::validateDecimal($value); $places = self::validatePlaces($places); } catch (Exception $e) { return $e->getMessage(); } $value = (int) floor((float) $value); if ($value > self::LARGEST_BINARY_IN_DECIMAL || $value < self::SMALLEST_BINARY_IN_DECIMAL) { return ExcelError::NAN(); } $r = decbin($value); // Two's Complement $r = substr($r, -10); return self::nbrConversionFormat($r, $places); } /** * toHex. * * Return a decimal value as hex. * * Excel Function: * DEC2HEX(x[,places]) * * @param array|bool|float|int|string $value The decimal integer you want to convert. If number is negative, * places is ignored and DEC2HEX returns a 10-character (40-bit) * hexadecimal number in which the most significant bit is the sign * bit. The remaining 39 bits are magnitude bits. Negative numbers * are represented using two's-complement notation. * If number < -549,755,813,888 or if number > 549,755,813,887, * DEC2HEX returns the #NUM! error value. * If number is nonnumeric, DEC2HEX returns the #VALUE! error value. * If DEC2HEX requires more than places characters, it returns the * #NUM! error value. * Or can be an array of values * @param null|array|float|int|string $places The number of characters to use. If places is omitted, DEC2HEX uses * the minimum number of characters necessary. Places is useful for * padding the return value with leading 0s (zeros). * If places is not an integer, it is truncated. * If places is nonnumeric, DEC2HEX returns the #VALUE! error value. * If places is zero or negative, DEC2HEX returns the #NUM! error value. * Or can be an array of values * * @return array|string Result, or an error * If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function toHex($value, $places = null) { if (is_array($value) || is_array($places)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places); } try { $value = self::validateValue($value); $value = self::validateDecimal($value); $places = self::validatePlaces($places); } catch (Exception $e) { return $e->getMessage(); } $value = floor((float) $value); if ($value > self::LARGEST_HEX_IN_DECIMAL || $value < self::SMALLEST_HEX_IN_DECIMAL) { return ExcelError::NAN(); } $r = strtoupper(dechex((int) $value)); $r = self::hex32bit($value, $r); return self::nbrConversionFormat($r, $places); } public static function hex32bit(float $value, string $hexstr, bool $force = false): string { if (PHP_INT_SIZE === 4 || $force) { if ($value >= 2 ** 32) { $quotient = (int) ($value / (2 ** 32)); return strtoupper(substr('0' . dechex($quotient), -2) . $hexstr); } if ($value < -(2 ** 32)) { $quotient = 256 - (int) ceil((-$value) / (2 ** 32)); return strtoupper(substr('0' . dechex($quotient), -2) . substr("00000000$hexstr", -8)); } if ($value < 0) { return "FF$hexstr"; } } return $hexstr; } /** * toOctal. * * Return an decimal value as octal. * * Excel Function: * DEC2OCT(x[,places]) * * @param array|bool|float|int|string $value The decimal integer you want to convert. If number is negative, * places is ignored and DEC2OCT returns a 10-character (30-bit) * octal number in which the most significant bit is the sign bit. * The remaining 29 bits are magnitude bits. Negative numbers are * represented using two's-complement notation. * If number < -536,870,912 or if number > 536,870,911, DEC2OCT * returns the #NUM! error value. * If number is nonnumeric, DEC2OCT returns the #VALUE! error value. * If DEC2OCT requires more than places characters, it returns the * #NUM! error value. * Or can be an array of values * @param array|int $places The number of characters to use. If places is omitted, DEC2OCT uses * the minimum number of characters necessary. Places is useful for * padding the return value with leading 0s (zeros). * If places is not an integer, it is truncated. * If places is nonnumeric, DEC2OCT returns the #VALUE! error value. * If places is zero or negative, DEC2OCT returns the #NUM! error value. * Or can be an array of values * * @return array|string Result, or an error * If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function toOctal($value, $places = null) { if (is_array($value) || is_array($places)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places); } try { $value = self::validateValue($value); $value = self::validateDecimal($value); $places = self::validatePlaces($places); } catch (Exception $e) { return $e->getMessage(); } $value = (int) floor((float) $value); if ($value > self::LARGEST_OCTAL_IN_DECIMAL || $value < self::SMALLEST_OCTAL_IN_DECIMAL) { return ExcelError::NAN(); } $r = decoct($value); $r = substr($r, -10); return self::nbrConversionFormat($r, $places); } protected static function validateDecimal(string $value): string { if (strlen($value) > preg_match_all('/[-0123456789.]/', $value)) { throw new Exception(ExcelError::VALUE()); } return $value; } } PK!Alibraries/vendor/PhpSpreadsheet/Calculation/Engineering/index.phpnu[getMessage(); } $split1 = self::splitNumber($number1); $split2 = self::splitNumber($number2); return self::SPLIT_DIVISOR * ($split1[0] & $split2[0]) + ($split1[1] & $split2[1]); } /** * BITOR. * * Returns the bitwise OR of two integer values. * * Excel Function: * BITOR(number1, number2) * * @param null|array|bool|float|int|string $number1 Or can be an array of values * @param null|array|bool|float|int|string $number2 Or can be an array of values * * @return array|int|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function BITOR($number1, $number2) { if (is_array($number1) || is_array($number2)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $number1, $number2); } try { $number1 = self::validateBitwiseArgument($number1); $number2 = self::validateBitwiseArgument($number2); } catch (Exception $e) { return $e->getMessage(); } $split1 = self::splitNumber($number1); $split2 = self::splitNumber($number2); return self::SPLIT_DIVISOR * ($split1[0] | $split2[0]) + ($split1[1] | $split2[1]); } /** * BITXOR. * * Returns the bitwise XOR of two integer values. * * Excel Function: * BITXOR(number1, number2) * * @param null|array|bool|float|int|string $number1 Or can be an array of values * @param null|array|bool|float|int|string $number2 Or can be an array of values * * @return array|int|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function BITXOR($number1, $number2) { if (is_array($number1) || is_array($number2)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $number1, $number2); } try { $number1 = self::validateBitwiseArgument($number1); $number2 = self::validateBitwiseArgument($number2); } catch (Exception $e) { return $e->getMessage(); } $split1 = self::splitNumber($number1); $split2 = self::splitNumber($number2); return self::SPLIT_DIVISOR * ($split1[0] ^ $split2[0]) + ($split1[1] ^ $split2[1]); } /** * BITLSHIFT. * * Returns the number value shifted left by shift_amount bits. * * Excel Function: * BITLSHIFT(number, shift_amount) * * @param null|array|bool|float|int|string $number Or can be an array of values * @param null|array|bool|float|int|string $shiftAmount Or can be an array of values * * @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function BITLSHIFT($number, $shiftAmount) { if (is_array($number) || is_array($shiftAmount)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $shiftAmount); } try { $number = self::validateBitwiseArgument($number); $shiftAmount = self::validateShiftAmount($shiftAmount); } catch (Exception $e) { return $e->getMessage(); } $result = floor($number * (2 ** $shiftAmount)); if ($result > 2 ** 48 - 1) { return ExcelError::NAN(); } return $result; } /** * BITRSHIFT. * * Returns the number value shifted right by shift_amount bits. * * Excel Function: * BITRSHIFT(number, shift_amount) * * @param null|array|bool|float|int|string $number Or can be an array of values * @param null|array|bool|float|int|string $shiftAmount Or can be an array of values * * @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function BITRSHIFT($number, $shiftAmount) { if (is_array($number) || is_array($shiftAmount)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $shiftAmount); } try { $number = self::validateBitwiseArgument($number); $shiftAmount = self::validateShiftAmount($shiftAmount); } catch (Exception $e) { return $e->getMessage(); } $result = floor($number / (2 ** $shiftAmount)); if ($result > 2 ** 48 - 1) { // possible because shiftAmount can be negative return ExcelError::NAN(); } return $result; } /** * Validate arguments passed to the bitwise functions. * @param mixed $value */ private static function validateBitwiseArgument($value): float { $value = self::nullFalseTrueToNumber($value); if (is_numeric($value)) { $value = (float) $value; if ($value == floor($value)) { if (($value > 2 ** 48 - 1) || ($value < 0)) { throw new Exception(ExcelError::NAN()); } return floor($value); } throw new Exception(ExcelError::NAN()); } throw new Exception(ExcelError::VALUE()); } /** * Validate arguments passed to the bitwise functions. * @param mixed $value */ private static function validateShiftAmount($value): int { $value = self::nullFalseTrueToNumber($value); if (is_numeric($value)) { if (abs($value + 0) > 53) { throw new Exception(ExcelError::NAN()); } return (int) $value; } throw new Exception(ExcelError::VALUE()); } /** * Many functions accept null/false/true argument treated as 0/0/1. * @param mixed $number * @return mixed */ private static function nullFalseTrueToNumber(&$number) { if ($number === null) { $number = 0; } elseif (is_bool($number)) { $number = (int) $number; } return $number; } } PK!͈rKKIlibraries/vendor/PhpSpreadsheet/Calculation/Engineering/ConvertBinary.phpnu[getMessage(); } if (strlen($value) == 10 && $value[0] === '1') { // Two's Complement $value = substr($value, -9); return -(512 - bindec($value)); } return bindec($value); } /** * toHex. * * Return a binary value as hex. * * Excel Function: * BIN2HEX(x[,places]) * * @param array|bool|float|int|string $value The binary number (as a string) that you want to convert. The number * cannot contain more than 10 characters (10 bits). The most significant * bit of number is the sign bit. The remaining 9 bits are magnitude bits. * Negative numbers are represented using two's-complement notation. * If number is not a valid binary number, or if number contains more than * 10 characters (10 bits), BIN2HEX returns the #NUM! error value. * Or can be an array of values * @param null|array|float|int|string $places The number of characters to use. If places is omitted, BIN2HEX uses the * minimum number of characters necessary. Places is useful for padding the * return value with leading 0s (zeros). * If places is not an integer, it is truncated. * If places is nonnumeric, BIN2HEX returns the #VALUE! error value. * If places is negative, BIN2HEX returns the #NUM! error value. * Or can be an array of values * * @return array|string Result, or an error * If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function toHex($value, $places = null) { if (is_array($value) || is_array($places)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places); } try { $value = self::validateValue($value); $value = self::validateBinary($value); $places = self::validatePlaces($places); } catch (Exception $e) { return $e->getMessage(); } if (strlen($value) == 10 && $value[0] === '1') { $high2 = substr($value, 0, 2); $low8 = substr($value, 2); $xarr = ['00' => '00000000', '01' => '00000001', '10' => 'FFFFFFFE', '11' => 'FFFFFFFF']; return $xarr[$high2] . strtoupper(substr('0' . dechex((int) bindec($low8)), -2)); } $hexVal = (string) strtoupper(dechex((int) bindec($value))); return self::nbrConversionFormat($hexVal, $places); } /** * toOctal. * * Return a binary value as octal. * * Excel Function: * BIN2OCT(x[,places]) * * @param array|bool|float|int|string $value The binary number (as a string) that you want to convert. The number * cannot contain more than 10 characters (10 bits). The most significant * bit of number is the sign bit. The remaining 9 bits are magnitude bits. * Negative numbers are represented using two's-complement notation. * If number is not a valid binary number, or if number contains more than * 10 characters (10 bits), BIN2OCT returns the #NUM! error value. * Or can be an array of values * @param null|array|float|int|string $places The number of characters to use. If places is omitted, BIN2OCT uses the * minimum number of characters necessary. Places is useful for padding the * return value with leading 0s (zeros). * If places is not an integer, it is truncated. * If places is nonnumeric, BIN2OCT returns the #VALUE! error value. * If places is negative, BIN2OCT returns the #NUM! error value. * Or can be an array of values * * @return array|string Result, or an error * If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function toOctal($value, $places = null) { if (is_array($value) || is_array($places)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places); } try { $value = self::validateValue($value); $value = self::validateBinary($value); $places = self::validatePlaces($places); } catch (Exception $e) { return $e->getMessage(); } if (strlen($value) == 10 && $value[0] === '1') { // Two's Complement return str_repeat('7', 6) . strtoupper(decoct((int) bindec("11$value"))); } $octVal = (string) decoct((int) bindec($value)); return self::nbrConversionFormat($octVal, $places); } protected static function validateBinary(string $value): string { if ((strlen($value) > preg_match_all('/[01]/', $value)) || (strlen($value) > 10)) { throw new Exception(ExcelError::NAN()); } return $value; } } PK!U zzMlibraries/vendor/PhpSpreadsheet/Calculation/Engineering/ComplexOperations.phpnu[divideby(new ComplexObject($complexDivisor)); } catch (ComplexException $exception) { return ExcelError::NAN(); } } /** * IMSUB. * * Returns the difference of two complex numbers in x + yi or x + yj text format. * * Excel Function: * IMSUB(complexNumber1,complexNumber2) * * @param array|string $complexNumber1 the complex number from which to subtract complexNumber2 * Or can be an array of values * @param array|string $complexNumber2 the complex number to subtract from complexNumber1 * Or can be an array of values * * @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function IMSUB($complexNumber1, $complexNumber2) { if (is_array($complexNumber1) || is_array($complexNumber2)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $complexNumber1, $complexNumber2); } try { return (string) (new ComplexObject($complexNumber1))->subtract(new ComplexObject($complexNumber2)); } catch (ComplexException $exception) { return ExcelError::NAN(); } } /** * IMSUM. * * Returns the sum of two or more complex numbers in x + yi or x + yj text format. * * Excel Function: * IMSUM(complexNumber[,complexNumber[,...]]) * * @param string ...$complexNumbers Series of complex numbers to add */ public static function IMSUM(...$complexNumbers): string { // Return value $returnValue = new ComplexObject(0.0); $aArgs = Functions::flattenArray($complexNumbers); try { // Loop through the arguments foreach ($aArgs as $complex) { $returnValue = $returnValue->add(new ComplexObject($complex)); } } catch (ComplexException $exception) { return ExcelError::NAN(); } return (string) $returnValue; } /** * IMPRODUCT. * * Returns the product of two or more complex numbers in x + yi or x + yj text format. * * Excel Function: * IMPRODUCT(complexNumber[,complexNumber[,...]]) * * @param string ...$complexNumbers Series of complex numbers to multiply */ public static function IMPRODUCT(...$complexNumbers): string { // Return value $returnValue = new ComplexObject(1.0); $aArgs = Functions::flattenArray($complexNumbers); try { // Loop through the arguments foreach ($aArgs as $complex) { $returnValue = $returnValue->multiply(new ComplexObject($complex)); } } catch (ComplexException $exception) { return ExcelError::NAN(); } return (string) $returnValue; } } PK!V] Clibraries/vendor/PhpSpreadsheet/Calculation/Engineering/Compare.phpnu[getMessage(); } return (int) (abs($a - $b) < 1.0e-15); } /** * GESTEP. * * Excel Function: * GESTEP(number[,step]) * * Returns 1 if number >= step; returns 0 (zero) otherwise * Use this function to filter a set of values. For example, by summing several GESTEP * functions you calculate the count of values that exceed a threshold. * * @param array|bool|float|int|string $number the value to test against step * Or can be an array of values * @param null|array|bool|float|int|string $step The threshold value. If you omit a value for step, GESTEP uses zero. * Or can be an array of values * * @return array|int|string (string in the event of an error) * If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function GESTEP($number, $step = 0.0) { if (is_array($number) || is_array($step)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $step); } try { $number = EngineeringValidations::validateFloat($number); $step = EngineeringValidations::validateFloat($step ?? 0.0); } catch (Exception $e) { return $e->getMessage(); } return (int) ($number >= $step); } } PK!e+ | |Flibraries/vendor/PhpSpreadsheet/Calculation/Engineering/ConvertUOM.phpnu[ */ private static array $conversionUnits = [ // Weight and Mass 'g' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'UnitName' => 'Gram', 'AllowPrefix' => true], 'sg' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'UnitName' => 'Slug', 'AllowPrefix' => false], 'lbm' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'UnitName' => 'Pound mass (avoirdupois)', 'AllowPrefix' => false], 'u' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'UnitName' => 'U (atomic mass unit)', 'AllowPrefix' => true], 'ozm' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'UnitName' => 'Ounce mass (avoirdupois)', 'AllowPrefix' => false], 'grain' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'UnitName' => 'Grain', 'AllowPrefix' => false], 'cwt' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'UnitName' => 'U.S. (short) hundredweight', 'AllowPrefix' => false], 'shweight' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'UnitName' => 'U.S. (short) hundredweight', 'AllowPrefix' => false], 'uk_cwt' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'UnitName' => 'Imperial hundredweight', 'AllowPrefix' => false], 'lcwt' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'UnitName' => 'Imperial hundredweight', 'AllowPrefix' => false], 'hweight' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'UnitName' => 'Imperial hundredweight', 'AllowPrefix' => false], 'stone' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'UnitName' => 'Stone', 'AllowPrefix' => false], 'ton' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'UnitName' => 'Ton', 'AllowPrefix' => false], 'uk_ton' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'UnitName' => 'Imperial ton', 'AllowPrefix' => false], 'LTON' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'UnitName' => 'Imperial ton', 'AllowPrefix' => false], 'brton' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'UnitName' => 'Imperial ton', 'AllowPrefix' => false], // Distance 'm' => ['Group' => self::CATEGORY_DISTANCE, 'UnitName' => 'Meter', 'AllowPrefix' => true], 'mi' => ['Group' => self::CATEGORY_DISTANCE, 'UnitName' => 'Statute mile', 'AllowPrefix' => false], 'Nmi' => ['Group' => self::CATEGORY_DISTANCE, 'UnitName' => 'Nautical mile', 'AllowPrefix' => false], 'in' => ['Group' => self::CATEGORY_DISTANCE, 'UnitName' => 'Inch', 'AllowPrefix' => false], 'ft' => ['Group' => self::CATEGORY_DISTANCE, 'UnitName' => 'Foot', 'AllowPrefix' => false], 'yd' => ['Group' => self::CATEGORY_DISTANCE, 'UnitName' => 'Yard', 'AllowPrefix' => false], 'ang' => ['Group' => self::CATEGORY_DISTANCE, 'UnitName' => 'Angstrom', 'AllowPrefix' => true], 'ell' => ['Group' => self::CATEGORY_DISTANCE, 'UnitName' => 'Ell', 'AllowPrefix' => false], 'ly' => ['Group' => self::CATEGORY_DISTANCE, 'UnitName' => 'Light Year', 'AllowPrefix' => false], 'parsec' => ['Group' => self::CATEGORY_DISTANCE, 'UnitName' => 'Parsec', 'AllowPrefix' => false], 'pc' => ['Group' => self::CATEGORY_DISTANCE, 'UnitName' => 'Parsec', 'AllowPrefix' => false], 'Pica' => ['Group' => self::CATEGORY_DISTANCE, 'UnitName' => 'Pica (1/72 in)', 'AllowPrefix' => false], 'Picapt' => ['Group' => self::CATEGORY_DISTANCE, 'UnitName' => 'Pica (1/72 in)', 'AllowPrefix' => false], 'pica' => ['Group' => self::CATEGORY_DISTANCE, 'UnitName' => 'Pica (1/6 in)', 'AllowPrefix' => false], 'survey_mi' => ['Group' => self::CATEGORY_DISTANCE, 'UnitName' => 'U.S survey mile (statute mile)', 'AllowPrefix' => false], // Time 'yr' => ['Group' => self::CATEGORY_TIME, 'UnitName' => 'Year', 'AllowPrefix' => false], 'day' => ['Group' => self::CATEGORY_TIME, 'UnitName' => 'Day', 'AllowPrefix' => false], 'd' => ['Group' => self::CATEGORY_TIME, 'UnitName' => 'Day', 'AllowPrefix' => false], 'hr' => ['Group' => self::CATEGORY_TIME, 'UnitName' => 'Hour', 'AllowPrefix' => false], 'mn' => ['Group' => self::CATEGORY_TIME, 'UnitName' => 'Minute', 'AllowPrefix' => false], 'min' => ['Group' => self::CATEGORY_TIME, 'UnitName' => 'Minute', 'AllowPrefix' => false], 'sec' => ['Group' => self::CATEGORY_TIME, 'UnitName' => 'Second', 'AllowPrefix' => true], 's' => ['Group' => self::CATEGORY_TIME, 'UnitName' => 'Second', 'AllowPrefix' => true], // Pressure 'Pa' => ['Group' => self::CATEGORY_PRESSURE, 'UnitName' => 'Pascal', 'AllowPrefix' => true], 'p' => ['Group' => self::CATEGORY_PRESSURE, 'UnitName' => 'Pascal', 'AllowPrefix' => true], 'atm' => ['Group' => self::CATEGORY_PRESSURE, 'UnitName' => 'Atmosphere', 'AllowPrefix' => true], 'at' => ['Group' => self::CATEGORY_PRESSURE, 'UnitName' => 'Atmosphere', 'AllowPrefix' => true], 'mmHg' => ['Group' => self::CATEGORY_PRESSURE, 'UnitName' => 'mm of Mercury', 'AllowPrefix' => true], 'psi' => ['Group' => self::CATEGORY_PRESSURE, 'UnitName' => 'PSI', 'AllowPrefix' => true], 'Torr' => ['Group' => self::CATEGORY_PRESSURE, 'UnitName' => 'Torr', 'AllowPrefix' => true], // Force 'N' => ['Group' => self::CATEGORY_FORCE, 'UnitName' => 'Newton', 'AllowPrefix' => true], 'dyn' => ['Group' => self::CATEGORY_FORCE, 'UnitName' => 'Dyne', 'AllowPrefix' => true], 'dy' => ['Group' => self::CATEGORY_FORCE, 'UnitName' => 'Dyne', 'AllowPrefix' => true], 'lbf' => ['Group' => self::CATEGORY_FORCE, 'UnitName' => 'Pound force', 'AllowPrefix' => false], 'pond' => ['Group' => self::CATEGORY_FORCE, 'UnitName' => 'Pond', 'AllowPrefix' => true], // Energy 'J' => ['Group' => self::CATEGORY_ENERGY, 'UnitName' => 'Joule', 'AllowPrefix' => true], 'e' => ['Group' => self::CATEGORY_ENERGY, 'UnitName' => 'Erg', 'AllowPrefix' => true], 'c' => ['Group' => self::CATEGORY_ENERGY, 'UnitName' => 'Thermodynamic calorie', 'AllowPrefix' => true], 'cal' => ['Group' => self::CATEGORY_ENERGY, 'UnitName' => 'IT calorie', 'AllowPrefix' => true], 'eV' => ['Group' => self::CATEGORY_ENERGY, 'UnitName' => 'Electron volt', 'AllowPrefix' => true], 'ev' => ['Group' => self::CATEGORY_ENERGY, 'UnitName' => 'Electron volt', 'AllowPrefix' => true], 'HPh' => ['Group' => self::CATEGORY_ENERGY, 'UnitName' => 'Horsepower-hour', 'AllowPrefix' => false], 'hh' => ['Group' => self::CATEGORY_ENERGY, 'UnitName' => 'Horsepower-hour', 'AllowPrefix' => false], 'Wh' => ['Group' => self::CATEGORY_ENERGY, 'UnitName' => 'Watt-hour', 'AllowPrefix' => true], 'wh' => ['Group' => self::CATEGORY_ENERGY, 'UnitName' => 'Watt-hour', 'AllowPrefix' => true], 'flb' => ['Group' => self::CATEGORY_ENERGY, 'UnitName' => 'Foot-pound', 'AllowPrefix' => false], 'BTU' => ['Group' => self::CATEGORY_ENERGY, 'UnitName' => 'BTU', 'AllowPrefix' => false], 'btu' => ['Group' => self::CATEGORY_ENERGY, 'UnitName' => 'BTU', 'AllowPrefix' => false], // Power 'HP' => ['Group' => self::CATEGORY_POWER, 'UnitName' => 'Horsepower', 'AllowPrefix' => false], 'h' => ['Group' => self::CATEGORY_POWER, 'UnitName' => 'Horsepower', 'AllowPrefix' => false], 'W' => ['Group' => self::CATEGORY_POWER, 'UnitName' => 'Watt', 'AllowPrefix' => true], 'w' => ['Group' => self::CATEGORY_POWER, 'UnitName' => 'Watt', 'AllowPrefix' => true], 'PS' => ['Group' => self::CATEGORY_POWER, 'UnitName' => 'Pferdestärke', 'AllowPrefix' => false], // Magnetism 'T' => ['Group' => self::CATEGORY_MAGNETISM, 'UnitName' => 'Tesla', 'AllowPrefix' => true], 'ga' => ['Group' => self::CATEGORY_MAGNETISM, 'UnitName' => 'Gauss', 'AllowPrefix' => true], // Temperature 'C' => ['Group' => self::CATEGORY_TEMPERATURE, 'UnitName' => 'Degrees Celsius', 'AllowPrefix' => false], 'cel' => ['Group' => self::CATEGORY_TEMPERATURE, 'UnitName' => 'Degrees Celsius', 'AllowPrefix' => false], 'F' => ['Group' => self::CATEGORY_TEMPERATURE, 'UnitName' => 'Degrees Fahrenheit', 'AllowPrefix' => false], 'fah' => ['Group' => self::CATEGORY_TEMPERATURE, 'UnitName' => 'Degrees Fahrenheit', 'AllowPrefix' => false], 'K' => ['Group' => self::CATEGORY_TEMPERATURE, 'UnitName' => 'Kelvin', 'AllowPrefix' => false], 'kel' => ['Group' => self::CATEGORY_TEMPERATURE, 'UnitName' => 'Kelvin', 'AllowPrefix' => false], 'Rank' => ['Group' => self::CATEGORY_TEMPERATURE, 'UnitName' => 'Degrees Rankine', 'AllowPrefix' => false], 'Reau' => ['Group' => self::CATEGORY_TEMPERATURE, 'UnitName' => 'Degrees Réaumur', 'AllowPrefix' => false], // Volume 'l' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Litre', 'AllowPrefix' => true], 'L' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Litre', 'AllowPrefix' => true], 'lt' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Litre', 'AllowPrefix' => true], 'tsp' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Teaspoon', 'AllowPrefix' => false], 'tspm' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Modern Teaspoon', 'AllowPrefix' => false], 'tbs' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Tablespoon', 'AllowPrefix' => false], 'oz' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Fluid Ounce', 'AllowPrefix' => false], 'cup' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cup', 'AllowPrefix' => false], 'pt' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'U.S. Pint', 'AllowPrefix' => false], 'us_pt' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'U.S. Pint', 'AllowPrefix' => false], 'uk_pt' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'U.K. Pint', 'AllowPrefix' => false], 'qt' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Quart', 'AllowPrefix' => false], 'uk_qt' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Imperial Quart (UK)', 'AllowPrefix' => false], 'gal' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Gallon', 'AllowPrefix' => false], 'uk_gal' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Imperial Gallon (UK)', 'AllowPrefix' => false], 'ang3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Angstrom', 'AllowPrefix' => true], 'ang^3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Angstrom', 'AllowPrefix' => true], 'barrel' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'US Oil Barrel', 'AllowPrefix' => false], 'bushel' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'US Bushel', 'AllowPrefix' => false], 'in3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Inch', 'AllowPrefix' => false], 'in^3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Inch', 'AllowPrefix' => false], 'ft3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Foot', 'AllowPrefix' => false], 'ft^3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Foot', 'AllowPrefix' => false], 'ly3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Light Year', 'AllowPrefix' => false], 'ly^3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Light Year', 'AllowPrefix' => false], 'm3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Meter', 'AllowPrefix' => true], 'm^3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Meter', 'AllowPrefix' => true], 'mi3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Mile', 'AllowPrefix' => false], 'mi^3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Mile', 'AllowPrefix' => false], 'yd3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Yard', 'AllowPrefix' => false], 'yd^3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Yard', 'AllowPrefix' => false], 'Nmi3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Nautical Mile', 'AllowPrefix' => false], 'Nmi^3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Nautical Mile', 'AllowPrefix' => false], 'Pica3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Pica', 'AllowPrefix' => false], 'Pica^3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Pica', 'AllowPrefix' => false], 'Picapt3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Pica', 'AllowPrefix' => false], 'Picapt^3' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Cubic Pica', 'AllowPrefix' => false], 'GRT' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Gross Registered Ton', 'AllowPrefix' => false], 'regton' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Gross Registered Ton', 'AllowPrefix' => false], 'MTON' => ['Group' => self::CATEGORY_VOLUME, 'UnitName' => 'Measurement Ton (Freight Ton)', 'AllowPrefix' => false], // Area 'ha' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Hectare', 'AllowPrefix' => true], 'uk_acre' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'International Acre', 'AllowPrefix' => false], 'us_acre' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'US Survey/Statute Acre', 'AllowPrefix' => false], 'ang2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Angstrom', 'AllowPrefix' => true], 'ang^2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Angstrom', 'AllowPrefix' => true], 'ar' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Are', 'AllowPrefix' => true], 'ft2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Feet', 'AllowPrefix' => false], 'ft^2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Feet', 'AllowPrefix' => false], 'in2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Inches', 'AllowPrefix' => false], 'in^2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Inches', 'AllowPrefix' => false], 'ly2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Light Years', 'AllowPrefix' => false], 'ly^2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Light Years', 'AllowPrefix' => false], 'm2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Meters', 'AllowPrefix' => true], 'm^2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Meters', 'AllowPrefix' => true], 'Morgen' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Morgen', 'AllowPrefix' => false], 'mi2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Miles', 'AllowPrefix' => false], 'mi^2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Miles', 'AllowPrefix' => false], 'Nmi2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Nautical Miles', 'AllowPrefix' => false], 'Nmi^2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Nautical Miles', 'AllowPrefix' => false], 'Pica2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Pica', 'AllowPrefix' => false], 'Pica^2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Pica', 'AllowPrefix' => false], 'Picapt2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Pica', 'AllowPrefix' => false], 'Picapt^2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Pica', 'AllowPrefix' => false], 'yd2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Yards', 'AllowPrefix' => false], 'yd^2' => ['Group' => self::CATEGORY_AREA, 'UnitName' => 'Square Yards', 'AllowPrefix' => false], // Information 'byte' => ['Group' => self::CATEGORY_INFORMATION, 'UnitName' => 'Byte', 'AllowPrefix' => true], 'bit' => ['Group' => self::CATEGORY_INFORMATION, 'UnitName' => 'Bit', 'AllowPrefix' => true], // Speed 'm/s' => ['Group' => self::CATEGORY_SPEED, 'UnitName' => 'Meters per second', 'AllowPrefix' => true], 'm/sec' => ['Group' => self::CATEGORY_SPEED, 'UnitName' => 'Meters per second', 'AllowPrefix' => true], 'm/h' => ['Group' => self::CATEGORY_SPEED, 'UnitName' => 'Meters per hour', 'AllowPrefix' => true], 'm/hr' => ['Group' => self::CATEGORY_SPEED, 'UnitName' => 'Meters per hour', 'AllowPrefix' => true], 'mph' => ['Group' => self::CATEGORY_SPEED, 'UnitName' => 'Miles per hour', 'AllowPrefix' => false], 'admkn' => ['Group' => self::CATEGORY_SPEED, 'UnitName' => 'Admiralty Knot', 'AllowPrefix' => false], 'kn' => ['Group' => self::CATEGORY_SPEED, 'UnitName' => 'Knot', 'AllowPrefix' => false], ]; /** * Details of the Multiplier prefixes that can be used with Units of Measure in CONVERTUOM(). * * @var array */ private static array $conversionMultipliers = [ 'Y' => ['multiplier' => 1E24, 'name' => 'yotta'], 'Z' => ['multiplier' => 1E21, 'name' => 'zetta'], 'E' => ['multiplier' => 1E18, 'name' => 'exa'], 'P' => ['multiplier' => 1E15, 'name' => 'peta'], 'T' => ['multiplier' => 1E12, 'name' => 'tera'], 'G' => ['multiplier' => 1E9, 'name' => 'giga'], 'M' => ['multiplier' => 1E6, 'name' => 'mega'], 'k' => ['multiplier' => 1E3, 'name' => 'kilo'], 'h' => ['multiplier' => 1E2, 'name' => 'hecto'], 'e' => ['multiplier' => 1E1, 'name' => 'dekao'], 'da' => ['multiplier' => 1E1, 'name' => 'dekao'], 'd' => ['multiplier' => 1E-1, 'name' => 'deci'], 'c' => ['multiplier' => 1E-2, 'name' => 'centi'], 'm' => ['multiplier' => 1E-3, 'name' => 'milli'], 'u' => ['multiplier' => 1E-6, 'name' => 'micro'], 'n' => ['multiplier' => 1E-9, 'name' => 'nano'], 'p' => ['multiplier' => 1E-12, 'name' => 'pico'], 'f' => ['multiplier' => 1E-15, 'name' => 'femto'], 'a' => ['multiplier' => 1E-18, 'name' => 'atto'], 'z' => ['multiplier' => 1E-21, 'name' => 'zepto'], 'y' => ['multiplier' => 1E-24, 'name' => 'yocto'], ]; /** * Details of the Multiplier prefixes that can be used with Units of Measure in CONVERTUOM(). * ** @var array */ private static array $binaryConversionMultipliers = [ 'Yi' => ['multiplier' => 2 ** 80, 'name' => 'yobi'], 'Zi' => ['multiplier' => 2 ** 70, 'name' => 'zebi'], 'Ei' => ['multiplier' => 2 ** 60, 'name' => 'exbi'], 'Pi' => ['multiplier' => 2 ** 50, 'name' => 'pebi'], 'Ti' => ['multiplier' => 2 ** 40, 'name' => 'tebi'], 'Gi' => ['multiplier' => 2 ** 30, 'name' => 'gibi'], 'Mi' => ['multiplier' => 2 ** 20, 'name' => 'mebi'], 'ki' => ['multiplier' => 2 ** 10, 'name' => 'kibi'], ]; /** * Details of the Units of measure conversion factors, organised by group. * * @var array> */ private static array $unitConversions = [ // Conversion uses gram (g) as an intermediate unit self::CATEGORY_WEIGHT_AND_MASS => [ 'g' => 1.0, 'sg' => 6.85217658567918E-05, 'lbm' => 2.20462262184878E-03, 'u' => 6.02214179421676E+23, 'ozm' => 3.52739619495804E-02, 'grain' => 1.54323583529414E+01, 'cwt' => 2.20462262184878E-05, 'shweight' => 2.20462262184878E-05, 'uk_cwt' => 1.96841305522212E-05, 'lcwt' => 1.96841305522212E-05, 'hweight' => 1.96841305522212E-05, 'stone' => 1.57473044417770E-04, 'ton' => 1.10231131092439E-06, 'uk_ton' => 9.84206527611061E-07, 'LTON' => 9.84206527611061E-07, 'brton' => 9.84206527611061E-07, ], // Conversion uses meter (m) as an intermediate unit self::CATEGORY_DISTANCE => [ 'm' => 1.0, 'mi' => 6.21371192237334E-04, 'Nmi' => 5.39956803455724E-04, 'in' => 3.93700787401575E+01, 'ft' => 3.28083989501312E+00, 'yd' => 1.09361329833771E+00, 'ang' => 1.0E+10, 'ell' => 8.74890638670166E-01, 'ly' => 1.05700083402462E-16, 'parsec' => 3.24077928966473E-17, 'pc' => 3.24077928966473E-17, 'Pica' => 2.83464566929134E+03, 'Picapt' => 2.83464566929134E+03, 'pica' => 2.36220472440945E+02, 'survey_mi' => 6.21369949494950E-04, ], // Conversion uses second (s) as an intermediate unit self::CATEGORY_TIME => [ 'yr' => 3.16880878140289E-08, 'day' => 1.15740740740741E-05, 'd' => 1.15740740740741E-05, 'hr' => 2.77777777777778E-04, 'mn' => 1.66666666666667E-02, 'min' => 1.66666666666667E-02, 'sec' => 1.0, 's' => 1.0, ], // Conversion uses Pascal (Pa) as an intermediate unit self::CATEGORY_PRESSURE => [ 'Pa' => 1.0, 'p' => 1.0, 'atm' => 9.86923266716013E-06, 'at' => 9.86923266716013E-06, 'mmHg' => 7.50063755419211E-03, 'psi' => 1.45037737730209E-04, 'Torr' => 7.50061682704170E-03, ], // Conversion uses Newton (N) as an intermediate unit self::CATEGORY_FORCE => [ 'N' => 1.0, 'dyn' => 1.0E+5, 'dy' => 1.0E+5, 'lbf' => 2.24808923655339E-01, 'pond' => 1.01971621297793E+02, ], // Conversion uses Joule (J) as an intermediate unit self::CATEGORY_ENERGY => [ 'J' => 1.0, 'e' => 9.99999519343231E+06, 'c' => 2.39006249473467E-01, 'cal' => 2.38846190642017E-01, 'eV' => 6.24145700000000E+18, 'ev' => 6.24145700000000E+18, 'HPh' => 3.72506430801000E-07, 'hh' => 3.72506430801000E-07, 'Wh' => 2.77777916238711E-04, 'wh' => 2.77777916238711E-04, 'flb' => 2.37304222192651E+01, 'BTU' => 9.47815067349015E-04, 'btu' => 9.47815067349015E-04, ], // Conversion uses Horsepower (HP) as an intermediate unit self::CATEGORY_POWER => [ 'HP' => 1.0, 'h' => 1.0, 'W' => 7.45699871582270E+02, 'w' => 7.45699871582270E+02, 'PS' => 1.01386966542400E+00, ], // Conversion uses Tesla (T) as an intermediate unit self::CATEGORY_MAGNETISM => [ 'T' => 1.0, 'ga' => 10000.0, ], // Conversion uses litre (l) as an intermediate unit self::CATEGORY_VOLUME => [ 'l' => 1.0, 'L' => 1.0, 'lt' => 1.0, 'tsp' => 2.02884136211058E+02, 'tspm' => 2.0E+02, 'tbs' => 6.76280454036860E+01, 'oz' => 3.38140227018430E+01, 'cup' => 4.22675283773038E+00, 'pt' => 2.11337641886519E+00, 'us_pt' => 2.11337641886519E+00, 'uk_pt' => 1.75975398639270E+00, 'qt' => 1.05668820943259E+00, 'uk_qt' => 8.79876993196351E-01, 'gal' => 2.64172052358148E-01, 'uk_gal' => 2.19969248299088E-01, 'ang3' => 1.0E+27, 'ang^3' => 1.0E+27, 'barrel' => 6.28981077043211E-03, 'bushel' => 2.83775932584017E-02, 'in3' => 6.10237440947323E+01, 'in^3' => 6.10237440947323E+01, 'ft3' => 3.53146667214886E-02, 'ft^3' => 3.53146667214886E-02, 'ly3' => 1.18093498844171E-51, 'ly^3' => 1.18093498844171E-51, 'm3' => 1.0E-03, 'm^3' => 1.0E-03, 'mi3' => 2.39912758578928E-13, 'mi^3' => 2.39912758578928E-13, 'yd3' => 1.30795061931439E-03, 'yd^3' => 1.30795061931439E-03, 'Nmi3' => 1.57426214685811E-13, 'Nmi^3' => 1.57426214685811E-13, 'Pica3' => 2.27769904358706E+07, 'Pica^3' => 2.27769904358706E+07, 'Picapt3' => 2.27769904358706E+07, 'Picapt^3' => 2.27769904358706E+07, 'GRT' => 3.53146667214886E-04, 'regton' => 3.53146667214886E-04, 'MTON' => 8.82866668037215E-04, ], // Conversion uses hectare (ha) as an intermediate unit self::CATEGORY_AREA => [ 'ha' => 1.0, 'uk_acre' => 2.47105381467165E+00, 'us_acre' => 2.47104393046628E+00, 'ang2' => 1.0E+24, 'ang^2' => 1.0E+24, 'ar' => 1.0E+02, 'ft2' => 1.07639104167097E+05, 'ft^2' => 1.07639104167097E+05, 'in2' => 1.55000310000620E+07, 'in^2' => 1.55000310000620E+07, 'ly2' => 1.11725076312873E-28, 'ly^2' => 1.11725076312873E-28, 'm2' => 1.0E+04, 'm^2' => 1.0E+04, 'Morgen' => 4.0E+00, 'mi2' => 3.86102158542446E-03, 'mi^2' => 3.86102158542446E-03, 'Nmi2' => 2.91553349598123E-03, 'Nmi^2' => 2.91553349598123E-03, 'Pica2' => 8.03521607043214E+10, 'Pica^2' => 8.03521607043214E+10, 'Picapt2' => 8.03521607043214E+10, 'Picapt^2' => 8.03521607043214E+10, 'yd2' => 1.19599004630108E+04, 'yd^2' => 1.19599004630108E+04, ], // Conversion uses bit (bit) as an intermediate unit self::CATEGORY_INFORMATION => [ 'bit' => 1.0, 'byte' => 0.125, ], // Conversion uses Meters per Second (m/s) as an intermediate unit self::CATEGORY_SPEED => [ 'm/s' => 1.0, 'm/sec' => 1.0, 'm/h' => 3.60E+03, 'm/hr' => 3.60E+03, 'mph' => 2.23693629205440E+00, 'admkn' => 1.94260256941567E+00, 'kn' => 1.94384449244060E+00, ], ]; /** * getConversionGroups * Returns a list of the different conversion groups for UOM conversions. */ public static function getConversionCategories(): array { $conversionGroups = []; foreach (self::$conversionUnits as $conversionUnit) { $conversionGroups[] = $conversionUnit['Group']; } return array_merge(array_unique($conversionGroups)); } /** * getConversionGroupUnits * Returns an array of units of measure, for a specified conversion group, or for all groups. * * @param ?string $category The group whose units of measure you want to retrieve */ public static function getConversionCategoryUnits(?string $category = null): array { $conversionGroups = []; foreach (self::$conversionUnits as $conversionUnit => $conversionGroup) { if (($category === null) || ($conversionGroup['Group'] == $category)) { $conversionGroups[$conversionGroup['Group']][] = $conversionUnit; } } return $conversionGroups; } /** * getConversionGroupUnitDetails. * * @param ?string $category The group whose units of measure you want to retrieve */ public static function getConversionCategoryUnitDetails(?string $category = null): array { $conversionGroups = []; foreach (self::$conversionUnits as $conversionUnit => $conversionGroup) { if (($category === null) || ($conversionGroup['Group'] == $category)) { $conversionGroups[$conversionGroup['Group']][] = [ 'unit' => $conversionUnit, 'description' => $conversionGroup['UnitName'], ]; } } return $conversionGroups; } /** * getConversionMultipliers * Returns an array of the Multiplier prefixes that can be used with Units of Measure in CONVERTUOM(). * * @return mixed[] */ public static function getConversionMultipliers(): array { return self::$conversionMultipliers; } /** * getBinaryConversionMultipliers * Returns an array of the additional Multiplier prefixes that can be used with Information Units of Measure in CONVERTUOM(). * * @return mixed[] */ public static function getBinaryConversionMultipliers(): array { return self::$binaryConversionMultipliers; } /** * CONVERT. * * Converts a number from one measurement system to another. * For example, CONVERT can translate a table of distances in miles to a table of distances * in kilometers. * * Excel Function: * CONVERT(value,fromUOM,toUOM) * * @param array|float|int|string $value the value in fromUOM to convert * Or can be an array of values * @param array|string $fromUOM the units for value * Or can be an array of values * @param array|string $toUOM the units for the result * Or can be an array of values * * @return array|float|string Result, or a string containing an error * If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function CONVERT($value, $fromUOM, $toUOM) { if (is_array($value) || is_array($fromUOM) || is_array($toUOM)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $fromUOM, $toUOM); } if (!is_numeric($value)) { return ExcelError::VALUE(); } try { [$fromUOM, $fromCategory, $fromMultiplier] = self::getUOMDetails($fromUOM); [$toUOM, $toCategory, $toMultiplier] = self::getUOMDetails($toUOM); } catch (Exception $exception) { return ExcelError::NA(); } if ($fromCategory !== $toCategory) { return ExcelError::NA(); } // @var float $value $value *= $fromMultiplier; if (($fromUOM === $toUOM) && ($fromMultiplier === $toMultiplier)) { // We've already factored $fromMultiplier into the value, so we need // to reverse it again return $value / $fromMultiplier; } elseif ($fromUOM === $toUOM) { return $value / $toMultiplier; } elseif ($fromCategory === self::CATEGORY_TEMPERATURE) { return self::convertTemperature($fromUOM, $toUOM, $value); } $baseValue = $value * (1.0 / self::$unitConversions[$fromCategory][$fromUOM]); return ($baseValue * self::$unitConversions[$fromCategory][$toUOM]) / $toMultiplier; } private static function getUOMDetails(string $uom): array { if (isset(self::$conversionUnits[$uom])) { $unitCategory = self::$conversionUnits[$uom]['Group']; return [$uom, $unitCategory, 1.0]; } // Check 1-character standard metric multiplier prefixes $multiplierType = substr($uom, 0, 1); $uom = substr($uom, 1); if (isset(self::$conversionUnits[$uom], self::$conversionMultipliers[$multiplierType])) { if (self::$conversionUnits[$uom]['AllowPrefix'] === false) { throw new Exception('Prefix not allowed for UoM'); } $unitCategory = self::$conversionUnits[$uom]['Group']; return [$uom, $unitCategory, self::$conversionMultipliers[$multiplierType]['multiplier']]; } $multiplierType .= substr($uom, 0, 1); $uom = substr($uom, 1); // Check 2-character standard metric multiplier prefixes if (isset(self::$conversionUnits[$uom], self::$conversionMultipliers[$multiplierType])) { if (self::$conversionUnits[$uom]['AllowPrefix'] === false) { throw new Exception('Prefix not allowed for UoM'); } $unitCategory = self::$conversionUnits[$uom]['Group']; return [$uom, $unitCategory, self::$conversionMultipliers[$multiplierType]['multiplier']]; } // Check 2-character binary multiplier prefixes if (isset(self::$conversionUnits[$uom], self::$binaryConversionMultipliers[$multiplierType])) { if (self::$conversionUnits[$uom]['AllowPrefix'] === false) { throw new Exception('Prefix not allowed for UoM'); } $unitCategory = self::$conversionUnits[$uom]['Group']; if ($unitCategory !== 'Information') { throw new Exception('Binary Prefix is only allowed for Information UoM'); } return [$uom, $unitCategory, self::$binaryConversionMultipliers[$multiplierType]['multiplier']]; } throw new Exception('UoM Not Found'); } /** * @param float|int $value * @return float|int */ protected static function convertTemperature(string $fromUOM, string $toUOM, $value) { $fromUOM = self::resolveTemperatureSynonyms($fromUOM); $toUOM = self::resolveTemperatureSynonyms($toUOM); if ($fromUOM === $toUOM) { return $value; } // Convert to Kelvin switch ($fromUOM) { case 'F': $value = ($value - 32) / 1.8 + 273.15; break; case 'C': $value += 273.15; break; case 'Rank': $value /= 1.8; break; case 'Reau': $value = $value * 1.25 + 273.15; break; } // Convert from Kelvin switch ($toUOM) { case 'F': $value = ($value - 273.15) * 1.8 + 32.00; break; case 'C': $value -= 273.15; break; case 'Rank': $value *= 1.8; break; case 'Reau': $value = ($value - 273.15) * 0.80000; break; } return $value; } private static function resolveTemperatureSynonyms(string $uom): string { switch ($uom) { case 'fah': return 'F'; case 'cel': return 'C'; case 'kel': return 'K'; default: return $uom; } } } PK!CClibraries/vendor/PhpSpreadsheet/Calculation/Engineering/BesselI.phpnu[getMessage(); } if ($ord < 0) { return ExcelError::NAN(); } $fResult = self::calculate($x, $ord); return (is_nan($fResult)) ? ExcelError::NAN() : $fResult; } private static function calculate(float $x, int $ord): float { switch ($ord) { case 0: return self::besselI0($x); case 1: return self::besselI1($x); default: return self::besselI2($x, $ord); } } private static function besselI0(float $x): float { $ax = abs($x); if ($ax < 3.75) { $y = $x / 3.75; $y = $y * $y; return 1.0 + $y * (3.5156229 + $y * (3.0899424 + $y * (1.2067492 + $y * (0.2659732 + $y * (0.360768e-1 + $y * 0.45813e-2))))); } $y = 3.75 / $ax; return (exp($ax) / sqrt($ax)) * (0.39894228 + $y * (0.1328592e-1 + $y * (0.225319e-2 + $y * (-0.157565e-2 + $y * (0.916281e-2 + $y * (-0.2057706e-1 + $y * (0.2635537e-1 + $y * (-0.1647633e-1 + $y * 0.392377e-2)))))))); } private static function besselI1(float $x): float { $ax = abs($x); if ($ax < 3.75) { $y = $x / 3.75; $y = $y * $y; $ans = $ax * (0.5 + $y * (0.87890594 + $y * (0.51498869 + $y * (0.15084934 + $y * (0.2658733e-1 + $y * (0.301532e-2 + $y * 0.32411e-3)))))); return ($x < 0.0) ? -$ans : $ans; } $y = 3.75 / $ax; $ans = 0.2282967e-1 + $y * (-0.2895312e-1 + $y * (0.1787654e-1 - $y * 0.420059e-2)); $ans = 0.39894228 + $y * (-0.3988024e-1 + $y * (-0.362018e-2 + $y * (0.163801e-2 + $y * (-0.1031555e-1 + $y * $ans)))); $ans *= exp($ax) / sqrt($ax); return ($x < 0.0) ? -$ans : $ans; } private static function besselI2(float $x, int $ord): float { if ($x === 0.0) { return 0.0; } $tox = 2.0 / abs($x); $bip = 0; $ans = 0.0; $bi = 1.0; for ($j = 2 * ($ord + (int) sqrt(40.0 * $ord)); $j > 0; --$j) { $bim = $bip + $j * $tox * $bi; $bip = $bi; $bi = $bim; if (abs($bi) > 1.0e+12) { $ans *= 1.0e-12; $bi *= 1.0e-12; $bip *= 1.0e-12; } if ($j === $ord) { $ans = $bip; } } $ans *= self::besselI0($x) / $bi; return ($x < 0.0 && (($ord % 2) === 1)) ? -$ans : $ans; } } PK!0Clibraries/vendor/PhpSpreadsheet/Calculation/Engineering/BesselJ.phpnu[ 8. This code provides a more accurate calculation * * @param mixed $x A float value at which to evaluate the function. * If x is nonnumeric, BESSELJ returns the #VALUE! error value. * Or can be an array of values * @param mixed $ord The integer order of the Bessel function. * If ord is not an integer, it is truncated. * If $ord is nonnumeric, BESSELJ returns the #VALUE! error value. * If $ord < 0, BESSELJ returns the #NUM! error value. * Or can be an array of values * * @return array|float|string Result, or a string containing an error * If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function BESSELJ($x, $ord) { if (is_array($x) || is_array($ord)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $x, $ord); } try { $x = EngineeringValidations::validateFloat($x); $ord = EngineeringValidations::validateInt($ord); } catch (Exception $e) { return $e->getMessage(); } if ($ord < 0) { return ExcelError::NAN(); } $fResult = self::calculate($x, $ord); return (is_nan($fResult)) ? ExcelError::NAN() : $fResult; } private static function calculate(float $x, int $ord): float { switch ($ord) { case 0: return self::besselJ0($x); case 1: return self::besselJ1($x); default: return self::besselJ2($x, $ord); } } private static function besselJ0(float $x): float { $ax = abs($x); if ($ax < 8.0) { $y = $x * $x; $ans1 = 57568490574.0 + $y * (-13362590354.0 + $y * (651619640.7 + $y * (-11214424.18 + $y * (77392.33017 + $y * (-184.9052456))))); $ans2 = 57568490411.0 + $y * (1029532985.0 + $y * (9494680.718 + $y * (59272.64853 + $y * (267.8532712 + $y * 1.0)))); return $ans1 / $ans2; } $z = 8.0 / $ax; $y = $z * $z; $xx = $ax - 0.785398164; $ans1 = 1.0 + $y * (-0.1098628627e-2 + $y * (0.2734510407e-4 + $y * (-0.2073370639e-5 + $y * 0.2093887211e-6))); $ans2 = -0.1562499995e-1 + $y * (0.1430488765e-3 + $y * (-0.6911147651e-5 + $y * (0.7621095161e-6 - $y * 0.934935152e-7))); return sqrt(0.636619772 / $ax) * (cos($xx) * $ans1 - $z * sin($xx) * $ans2); } private static function besselJ1(float $x): float { $ax = abs($x); if ($ax < 8.0) { $y = $x * $x; $ans1 = $x * (72362614232.0 + $y * (-7895059235.0 + $y * (242396853.1 + $y * (-2972611.439 + $y * (15704.48260 + $y * (-30.16036606)))))); $ans2 = 144725228442.0 + $y * (2300535178.0 + $y * (18583304.74 + $y * (99447.43394 + $y * (376.9991397 + $y * 1.0)))); return $ans1 / $ans2; } $z = 8.0 / $ax; $y = $z * $z; $xx = $ax - 2.356194491; $ans1 = 1.0 + $y * (0.183105e-2 + $y * (-0.3516396496e-4 + $y * (0.2457520174e-5 + $y * (-0.240337019e-6)))); $ans2 = 0.04687499995 + $y * (-0.2002690873e-3 + $y * (0.8449199096e-5 + $y * (-0.88228987e-6 + $y * 0.105787412e-6))); $ans = sqrt(0.636619772 / $ax) * (cos($xx) * $ans1 - $z * sin($xx) * $ans2); return ($x < 0.0) ? -$ans : $ans; } private static function besselJ2(float $x, int $ord): float { $ax = abs($x); if ($ax === 0.0) { return 0.0; } if ($ax > $ord) { return self::besselj2a($ax, $ord, $x); } return self::besselj2b($ax, $ord, $x); } private static function besselj2a(float $ax, int $ord, float $x): float { $tox = 2.0 / $ax; $bjm = self::besselJ0($ax); $bj = self::besselJ1($ax); for ($j = 1; $j < $ord; ++$j) { $bjp = $j * $tox * $bj - $bjm; $bjm = $bj; $bj = $bjp; } $ans = $bj; return ($x < 0.0 && ($ord % 2) == 1) ? -$ans : $ans; } private static function besselj2b(float $ax, int $ord, float $x): float { $tox = 2.0 / $ax; $jsum = false; $bjp = $ans = $sum = 0.0; $bj = 1.0; for ($j = 2 * ($ord + (int) sqrt(40.0 * $ord)); $j > 0; --$j) { $bjm = $j * $tox * $bj - $bjp; $bjp = $bj; $bj = $bjm; if (abs($bj) > 1.0e+10) { $bj *= 1.0e-10; $bjp *= 1.0e-10; $ans *= 1.0e-10; $sum *= 1.0e-10; } if ($jsum === true) { $sum += $bj; } $jsum = $jsum === false; if ($j === $ord) { $ans = $bjp; } } $sum = 2.0 * $sum - $bj; $ans /= $sum; return ($x < 0.0 && ($ord % 2) === 1) ? -$ans : $ans; } } PK!1 Clibraries/vendor/PhpSpreadsheet/Calculation/Engineering/BesselK.phpnu[getMessage(); } if (($ord < 0) || ($x <= 0.0)) { return ExcelError::NAN(); } $fBk = self::calculate($x, $ord); return (is_nan($fBk)) ? ExcelError::NAN() : $fBk; } private static function calculate(float $x, int $ord): float { switch ($ord) { case 0: return self::besselK0($x); case 1: return self::besselK1($x); default: return self::besselK2($x, $ord); } } /** * Mollify Phpstan. * * @codeCoverageIgnore */ private static function callBesselI(float $x, int $ord): float { $rslt = BesselI::BESSELI($x, $ord); if (!is_float($rslt)) { throw new Exception('Unexpected array or string'); } return $rslt; } private static function besselK0(float $x): float { if ($x <= 2) { $fNum2 = $x * 0.5; $y = ($fNum2 * $fNum2); return -log($fNum2) * self::callBesselI($x, 0) + (-0.57721566 + $y * (0.42278420 + $y * (0.23069756 + $y * (0.3488590e-1 + $y * (0.262698e-2 + $y * (0.10750e-3 + $y * 0.74e-5)))))); } $y = 2 / $x; return exp(-$x) / sqrt($x) * (1.25331414 + $y * (-0.7832358e-1 + $y * (0.2189568e-1 + $y * (-0.1062446e-1 + $y * (0.587872e-2 + $y * (-0.251540e-2 + $y * 0.53208e-3)))))); } private static function besselK1(float $x): float { if ($x <= 2) { $fNum2 = $x * 0.5; $y = ($fNum2 * $fNum2); return log($fNum2) * self::callBesselI($x, 1) + (1 + $y * (0.15443144 + $y * (-0.67278579 + $y * (-0.18156897 + $y * (-0.1919402e-1 + $y * (-0.110404e-2 + $y * (-0.4686e-4))))))) / $x; } $y = 2 / $x; return exp(-$x) / sqrt($x) * (1.25331414 + $y * (0.23498619 + $y * (-0.3655620e-1 + $y * (0.1504268e-1 + $y * (-0.780353e-2 + $y * (0.325614e-2 + $y * (-0.68245e-3))))))); } private static function besselK2(float $x, int $ord): float { $fTox = 2 / $x; $fBkm = self::besselK0($x); $fBk = self::besselK1($x); for ($n = 1; $n < $ord; ++$n) { $fBkp = $fBkm + $n * $fTox * $fBk; $fBkm = $fBk; $fBk = $fBkp; } return $fBk; } } PK!}q<libraries/vendor/PhpSpreadsheet/Calculation/ArrayEnabled.phpnu[initialise(($arguments === false) ? [] : $arguments); } /** * Handles array argument processing when the function accepts a single argument that can be an array argument. * Example use for: * DAYOFMONTH() or FACT(). */ protected static function evaluateSingleArgumentArray(callable $method, array $values): array { $result = []; foreach ($values as $value) { $result[] = $method($value); } return $result; } /** * Handles array argument processing when the function accepts multiple arguments, * and any of them can be an array argument. * Example use for: * ROUND() or DATE(). * @param mixed ...$arguments */ protected static function evaluateArrayArguments(callable $method, ...$arguments): array { self::initialiseHelper($arguments); $arguments = self::$arrayArgumentHelper->arguments(); return ArrayArgumentProcessor::processArguments(self::$arrayArgumentHelper, $method, ...$arguments); } /** * Handles array argument processing when the function accepts multiple arguments, * but only the first few (up to limit) can be an array arguments. * Example use for: * NETWORKDAYS() or CONCATENATE(), where the last argument is a matrix (or a series of values) that need * to be treated as a such rather than as an array arguments. * @param mixed ...$arguments */ protected static function evaluateArrayArgumentsSubset(callable $method, int $limit, ...$arguments): array { self::initialiseHelper(array_slice($arguments, 0, $limit)); $trailingArguments = array_slice($arguments, $limit); $arguments = self::$arrayArgumentHelper->arguments(); $arguments = array_merge($arguments, $trailingArguments); return ArrayArgumentProcessor::processArguments(self::$arrayArgumentHelper, $method, ...$arguments); } /** * @param mixed $value */ private static function testFalse($value): bool { return $value === false; } /** * Handles array argument processing when the function accepts multiple arguments, * but only the last few (from start) can be an array arguments. * Example use for: * Z.TEST() or INDEX(), where the first argument 1 is a matrix that needs to be treated as a dataset * rather than as an array argument. * @param mixed ...$arguments */ protected static function evaluateArrayArgumentsSubsetFrom(callable $method, int $start, ...$arguments): array { $arrayArgumentsSubset = array_combine( range($start, count($arguments) - $start), array_slice($arguments, $start) ); if (self::testFalse($arrayArgumentsSubset)) { return ['#VALUE!']; } self::initialiseHelper($arrayArgumentsSubset); $leadingArguments = array_slice($arguments, 0, $start); $arguments = self::$arrayArgumentHelper->arguments(); $arguments = array_merge($leadingArguments, $arguments); return ArrayArgumentProcessor::processArguments(self::$arrayArgumentHelper, $method, ...$arguments); } /** * Handles array argument processing when the function accepts multiple arguments, * and any of them can be an array argument except for the one specified by ignore. * Example use for: * HLOOKUP() and VLOOKUP(), where argument 1 is a matrix that needs to be treated as a database * rather than as an array argument. * @param mixed ...$arguments */ protected static function evaluateArrayArgumentsIgnore(callable $method, int $ignore, ...$arguments): array { $leadingArguments = array_slice($arguments, 0, $ignore); $ignoreArgument = array_slice($arguments, $ignore, 1); $trailingArguments = array_slice($arguments, $ignore + 1); self::initialiseHelper(array_merge($leadingArguments, [[null]], $trailingArguments)); $arguments = self::$arrayArgumentHelper->arguments(); array_splice($arguments, $ignore, 1, $ignoreArgument); return ArrayArgumentProcessor::processArguments(self::$arrayArgumentHelper, $method, ...$arguments); } } PK!%<}Clibraries/vendor/PhpSpreadsheet/Calculation/Engine/BranchPruner.phpnu[branchPruningEnabled = $branchPruningEnabled; } public function clearBranchStore(): void { $this->branchStoreKeyCounter = 0; } public function initialiseForLoop(): void { $this->currentCondition = null; $this->currentOnlyIf = null; $this->currentOnlyIfNot = null; $this->previousStoreKey = null; $this->pendingStoreKey = empty($this->storeKeysStack) ? null : end($this->storeKeysStack); if ($this->branchPruningEnabled) { $this->initialiseCondition(); $this->initialiseThen(); $this->initialiseElse(); } } private function initialiseCondition(): void { if (isset($this->conditionMap[$this->pendingStoreKey]) && $this->conditionMap[$this->pendingStoreKey]) { $this->currentCondition = $this->pendingStoreKey; $stackDepth = count($this->storeKeysStack); if ($stackDepth > 1) { // nested if $this->previousStoreKey = $this->storeKeysStack[$stackDepth - 2]; } } } private function initialiseThen(): void { if (isset($this->thenMap[$this->pendingStoreKey]) && $this->thenMap[$this->pendingStoreKey]) { $this->currentOnlyIf = $this->pendingStoreKey; } elseif ( isset($this->previousStoreKey, $this->thenMap[$this->previousStoreKey]) && $this->thenMap[$this->previousStoreKey] ) { $this->currentOnlyIf = $this->previousStoreKey; } } private function initialiseElse(): void { if (isset($this->elseMap[$this->pendingStoreKey]) && $this->elseMap[$this->pendingStoreKey]) { $this->currentOnlyIfNot = $this->pendingStoreKey; } elseif ( isset($this->previousStoreKey, $this->elseMap[$this->previousStoreKey]) && $this->elseMap[$this->previousStoreKey] ) { $this->currentOnlyIfNot = $this->previousStoreKey; } } public function decrementDepth(): void { if (!empty($this->pendingStoreKey)) { --$this->braceDepthMap[$this->pendingStoreKey]; } } public function incrementDepth(): void { if (!empty($this->pendingStoreKey)) { ++$this->braceDepthMap[$this->pendingStoreKey]; } } public function functionCall(string $functionName): void { if ($this->branchPruningEnabled && ($functionName === 'IF(')) { // we handle a new if $this->pendingStoreKey = $this->getUnusedBranchStoreKey(); $this->storeKeysStack[] = $this->pendingStoreKey; $this->conditionMap[$this->pendingStoreKey] = true; $this->braceDepthMap[$this->pendingStoreKey] = 0; } elseif (!empty($this->pendingStoreKey) && array_key_exists($this->pendingStoreKey, $this->braceDepthMap)) { // this is not an if but we go deeper ++$this->braceDepthMap[$this->pendingStoreKey]; } } public function argumentSeparator(): void { if (!empty($this->pendingStoreKey) && $this->braceDepthMap[$this->pendingStoreKey] === 0) { // We must go to the IF next argument if ($this->conditionMap[$this->pendingStoreKey]) { $this->conditionMap[$this->pendingStoreKey] = false; $this->thenMap[$this->pendingStoreKey] = true; } elseif ($this->thenMap[$this->pendingStoreKey]) { $this->thenMap[$this->pendingStoreKey] = false; $this->elseMap[$this->pendingStoreKey] = true; } elseif ($this->elseMap[$this->pendingStoreKey]) { throw new Exception('Reaching fourth argument of an IF'); } } } /** * @param mixed $value */ public function closingBrace($value): void { if (!empty($this->pendingStoreKey) && $this->braceDepthMap[$this->pendingStoreKey] === -1) { // we are closing an IF( if ($value !== 'IF(') { throw new Exception('Parser bug we should be in an "IF("'); } if ($this->conditionMap[$this->pendingStoreKey]) { throw new Exception('We should not be expecting a condition'); } $this->thenMap[$this->pendingStoreKey] = false; $this->elseMap[$this->pendingStoreKey] = false; --$this->braceDepthMap[$this->pendingStoreKey]; array_pop($this->storeKeysStack); $this->pendingStoreKey = null; } } public function currentCondition(): ?string { return $this->currentCondition; } public function currentOnlyIf(): ?string { return $this->currentOnlyIf; } public function currentOnlyIfNot(): ?string { return $this->currentOnlyIfNot; } private function getUnusedBranchStoreKey(): string { $storeKeyValue = 'storeKey-' . $this->branchStoreKeyCounter; ++$this->branchStoreKeyCounter; return $storeKeyValue; } } PK!Elibraries/vendor/PhpSpreadsheet/Calculation/Engine/Operands/index.phpnu[value = $structuredReference; } public static function fromParser(string $formula, int $index, array $matches): self { $val = $matches[0]; $srCount = substr_count($val, self::OPEN_BRACE) - substr_count($val, self::CLOSE_BRACE); while ($srCount > 0) { $srIndex = strlen($val); $srStringRemainder = substr($formula, $index + $srIndex); $closingPos = strpos($srStringRemainder, self::CLOSE_BRACE); if ($closingPos === false) { throw new Exception("Formula Error: No closing ']' to match opening '['"); } $srStringRemainder = substr($srStringRemainder, 0, $closingPos + 1); --$srCount; if (str_contains($srStringRemainder, self::OPEN_BRACE)) { ++$srCount; } $val .= $srStringRemainder; } return new self($val); } /** * @throws Exception * @throws \TablePress\PhpOffice\PhpSpreadsheet\Exception */ public function parse(Cell $cell): string { $this->getTableStructure($cell); $cellRange = ($this->isRowReference()) ? $this->getRowReference($cell) : $this->getColumnReference(); $sheetName = ''; $worksheet = $this->table->getWorksheet(); if ($worksheet !== null && $worksheet !== $cell->getWorksheet()) { $sheetName = "'" . $worksheet->getTitle() . "'!"; } return $sheetName . $cellRange; } private function isRowReference(): bool { return str_contains($this->value, '[@') || str_contains($this->value, '[' . self::ITEM_SPECIFIER_THIS_ROW . ']'); } /** * @throws Exception * @throws \TablePress\PhpOffice\PhpSpreadsheet\Exception */ private function getTableStructure(Cell $cell): void { preg_match(self::TABLE_REFERENCE, $this->value, $matches); $this->tableName = $matches[1]; $this->table = ($this->tableName === '') ? $this->getTableForCell($cell) : $this->getTableByName($cell); $this->reference = $matches[2]; $tableRange = Coordinate::getRangeBoundaries($this->table->getRange()); $this->headersRow = ($this->table->getShowHeaderRow()) ? (int) $tableRange[0][1] : null; $this->firstDataRow = ($this->table->getShowHeaderRow()) ? (int) $tableRange[0][1] + 1 : $tableRange[0][1]; $this->totalsRow = ($this->table->getShowTotalsRow()) ? (int) $tableRange[1][1] : null; $this->lastDataRow = ($this->table->getShowTotalsRow()) ? (int) $tableRange[1][1] - 1 : $tableRange[1][1]; $cellParam = $cell; $worksheet = $this->table->getWorksheet(); if ($worksheet !== null && $worksheet !== $cell->getWorksheet()) { $cellParam = $worksheet->getCell('A1'); } $this->columns = $this->getColumns($cellParam, $tableRange); } /** * @throws Exception * @throws \TablePress\PhpOffice\PhpSpreadsheet\Exception */ private function getTableForCell(Cell $cell): Table { $tables = $cell->getWorksheet()->getTableCollection(); foreach ($tables as $table) { /** @var Table $table */ $range = $table->getRange(); if ($cell->isInRange($range) === true) { $this->tableName = $table->getName(); return $table; } } throw new Exception('Table for Structured Reference cannot be identified'); } /** * @throws Exception * @throws \TablePress\PhpOffice\PhpSpreadsheet\Exception */ private function getTableByName(Cell $cell): Table { $table = $cell->getWorksheet()->getTableByName($this->tableName); if ($table === null) { $spreadsheet = $cell->getWorksheet()->getParent(); if ($spreadsheet !== null) { $table = $spreadsheet->getTableByName($this->tableName); } } if ($table === null) { throw new Exception("Table {$this->tableName} for Structured Reference cannot be located"); } return $table; } private function getColumns(Cell $cell, array $tableRange): array { $worksheet = $cell->getWorksheet(); $cellReference = $cell->getCoordinate(); $columns = []; $lastColumn = ++$tableRange[1][0]; for ($column = $tableRange[0][0]; $column !== $lastColumn; ++$column) { $columns[$column] = $worksheet ->getCell($column . ($this->headersRow ?? ($this->firstDataRow - 1))) ->getCalculatedValue(); } $worksheet->getCell($cellReference); return $columns; } private function getRowReference(Cell $cell): string { $reference = str_replace("\u{a0}", ' ', $this->reference); /** @var string $reference */ $reference = str_replace('[' . self::ITEM_SPECIFIER_THIS_ROW . '],', '', $reference); foreach ($this->columns as $columnId => $columnName) { $columnName = str_replace("\u{a0}", ' ', $columnName); $reference = $this->adjustRowReference($columnName, $reference, $cell, $columnId); } return $this->validateParsedReference(trim($reference, '[]@, ')); } private function adjustRowReference(string $columnName, string $reference, Cell $cell, string $columnId): string { if ($columnName !== '') { $cellReference = $columnId . $cell->getRow(); $pattern1 = '/\[' . preg_quote($columnName, '/') . '\]/miu'; $pattern2 = '/@' . preg_quote($columnName, '/') . '/miu'; if (preg_match($pattern1, $reference) === 1) { $reference = preg_replace($pattern1, $cellReference, $reference); } elseif (preg_match($pattern2, $reference) === 1) { $reference = preg_replace($pattern2, $cellReference, $reference); } /** @var string $reference */ } return $reference; } /** * @throws Exception * @throws \TablePress\PhpOffice\PhpSpreadsheet\Exception */ private function getColumnReference(): string { $reference = str_replace("\u{a0}", ' ', $this->reference); $startRow = ($this->totalsRow === null) ? $this->lastDataRow : $this->totalsRow; $endRow = ($this->headersRow === null) ? $this->firstDataRow : $this->headersRow; [$startRow, $endRow] = $this->getRowsForColumnReference($reference, $startRow, $endRow); $reference = $this->getColumnsForColumnReference($reference, $startRow, $endRow); $reference = trim($reference, '[]@, '); if (substr_count($reference, ':') > 1) { $cells = explode(':', $reference); $firstCell = array_shift($cells); $lastCell = array_pop($cells); $reference = "{$firstCell}:{$lastCell}"; } return $this->validateParsedReference($reference); } /** * @throws Exception * @throws \TablePress\PhpOffice\PhpSpreadsheet\Exception */ private function validateParsedReference(string $reference): string { if (preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . ':' . Calculation::CALCULATION_REGEXP_CELLREF . '$/miu', $reference) !== 1) { if (preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/miu', $reference) !== 1) { throw new Exception( "Invalid Structured Reference {$this->reference} {$reference}", Exception::CALCULATION_ENGINE_PUSH_TO_STACK ); } } return $reference; } private function fullData(int $startRow, int $endRow): string { $columns = array_keys($this->columns); $firstColumn = array_shift($columns); $lastColumn = (empty($columns)) ? $firstColumn : array_pop($columns); return "{$firstColumn}{$startRow}:{$lastColumn}{$endRow}"; } private function getMinimumRow(string $reference): int { switch ($reference) { case self::ITEM_SPECIFIER_ALL: case self::ITEM_SPECIFIER_HEADERS: return $this->headersRow ?? $this->firstDataRow; case self::ITEM_SPECIFIER_DATA: return $this->firstDataRow; case self::ITEM_SPECIFIER_TOTALS: return $this->totalsRow ?? $this->lastDataRow; default: return $this->headersRow ?? $this->firstDataRow; } } private function getMaximumRow(string $reference): int { switch ($reference) { case self::ITEM_SPECIFIER_HEADERS: return $this->headersRow ?? $this->firstDataRow; case self::ITEM_SPECIFIER_DATA: return $this->lastDataRow; case self::ITEM_SPECIFIER_ALL: case self::ITEM_SPECIFIER_TOTALS: return $this->totalsRow ?? $this->lastDataRow; default: return $this->totalsRow ?? $this->lastDataRow; } } public function value(): string { return $this->value; } /** * @return array */ private function getRowsForColumnReference(string &$reference, int $startRow, int $endRow): array { $rowsSelected = false; foreach (self::ITEM_SPECIFIER_ROWS_SET as $rowReference) { $pattern = '/\[' . $rowReference . '\]/mui'; if (preg_match($pattern, $reference) === 1) { if (($rowReference === self::ITEM_SPECIFIER_HEADERS) && ($this->table->getShowHeaderRow() === false)) { throw new Exception( 'Table Headers are Hidden, and should not be Referenced', Exception::CALCULATION_ENGINE_PUSH_TO_STACK ); } $rowsSelected = true; $startRow = min($startRow, $this->getMinimumRow($rowReference)); $endRow = max($endRow, $this->getMaximumRow($rowReference)); $reference = preg_replace($pattern, '', $reference) ?? ''; } } if ($rowsSelected === false) { // If there isn't any Special Item Identifier specified, then the selection defaults to data rows only. $startRow = $this->firstDataRow; $endRow = $this->lastDataRow; } return [$startRow, $endRow]; } private function getColumnsForColumnReference(string $reference, int $startRow, int $endRow): string { $columnsSelected = false; foreach ($this->columns as $columnId => $columnName) { $columnName = str_replace("\u{a0}", ' ', $columnName ?? ''); $cellFrom = "{$columnId}{$startRow}"; $cellTo = "{$columnId}{$endRow}"; $cellReference = ($cellFrom === $cellTo) ? $cellFrom : "{$cellFrom}:{$cellTo}"; $pattern = '/\[' . preg_quote($columnName, '/') . '\]/mui'; if (preg_match($pattern, $reference) === 1) { $columnsSelected = true; $reference = preg_replace($pattern, $cellReference, $reference); } /** @var string $reference */ } if ($columnsSelected === false) { return $this->fullData($startRow, $endRow); } return $reference; } public function __toString(): string { return $this->value; } } PK!1  =libraries/vendor/PhpSpreadsheet/Calculation/Engine/Logger.phpnu[cellStack = $stack; } /** * Enable/Disable Calculation engine logging. */ public function setWriteDebugLog(bool $writeDebugLog): void { $this->writeDebugLog = $writeDebugLog; } /** * Return whether calculation engine logging is enabled or disabled. */ public function getWriteDebugLog(): bool { return $this->writeDebugLog; } /** * Enable/Disable echoing of debug log information. */ public function setEchoDebugLog(bool $echoDebugLog): void { $this->echoDebugLog = $echoDebugLog; } /** * Return whether echoing of debug log information is enabled or disabled. */ public function getEchoDebugLog(): bool { return $this->echoDebugLog; } /** * Write an entry to the calculation engine debug log. * @param mixed ...$args */ public function writeDebugLog(string $message, ...$args): void { // Only write the debug log if logging is enabled if ($this->writeDebugLog) { $message = sprintf($message, ...$args); //* @phpstan-ignore-line $cellReference = implode(' -> ', $this->cellStack->showStack()); if ($this->echoDebugLog) { echo $cellReference, ($this->cellStack->count() > 0 ? ' => ' : ''), $message, PHP_EOL; } $this->debugLog[] = $cellReference . ($this->cellStack->count() > 0 ? ' => ' : '') . $message; } } /** * Write a series of entries to the calculation engine debug log. * * @param string[] $args */ public function mergeDebugLog(array $args): void { if ($this->writeDebugLog) { foreach ($args as $entry) { $this->writeDebugLog($entry); } } } /** * Clear the calculation engine debug log. */ public function clearLog(): void { $this->debugLog = []; } /** * Return the calculation engine debug log. * * @return string[] */ public function getLog(): array { return $this->debugLog; } } PK!,,Klibraries/vendor/PhpSpreadsheet/Calculation/Engine/CyclicReferenceStack.phpnu[stack); } /** * Push a new entry onto the stack. * @param mixed $value */ public function push($value): void { $this->stack[$value] = $value; } /** * Pop the last entry from the stack. * @return mixed */ public function pop() { return array_pop($this->stack); } /** * Test to see if a specified entry exists on the stack. * * @param mixed $value The value to test */ public function onStack($value): bool { return isset($this->stack[$value]); } /** * Clear the stack. */ public function clear(): void { $this->stack = []; } /** * Return an array of all entries on the stack. * * @return mixed[] */ public function showStack(): array { return $this->stack; } } PK!<libraries/vendor/PhpSpreadsheet/Calculation/Engine/index.phpnu[[-+])? *\% *(?[-+])? *(?[0-9]+\.?[0-9*]*(?:E[-+]?[0-9]*)?) *)|(?: *(?[-+])? *(?[0-9]+\.?[0-9]*(?:E[-+]?[0-9]*)?) *\% *))$~i'; // preg_quoted string for major currency symbols, with a %s for locale currency private const CURRENCY_CONVERSION_LIST = '\$€£¥%s'; private const STRING_CONVERSION_LIST = [ [self::class, 'convertToNumberIfNumeric'], [self::class, 'convertToNumberIfFraction'], [self::class, 'convertToNumberIfPercent'], [self::class, 'convertToNumberIfCurrency'], ]; /** * Identify whether a string contains a formatted numeric value, * and convert it to a numeric if it is. * * @param string $operand string value to test */ public static function convertToNumberIfFormatted(string &$operand): bool { foreach (self::STRING_CONVERSION_LIST as $conversionMethod) { if ($conversionMethod($operand) === true) { return true; } } return false; } /** * Identify whether a string contains a numeric value, * and convert it to a numeric if it is. * * @param float|string $operand string value to test */ public static function convertToNumberIfNumeric(&$operand): bool { $thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator(), '/'); $value = preg_replace(['/(\d)' . $thousandsSeparator . '(\d)/u', '/([+-])\s+(\d)/u'], ['$1$2', '$1$2'], trim("$operand")); $decimalSeparator = preg_quote(StringHelper::getDecimalSeparator(), '/'); $value = preg_replace(['/(\d)' . $decimalSeparator . '(\d)/u', '/([+-])\s+(\d)/u'], ['$1.$2', '$1$2'], $value ?? ''); if (is_numeric($value)) { $operand = (float) $value; return true; } return false; } /** * Identify whether a string contains a fractional numeric value, * and convert it to a numeric if it is. * * @param string $operand string value to test */ public static function convertToNumberIfFraction(string &$operand): bool { if (preg_match(self::STRING_REGEXP_FRACTION, $operand, $match)) { $sign = ($match[1] === '-') ? '-' : '+'; $wholePart = ($match[3] === '') ? '' : ($sign . $match[3]); $fractionFormula = '=' . $wholePart . $sign . $match[4]; /** @var string */ $operandx = Calculation::getInstance()->_calculateFormulaValue($fractionFormula); $operand = $operandx; return true; } return false; } /** * Identify whether a string contains a percentage, and if so, * convert it to a numeric. * * @param float|string $operand string value to test */ public static function convertToNumberIfPercent(&$operand): bool { $thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator(), '/'); $value = preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', trim("$operand")); $decimalSeparator = preg_quote(StringHelper::getDecimalSeparator(), '/'); $value = preg_replace(['/(\d)' . $decimalSeparator . '(\d)/u', '/([+-])\s+(\d)/u'], ['$1.$2', '$1$2'], $value ?? ''); $match = []; if ($value !== null && preg_match(self::STRING_REGEXP_PERCENT, $value, $match, PREG_UNMATCHED_AS_NULL)) { //Calculate the percentage $sign = ($match['PrefixedSign'] ?? $match['PrefixedSign2'] ?? $match['PostfixedSign']) ?? ''; $operand = (float) ($sign . ($match['PostfixedValue'] ?? $match['PrefixedValue'])) / 100; return true; } return false; } /** * Identify whether a string contains a currency value, and if so, * convert it to a numeric. * * @param float|string $operand string value to test */ public static function convertToNumberIfCurrency(&$operand): bool { $currencyRegexp = self::currencyMatcherRegexp(); $thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator(), '/'); $value = preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', "$operand"); $match = []; if ($value !== null && preg_match($currencyRegexp, $value, $match, PREG_UNMATCHED_AS_NULL)) { //Determine the sign $sign = ($match['PrefixedSign'] ?? $match['PrefixedSign2'] ?? $match['PostfixedSign']) ?? ''; $decimalSeparator = StringHelper::getDecimalSeparator(); //Cast to a float $intermediate = (string) ($match['PostfixedValue'] ?? $match['PrefixedValue']); $intermediate = str_replace($decimalSeparator, '.', $intermediate); if (is_numeric($intermediate)) { $operand = (float) ($sign . str_replace($decimalSeparator, '.', $intermediate)); return true; } } return false; } public static function currencyMatcherRegexp(): string { $currencyCodes = sprintf(self::CURRENCY_CONVERSION_LIST, preg_quote(StringHelper::getCurrencyCode(), '/')); $decimalSeparator = preg_quote(StringHelper::getDecimalSeparator(), '/'); return '~^(?:(?: *(?[-+])? *(?[' . $currencyCodes . ']) *(?[-+])? *(?[0-9]+[' . $decimalSeparator . ']?[0-9*]*(?:E[-+]?[0-9]*)?) *)|(?: *(?[-+])? *(?[0-9]+' . $decimalSeparator . '?[0-9]*(?:E[-+]?[0-9]*)?) *(?[' . $currencyCodes . ']) *))$~ui'; } } PK!*:Mlibraries/vendor/PhpSpreadsheet/Calculation/Engine/ArrayArgumentProcessor.phpnu[hasArrayArgument() === false) { return [$method(...$arguments)]; } if (self::$arrayArgumentHelper->arrayArguments() === 1) { $nthArgument = self::$arrayArgumentHelper->getFirstArrayArgumentNumber(); return self::evaluateNthArgumentAsArray($method, $nthArgument, ...$arguments); } $singleRowVectorIndex = self::$arrayArgumentHelper->getSingleRowVector(); $singleColumnVectorIndex = self::$arrayArgumentHelper->getSingleColumnVector(); if ($singleRowVectorIndex !== null && $singleColumnVectorIndex !== null) { // Basic logic for a single row vector and a single column vector return self::evaluateVectorPair($method, $singleRowVectorIndex, $singleColumnVectorIndex, ...$arguments); } $matrixPair = self::$arrayArgumentHelper->getMatrixPair(); if ($matrixPair !== []) { if ( (self::$arrayArgumentHelper->isVector($matrixPair[0]) === true && self::$arrayArgumentHelper->isVector($matrixPair[1]) === false) || (self::$arrayArgumentHelper->isVector($matrixPair[0]) === false && self::$arrayArgumentHelper->isVector($matrixPair[1]) === true) ) { // Logic for a matrix and a vector (row or column) return self::evaluateVectorMatrixPair($method, $matrixPair, ...$arguments); } // Logic for matrix/matrix, column vector/column vector or row vector/row vector return self::evaluateMatrixPair($method, $matrixPair, ...$arguments); } // Still need to work out the logic for more than two array arguments, // For the moment, we're throwing an Exception when we initialise the ArrayArgumentHelper return ['#VALUE!']; } /** * @param mixed ...$arguments */ private static function evaluateVectorMatrixPair(callable $method, array $matrixIndexes, ...$arguments): array { $matrix2 = array_pop($matrixIndexes); /** @var array $matrixValues2 */ $matrixValues2 = $arguments[$matrix2]; $matrix1 = array_pop($matrixIndexes); /** @var array $matrixValues1 */ $matrixValues1 = $arguments[$matrix1]; $rows = min(array_map([self::$arrayArgumentHelper, 'rowCount'], [$matrix1, $matrix2])); $columns = min(array_map([self::$arrayArgumentHelper, 'columnCount'], [$matrix1, $matrix2])); if ($rows === 1) { $rows = max(array_map([self::$arrayArgumentHelper, 'rowCount'], [$matrix1, $matrix2])); } if ($columns === 1) { $columns = max(array_map([self::$arrayArgumentHelper, 'columnCount'], [$matrix1, $matrix2])); } $result = []; for ($rowIndex = 0; $rowIndex < $rows; ++$rowIndex) { for ($columnIndex = 0; $columnIndex < $columns; ++$columnIndex) { $rowIndex1 = self::$arrayArgumentHelper->isRowVector($matrix1) ? 0 : $rowIndex; $columnIndex1 = self::$arrayArgumentHelper->isColumnVector($matrix1) ? 0 : $columnIndex; $value1 = $matrixValues1[$rowIndex1][$columnIndex1]; $rowIndex2 = self::$arrayArgumentHelper->isRowVector($matrix2) ? 0 : $rowIndex; $columnIndex2 = self::$arrayArgumentHelper->isColumnVector($matrix2) ? 0 : $columnIndex; $value2 = $matrixValues2[$rowIndex2][$columnIndex2]; $arguments[$matrix1] = $value1; $arguments[$matrix2] = $value2; $result[$rowIndex][$columnIndex] = $method(...$arguments); } } return $result; } /** * @param mixed ...$arguments */ private static function evaluateMatrixPair(callable $method, array $matrixIndexes, ...$arguments): array { $matrix2 = array_pop($matrixIndexes); /** @var array $matrixValues2 */ $matrixValues2 = $arguments[$matrix2]; $matrix1 = array_pop($matrixIndexes); /** @var array $matrixValues1 */ $matrixValues1 = $arguments[$matrix1]; $result = []; foreach ($matrixValues1 as $rowIndex => $row) { foreach ($row as $columnIndex => $value1) { if (isset($matrixValues2[$rowIndex][$columnIndex]) === false) { continue; } $value2 = $matrixValues2[$rowIndex][$columnIndex]; $arguments[$matrix1] = $value1; $arguments[$matrix2] = $value2; $result[$rowIndex][$columnIndex] = $method(...$arguments); } } return $result; } /** * @param mixed ...$arguments */ private static function evaluateVectorPair(callable $method, int $rowIndex, int $columnIndex, ...$arguments): array { $rowVector = Functions::flattenArray($arguments[$rowIndex]); $columnVector = Functions::flattenArray($arguments[$columnIndex]); $result = []; foreach ($columnVector as $column) { $rowResults = []; foreach ($rowVector as $row) { $arguments[$rowIndex] = $row; $arguments[$columnIndex] = $column; $rowResults[] = $method(...$arguments); } $result[] = $rowResults; } return $result; } /** * Note, offset is from 1 (for the first argument) rather than from 0. * @param mixed ...$arguments */ private static function evaluateNthArgumentAsArray(callable $method, int $nthArgument, ...$arguments): array { $values = array_slice($arguments, $nthArgument - 1, 1); /** @var array $values */ $values = array_pop($values); $result = []; foreach ($values as $value) { $arguments[$nthArgument - 1] = $value; $result[] = $method(...$arguments); } return $result; } } PK!Jlibraries/vendor/PhpSpreadsheet/Calculation/Engine/ArrayArgumentHelper.phpnu[indexStart = (int) array_shift($keys); $this->rows = $this->rows($arguments); $this->columns = $this->columns($arguments); $this->argumentCount = count($arguments); $this->arguments = $this->flattenSingleCellArrays($arguments, $this->rows, $this->columns); $this->rows = $this->rows($arguments); $this->columns = $this->columns($arguments); if ($this->arrayArguments() > 2) { throw new Exception('Formulae with more than two array arguments are not supported'); } } public function arguments(): array { return $this->arguments; } public function hasArrayArgument(): bool { return $this->arrayArguments() > 0; } public function getFirstArrayArgumentNumber(): int { $rowArrays = $this->filterArray($this->rows); $columnArrays = $this->filterArray($this->columns); for ($index = $this->indexStart; $index < $this->argumentCount; ++$index) { if (isset($rowArrays[$index]) || isset($columnArrays[$index])) { return ++$index; } } return 0; } public function getSingleRowVector(): ?int { $rowVectors = $this->getRowVectors(); return count($rowVectors) === 1 ? array_pop($rowVectors) : null; } private function getRowVectors(): array { $rowVectors = []; for ($index = $this->indexStart; $index < ($this->indexStart + $this->argumentCount); ++$index) { if ($this->rows[$index] === 1 && $this->columns[$index] > 1) { $rowVectors[] = $index; } } return $rowVectors; } public function getSingleColumnVector(): ?int { $columnVectors = $this->getColumnVectors(); return count($columnVectors) === 1 ? array_pop($columnVectors) : null; } private function getColumnVectors(): array { $columnVectors = []; for ($index = $this->indexStart; $index < ($this->indexStart + $this->argumentCount); ++$index) { if ($this->rows[$index] > 1 && $this->columns[$index] === 1) { $columnVectors[] = $index; } } return $columnVectors; } public function getMatrixPair(): array { for ($i = $this->indexStart; $i < ($this->indexStart + $this->argumentCount - 1); ++$i) { for ($j = $i + 1; $j < $this->argumentCount; ++$j) { if (isset($this->rows[$i], $this->rows[$j])) { return [$i, $j]; } } } return []; } public function isVector(int $argument): bool { return $this->rows[$argument] === 1 || $this->columns[$argument] === 1; } public function isRowVector(int $argument): bool { return $this->rows[$argument] === 1; } public function isColumnVector(int $argument): bool { return $this->columns[$argument] === 1; } public function rowCount(int $argument): int { return $this->rows[$argument]; } public function columnCount(int $argument): int { return $this->columns[$argument]; } private function rows(array $arguments): array { return array_map( fn ($argument): int => is_countable($argument) ? count($argument) : 1, $arguments ); } private function columns(array $arguments): array { return array_map( fn ($argument): int => is_array($argument) && is_array($argument[array_keys($argument)[0]]) ? count($argument[array_keys($argument)[0]]) : 1, $arguments ); } public function arrayArguments(): int { $count = 0; foreach (array_keys($this->arguments) as $argument) { if ($this->rows[$argument] > 1 || $this->columns[$argument] > 1) { ++$count; } } return $count; } private function flattenSingleCellArrays(array $arguments, array $rows, array $columns): array { foreach ($arguments as $index => $argument) { if ($rows[$index] === 1 && $columns[$index] === 1) { while (is_array($argument)) { $argument = array_pop($argument); } $arguments[$index] = $argument; } } return $arguments; } private function filterArray(array $array): array { return array_filter( $array, fn ($value): bool => $value > 1 ); } } PK!libraries/vendor/PhpSpreadsheet/Calculation/Internal/index.phpnu[getWorksheet(); [$referenceWorksheetName, $referenceCellCoordinate] = Worksheet::extractSheetTitle($cellReference, true, true); if (preg_match('/^([$]?[a-z]{1,3})([$]?([0-9]{1,7})):([$]?[a-z]{1,3})([$]?([0-9]{1,7}))$/i', "$referenceCellCoordinate", $matches) === 1) { $ourRow = $cell->getRow(); $firstRow = (int) $matches[3]; $lastRow = (int) $matches[6]; if ($ourRow < $firstRow || $ourRow > $lastRow || $matches[1] !== $matches[4]) { return ExcelError::VALUE(); } $referenceCellCoordinate = $matches[1] . $ourRow; } $referenceCell = ($referenceWorksheetName === '') ? $worksheet->getCell((string) $referenceCellCoordinate) : $worksheet->getParentOrThrow() ->getSheetByNameOrThrow((string) $referenceWorksheetName) ->getCell((string) $referenceCellCoordinate); $result = $referenceCell->getCalculatedValue(); while (is_array($result)) { $result = array_shift($result); } return $result; } /** * @return mixed[]|string */ public static function anchorArray(string $cellReference, Cell $cell) { //$coordinate = $cell->getCoordinate(); $worksheet = $cell->getWorksheet(); [$referenceWorksheetName, $referenceCellCoordinate] = Worksheet::extractSheetTitle($cellReference, true, true); $referenceCell = ($referenceWorksheetName === '') ? $worksheet->getCell((string) $referenceCellCoordinate) : $worksheet->getParentOrThrow() ->getSheetByNameOrThrow((string) $referenceWorksheetName) ->getCell((string) $referenceCellCoordinate); // We should always use the sizing for the array formula range from the referenced cell formula //$referenceRange = null; /*if ($referenceCell->isFormula() && $referenceCell->isArrayFormula()) { $referenceRange = $referenceCell->arrayFormulaRange(); }*/ $calcEngine = Calculation::getInstance($worksheet->getParent()); $result = $calcEngine->calculateCellValue($referenceCell, false); if (!is_array($result)) { $result = ExcelError::REF(); } // Ensure that our array result dimensions match the specified array formula range dimensions, // from the referenced cell, expanding or shrinking it as necessary. /*$result = Functions::resizeMatrix( $result, ...Coordinate::rangeDimension($referenceRange ?? $coordinate) );*/ // Set the result for our target cell (with spillage) // But if we do write it, we get problems with #SPILL! Errors if the spreadsheet is saved // TODO How are we going to identify and handle a #SPILL! or a #CALC! error? // IOFactory::setLoading(true); // $worksheet->fromArray( // $result, // null, // $coordinate, // true // ); // IOFactory::setLoading(true); // Calculate the array formula range that we should set for our target, based on our target cell coordinate // [$col, $row] = Coordinate::indexesFromString($coordinate); // $row += count($result) - 1; // $col = Coordinate::stringFromColumnIndex($col + count($result[0]) - 1); // $arrayFormulaRange = "{$coordinate}:{$col}{$row}"; // $formulaAttributes = ['t' => 'array', 'ref' => $arrayFormulaRange]; // Using fromArray() would reset the value for this cell with the calculation result // as well as updating the spillage cells, // so we need to restore this cell to its formula value, attributes, and datatype // $cell = $worksheet->getCell($coordinate); // $cell->setValueExplicit($value, DataType::TYPE_FORMULA, true, $arrayFormulaRange); // $cell->setFormulaAttributes($formulaAttributes); // $cell->updateInCollection(); return $result; } } PK!!Y'((?libraries/vendor/PhpSpreadsheet/Calculation/locale/nl/functionsnu[############################################################ ## ## PhpSpreadsheet - function name translations ## ## Nederlands (Dutch) ## ############################################################ ## ## Kubusfuncties (Cube Functions) ## CUBEKPIMEMBER = KUBUSKPILID CUBEMEMBER = KUBUSLID CUBEMEMBERPROPERTY = KUBUSLIDEIGENSCHAP CUBERANKEDMEMBER = KUBUSGERANGSCHIKTLID CUBESET = KUBUSSET CUBESETCOUNT = KUBUSSETAANTAL CUBEVALUE = KUBUSWAARDE ## ## Databasefuncties (Database Functions) ## DAVERAGE = DBGEMIDDELDE DCOUNT = DBAANTAL DCOUNTA = DBAANTALC DGET = DBLEZEN DMAX = DBMAX DMIN = DBMIN DPRODUCT = DBPRODUCT DSTDEV = DBSTDEV DSTDEVP = DBSTDEVP DSUM = DBSOM DVAR = DBVAR DVARP = DBVARP ## ## Datum- en tijdfuncties (Date & Time Functions) ## DATE = DATUM DATESTRING = DATUMNOTATIE DATEVALUE = DATUMWAARDE DAY = DAG DAYS = DAGEN DAYS360 = DAGEN360 EDATE = ZELFDE.DAG EOMONTH = LAATSTE.DAG HOUR = UUR ISOWEEKNUM = ISO.WEEKNUMMER MINUTE = MINUUT MONTH = MAAND NETWORKDAYS = NETTO.WERKDAGEN NETWORKDAYS.INTL = NETWERKDAGEN.INTL NOW = NU SECOND = SECONDE THAIDAYOFWEEK = THAIS.WEEKDAG THAIMONTHOFYEAR = THAIS.MAAND.VAN.JAAR THAIYEAR = THAIS.JAAR TIME = TIJD TIMEVALUE = TIJDWAARDE TODAY = VANDAAG WEEKDAY = WEEKDAG WEEKNUM = WEEKNUMMER WORKDAY = WERKDAG WORKDAY.INTL = WERKDAG.INTL YEAR = JAAR YEARFRAC = JAAR.DEEL ## ## Technische functies (Engineering Functions) ## BESSELI = BESSEL.I BESSELJ = BESSEL.J BESSELK = BESSEL.K BESSELY = BESSEL.Y BIN2DEC = BIN.N.DEC BIN2HEX = BIN.N.HEX BIN2OCT = BIN.N.OCT BITAND = BIT.EN BITLSHIFT = BIT.VERSCHUIF.LINKS BITOR = BIT.OF BITRSHIFT = BIT.VERSCHUIF.RECHTS BITXOR = BIT.EX.OF COMPLEX = COMPLEX CONVERT = CONVERTEREN DEC2BIN = DEC.N.BIN DEC2HEX = DEC.N.HEX DEC2OCT = DEC.N.OCT DELTA = DELTA ERF = FOUTFUNCTIE ERF.PRECISE = FOUTFUNCTIE.NAUWKEURIG ERFC = FOUT.COMPLEMENT ERFC.PRECISE = FOUT.COMPLEMENT.NAUWKEURIG GESTEP = GROTER.DAN HEX2BIN = HEX.N.BIN HEX2DEC = HEX.N.DEC HEX2OCT = HEX.N.OCT IMABS = C.ABS IMAGINARY = C.IM.DEEL IMARGUMENT = C.ARGUMENT IMCONJUGATE = C.TOEGEVOEGD IMCOS = C.COS IMCOSH = C.COSH IMCOT = C.COT IMCSC = C.COSEC IMCSCH = C.COSECH IMDIV = C.QUOTIENT IMEXP = C.EXP IMLN = C.LN IMLOG10 = C.LOG10 IMLOG2 = C.LOG2 IMPOWER = C.MACHT IMPRODUCT = C.PRODUCT IMREAL = C.REEEL.DEEL IMSEC = C.SEC IMSECH = C.SECH IMSIN = C.SIN IMSINH = C.SINH IMSQRT = C.WORTEL IMSUB = C.VERSCHIL IMSUM = C.SOM IMTAN = C.TAN OCT2BIN = OCT.N.BIN OCT2DEC = OCT.N.DEC OCT2HEX = OCT.N.HEX ## ## Financiële functies (Financial Functions) ## ACCRINT = SAMENG.RENTE ACCRINTM = SAMENG.RENTE.V AMORDEGRC = AMORDEGRC AMORLINC = AMORLINC COUPDAYBS = COUP.DAGEN.BB COUPDAYS = COUP.DAGEN COUPDAYSNC = COUP.DAGEN.VV COUPNCD = COUP.DATUM.NB COUPNUM = COUP.AANTAL COUPPCD = COUP.DATUM.VB CUMIPMT = CUM.RENTE CUMPRINC = CUM.HOOFDSOM DB = DB DDB = DDB DISC = DISCONTO DOLLARDE = EURO.DE DOLLARFR = EURO.BR DURATION = DUUR EFFECT = EFFECT.RENTE FV = TW FVSCHEDULE = TOEK.WAARDE2 INTRATE = RENTEPERCENTAGE IPMT = IBET IRR = IR ISPMT = ISBET MDURATION = AANG.DUUR MIRR = GIR NOMINAL = NOMINALE.RENTE NPER = NPER NPV = NHW ODDFPRICE = AFW.ET.PRIJS ODDFYIELD = AFW.ET.REND ODDLPRICE = AFW.LT.PRIJS ODDLYIELD = AFW.LT.REND PDURATION = PDUUR PMT = BET PPMT = PBET PRICE = PRIJS.NOM PRICEDISC = PRIJS.DISCONTO PRICEMAT = PRIJS.VERVALDAG PV = HW RATE = RENTE RECEIVED = OPBRENGST RRI = RRI SLN = LIN.AFSCHR SYD = SYD TBILLEQ = SCHATK.OBL TBILLPRICE = SCHATK.PRIJS TBILLYIELD = SCHATK.REND VDB = VDB XIRR = IR.SCHEMA XNPV = NHW2 YIELD = RENDEMENT YIELDDISC = REND.DISCONTO YIELDMAT = REND.VERVAL ## ## Informatiefuncties (Information Functions) ## CELL = CEL ERROR.TYPE = TYPE.FOUT INFO = INFO ISBLANK = ISLEEG ISERR = ISFOUT2 ISERROR = ISFOUT ISEVEN = IS.EVEN ISFORMULA = ISFORMULE ISLOGICAL = ISLOGISCH ISNA = ISNB ISNONTEXT = ISGEENTEKST ISNUMBER = ISGETAL ISODD = IS.ONEVEN ISREF = ISVERWIJZING ISTEXT = ISTEKST N = N NA = NB SHEET = BLAD SHEETS = BLADEN TYPE = TYPE ## ## Logische functies (Logical Functions) ## AND = EN FALSE = ONWAAR IF = ALS IFERROR = ALS.FOUT IFNA = ALS.NB IFS = ALS.VOORWAARDEN NOT = NIET OR = OF SWITCH = SCHAKELEN TRUE = WAAR XOR = EX.OF ## ## Zoek- en verwijzingsfuncties (Lookup & Reference Functions) ## ADDRESS = ADRES AREAS = BEREIKEN CHOOSE = KIEZEN COLUMN = KOLOM COLUMNS = KOLOMMEN FORMULATEXT = FORMULETEKST GETPIVOTDATA = DRAAITABEL.OPHALEN HLOOKUP = HORIZ.ZOEKEN HYPERLINK = HYPERLINK INDEX = INDEX INDIRECT = INDIRECT LOOKUP = ZOEKEN MATCH = VERGELIJKEN OFFSET = VERSCHUIVING ROW = RIJ ROWS = RIJEN RTD = RTG TRANSPOSE = TRANSPONEREN VLOOKUP = VERT.ZOEKEN *RC = RK ## ## Wiskundige en trigonometrische functies (Math & Trig Functions) ## ABS = ABS ACOS = BOOGCOS ACOSH = BOOGCOSH ACOT = BOOGCOT ACOTH = BOOGCOTH AGGREGATE = AGGREGAAT ARABIC = ARABISCH ASIN = BOOGSIN ASINH = BOOGSINH ATAN = BOOGTAN ATAN2 = BOOGTAN2 ATANH = BOOGTANH BASE = BASIS CEILING.MATH = AFRONDEN.BOVEN.WISK CEILING.PRECISE = AFRONDEN.BOVEN.NAUWKEURIG COMBIN = COMBINATIES COMBINA = COMBIN.A COS = COS COSH = COSH COT = COT COTH = COTH CSC = COSEC CSCH = COSECH DECIMAL = DECIMAAL DEGREES = GRADEN ECMA.CEILING = ECMA.AFRONDEN.BOVEN EVEN = EVEN EXP = EXP FACT = FACULTEIT FACTDOUBLE = DUBBELE.FACULTEIT FLOOR.MATH = AFRONDEN.BENEDEN.WISK FLOOR.PRECISE = AFRONDEN.BENEDEN.NAUWKEURIG GCD = GGD INT = INTEGER ISO.CEILING = ISO.AFRONDEN.BOVEN LCM = KGV LN = LN LOG = LOG LOG10 = LOG10 MDETERM = DETERMINANTMAT MINVERSE = INVERSEMAT MMULT = PRODUCTMAT MOD = REST MROUND = AFRONDEN.N.VEELVOUD MULTINOMIAL = MULTINOMIAAL MUNIT = EENHEIDMAT ODD = ONEVEN PI = PI POWER = MACHT PRODUCT = PRODUCT QUOTIENT = QUOTIENT RADIANS = RADIALEN RAND = ASELECT RANDBETWEEN = ASELECTTUSSEN ROMAN = ROMEINS ROUND = AFRONDEN ROUNDBAHTDOWN = BAHT.AFR.NAAR.BENEDEN ROUNDBAHTUP = BAHT.AFR.NAAR.BOVEN ROUNDDOWN = AFRONDEN.NAAR.BENEDEN ROUNDUP = AFRONDEN.NAAR.BOVEN SEC = SEC SECH = SECH SERIESSUM = SOM.MACHTREEKS SIGN = POS.NEG SIN = SIN SINH = SINH SQRT = WORTEL SQRTPI = WORTEL.PI SUBTOTAL = SUBTOTAAL SUM = SOM SUMIF = SOM.ALS SUMIFS = SOMMEN.ALS SUMPRODUCT = SOMPRODUCT SUMSQ = KWADRATENSOM SUMX2MY2 = SOM.X2MINY2 SUMX2PY2 = SOM.X2PLUSY2 SUMXMY2 = SOM.XMINY.2 TAN = TAN TANH = TANH TRUNC = GEHEEL ## ## Statistische functies (Statistical Functions) ## AVEDEV = GEM.DEVIATIE AVERAGE = GEMIDDELDE AVERAGEA = GEMIDDELDEA AVERAGEIF = GEMIDDELDE.ALS AVERAGEIFS = GEMIDDELDEN.ALS BETA.DIST = BETA.VERD BETA.INV = BETA.INV BINOM.DIST = BINOM.VERD BINOM.DIST.RANGE = BINOM.VERD.BEREIK BINOM.INV = BINOMIALE.INV CHISQ.DIST = CHIKW.VERD CHISQ.DIST.RT = CHIKW.VERD.RECHTS CHISQ.INV = CHIKW.INV CHISQ.INV.RT = CHIKW.INV.RECHTS CHISQ.TEST = CHIKW.TEST CONFIDENCE.NORM = VERTROUWELIJKHEID.NORM CONFIDENCE.T = VERTROUWELIJKHEID.T CORREL = CORRELATIE COUNT = AANTAL COUNTA = AANTALARG COUNTBLANK = AANTAL.LEGE.CELLEN COUNTIF = AANTAL.ALS COUNTIFS = AANTALLEN.ALS COVARIANCE.P = COVARIANTIE.P COVARIANCE.S = COVARIANTIE.S DEVSQ = DEV.KWAD EXPON.DIST = EXPON.VERD.N F.DIST = F.VERD F.DIST.RT = F.VERD.RECHTS F.INV = F.INV F.INV.RT = F.INV.RECHTS F.TEST = F.TEST FISHER = FISHER FISHERINV = FISHER.INV FORECAST.ETS = VOORSPELLEN.ETS FORECAST.ETS.CONFINT = VOORSPELLEN.ETS.CONFINT FORECAST.ETS.SEASONALITY = VOORSPELLEN.ETS.SEASONALITY FORECAST.ETS.STAT = FORECAST.ETS.STAT FORECAST.LINEAR = VOORSPELLEN.LINEAR FREQUENCY = INTERVAL GAMMA = GAMMA GAMMA.DIST = GAMMA.VERD.N GAMMA.INV = GAMMA.INV.N GAMMALN = GAMMA.LN GAMMALN.PRECISE = GAMMA.LN.NAUWKEURIG GAUSS = GAUSS GEOMEAN = MEETK.GEM GROWTH = GROEI HARMEAN = HARM.GEM HYPGEOM.DIST = HYPGEOM.VERD INTERCEPT = SNIJPUNT KURT = KURTOSIS LARGE = GROOTSTE LINEST = LIJNSCH LOGEST = LOGSCH LOGNORM.DIST = LOGNORM.VERD LOGNORM.INV = LOGNORM.INV MAX = MAX MAXA = MAXA MAXIFS = MAX.ALS.VOORWAARDEN MEDIAN = MEDIAAN MIN = MIN MINA = MINA MINIFS = MIN.ALS.VOORWAARDEN MODE.MULT = MODUS.MEERV MODE.SNGL = MODUS.ENKELV NEGBINOM.DIST = NEGBINOM.VERD NORM.DIST = NORM.VERD.N NORM.INV = NORM.INV.N NORM.S.DIST = NORM.S.VERD NORM.S.INV = NORM.S.INV PEARSON = PEARSON PERCENTILE.EXC = PERCENTIEL.EXC PERCENTILE.INC = PERCENTIEL.INC PERCENTRANK.EXC = PROCENTRANG.EXC PERCENTRANK.INC = PROCENTRANG.INC PERMUT = PERMUTATIES PERMUTATIONA = PERMUTATIE.A PHI = PHI POISSON.DIST = POISSON.VERD PROB = KANS QUARTILE.EXC = KWARTIEL.EXC QUARTILE.INC = KWARTIEL.INC RANK.AVG = RANG.GEMIDDELDE RANK.EQ = RANG.GELIJK RSQ = R.KWADRAAT SKEW = SCHEEFHEID SKEW.P = SCHEEFHEID.P SLOPE = RICHTING SMALL = KLEINSTE STANDARDIZE = NORMALISEREN STDEV.P = STDEV.P STDEV.S = STDEV.S STDEVA = STDEVA STDEVPA = STDEVPA STEYX = STAND.FOUT.YX T.DIST = T.DIST T.DIST.2T = T.VERD.2T T.DIST.RT = T.VERD.RECHTS T.INV = T.INV T.INV.2T = T.INV.2T T.TEST = T.TEST TREND = TREND TRIMMEAN = GETRIMD.GEM VAR.P = VAR.P VAR.S = VAR.S VARA = VARA VARPA = VARPA WEIBULL.DIST = WEIBULL.VERD Z.TEST = Z.TEST ## ## Tekstfuncties (Text Functions) ## BAHTTEXT = BAHT.TEKST CHAR = TEKEN CLEAN = WISSEN.CONTROL CODE = CODE CONCAT = TEKST.SAMENV DOLLAR = EURO EXACT = GELIJK FIND = VIND.ALLES FIXED = VAST ISTHAIDIGIT = IS.THAIS.CIJFER LEFT = LINKS LEN = LENGTE LOWER = KLEINE.LETTERS MID = DEEL NUMBERSTRING = GETALNOTATIE NUMBERVALUE = NUMERIEKE.WAARDE PHONETIC = FONETISCH PROPER = BEGINLETTERS REPLACE = VERVANGEN REPT = HERHALING RIGHT = RECHTS SEARCH = VIND.SPEC SUBSTITUTE = SUBSTITUEREN T = T TEXT = TEKST TEXTJOIN = TEKST.COMBINEREN THAIDIGIT = THAIS.CIJFER THAINUMSOUND = THAIS.GETAL.GELUID THAINUMSTRING = THAIS.GETAL.REEKS THAISTRINGLENGTH = THAIS.REEKS.LENGTE TRIM = SPATIES.WISSEN UNICHAR = UNITEKEN UNICODE = UNICODE UPPER = HOOFDLETTERS VALUE = WAARDE ## ## Webfuncties (Web Functions) ## ENCODEURL = URL.CODEREN FILTERXML = XML.FILTEREN WEBSERVICE = WEBSERVICE ## ## Compatibiliteitsfuncties (Compatibility Functions) ## BETADIST = BETAVERD BETAINV = BETAINV BINOMDIST = BINOMIALE.VERD CEILING = AFRONDEN.BOVEN CHIDIST = CHI.KWADRAAT CHIINV = CHI.KWADRAAT.INV CHITEST = CHI.TOETS CONCATENATE = TEKST.SAMENVOEGEN CONFIDENCE = BETROUWBAARHEID COVAR = COVARIANTIE CRITBINOM = CRIT.BINOM EXPONDIST = EXPON.VERD FDIST = F.VERDELING FINV = F.INVERSE FLOOR = AFRONDEN.BENEDEN FORECAST = VOORSPELLEN FTEST = F.TOETS GAMMADIST = GAMMA.VERD GAMMAINV = GAMMA.INV HYPGEOMDIST = HYPERGEO.VERD LOGINV = LOG.NORM.INV LOGNORMDIST = LOG.NORM.VERD MODE = MODUS NEGBINOMDIST = NEG.BINOM.VERD NORMDIST = NORM.VERD NORMINV = NORM.INV NORMSDIST = STAND.NORM.VERD NORMSINV = STAND.NORM.INV PERCENTILE = PERCENTIEL PERCENTRANK = PERCENT.RANG POISSON = POISSON QUARTILE = KWARTIEL RANK = RANG STDEV = STDEV STDEVP = STDEVP TDIST = T.VERD TINV = TINV TTEST = T.TOETS VAR = VAR VARP = VARP WEIBULL = WEIBULL ZTEST = Z.TOETS PK!?libraries/vendor/PhpSpreadsheet/Calculation/locale/nl/index.phpnu[>>?libraries/vendor/PhpSpreadsheet/Calculation/locale/en/uk/confignu[############################################################ ## ## PhpSpreadsheet - locale settings ## ## English-UK (English-UK) ## ############################################################ ArgumentSeparator = , ## ## (For future use) ## currencySymbol = £ ## ## Error Codes ## NULL DIV0 VALUE REF NAME NUM NA PK!?libraries/vendor/PhpSpreadsheet/Calculation/locale/en/index.phpnu[value; } /** * Set the range value. */ public function setRange(string $range): self { if (!empty($range)) { $this->value = $range; } return $this; } public function getCellsInRange(): array { $range = $this->value; if (str_starts_with($range, '=')) { $range = substr($range, 1); } return Coordinate::extractAllCellReferencesInRange($range); } } PK!Fc2libraries/vendor/PhpSpreadsheet/Cell/Hyperlink.phpnu[url = $url; $this->tooltip = $tooltip; } /** * Get URL. */ public function getUrl(): string { return $this->url; } /** * Set URL. * * @return $this */ public function setUrl(string $url) { $this->url = $url; return $this; } /** * Get tooltip. */ public function getTooltip(): string { return $this->tooltip; } /** * Set tooltip. * * @return $this */ public function setTooltip(string $tooltip) { $this->tooltip = $tooltip; return $this; } /** * Is this hyperlink internal? (to another worksheet). */ public function isInternal(): bool { return str_contains($this->url, 'sheet://'); } public function getTypeHyperlink(): string { return $this->isInternal() ? '' : 'External'; } /** * Get hash code. * * @return string Hash code */ public function getHashCode(): string { return md5( $this->url . $this->tooltip . __CLASS__ ); } } PK!PP2libraries/vendor/PhpSpreadsheet/Cell/CellRange.phpnu[ */ class CellRange implements AddressRange { protected CellAddress $from; protected CellAddress $to; public function __construct(CellAddress $from, CellAddress $to) { $this->validateFromTo($from, $to); } private function validateFromTo(CellAddress $from, CellAddress $to): void { // Identify actual top-left and bottom-right values (in case we've been given top-right and bottom-left) $firstColumn = min($from->columnId(), $to->columnId()); $firstRow = min($from->rowId(), $to->rowId()); $lastColumn = max($from->columnId(), $to->columnId()); $lastRow = max($from->rowId(), $to->rowId()); $fromWorksheet = $from->worksheet(); $toWorksheet = $to->worksheet(); $this->validateWorksheets($fromWorksheet, $toWorksheet); $this->from = $this->cellAddressWrapper($firstColumn, $firstRow, $fromWorksheet); $this->to = $this->cellAddressWrapper($lastColumn, $lastRow, $toWorksheet); } private function validateWorksheets(?Worksheet $fromWorksheet, ?Worksheet $toWorksheet): void { if ($fromWorksheet !== null && $toWorksheet !== null) { // We could simply compare worksheets rather than worksheet titles; but at some point we may introduce // support for 3d ranges; and at that point we drop this check and let the validation fall through // to the check for same workbook; but unless we check on titles, this test will also detect if the // worksheets are in different spreadsheets, and the next check will never execute or throw its // own exception. if ($fromWorksheet->getTitle() !== $toWorksheet->getTitle()) { throw new Exception('3d Cell Ranges are not supported'); } elseif ($fromWorksheet->getParent() !== $toWorksheet->getParent()) { throw new Exception('Worksheets must be in the same spreadsheet'); } } } private function cellAddressWrapper(int $column, int $row, ?Worksheet $worksheet = null): CellAddress { $cellAddress = Coordinate::stringFromColumnIndex($column) . (string) $row; return new class ($cellAddress, $worksheet) extends CellAddress { public function nextRow(int $offset = 1): CellAddress { /** @var CellAddress $result */ $result = parent::nextRow($offset); $this->rowId = $result->rowId; $this->cellAddress = $result->cellAddress; return $this; } public function previousRow(int $offset = 1): CellAddress { /** @var CellAddress $result */ $result = parent::previousRow($offset); $this->rowId = $result->rowId; $this->cellAddress = $result->cellAddress; return $this; } public function nextColumn(int $offset = 1): CellAddress { /** @var CellAddress $result */ $result = parent::nextColumn($offset); $this->columnId = $result->columnId; $this->columnName = $result->columnName; $this->cellAddress = $result->cellAddress; return $this; } public function previousColumn(int $offset = 1): CellAddress { /** @var CellAddress $result */ $result = parent::previousColumn($offset); $this->columnId = $result->columnId; $this->columnName = $result->columnName; $this->cellAddress = $result->cellAddress; return $this; } }; } public function from(): CellAddress { // Re-order from/to in case the cell addresses have been modified $this->validateFromTo($this->from, $this->to); return $this->from; } public function to(): CellAddress { // Re-order from/to in case the cell addresses have been modified $this->validateFromTo($this->from, $this->to); return $this->to; } public function __toString(): string { // Re-order from/to in case the cell addresses have been modified $this->validateFromTo($this->from, $this->to); if ($this->from->cellAddress() === $this->to->cellAddress()) { return "{$this->from->fullCellAddress()}"; } $fromAddress = $this->from->fullCellAddress(); $toAddress = $this->to->cellAddress(); return "{$fromAddress}:{$toAddress}"; } } PK!Q2,6libraries/vendor/PhpSpreadsheet/Cell/DataValidator.phpnu[hasDataValidation() || $cell->getDataValidation()->getType() === DataValidation::TYPE_NONE) { return true; } $cellValue = $cell->getValue(); $dataValidation = $cell->getDataValidation(); if (!$dataValidation->getAllowBlank() && ($cellValue === null || $cellValue === '')) { return false; } $returnValue = false; $type = $dataValidation->getType(); if ($type === DataValidation::TYPE_LIST) { $returnValue = $this->isValueInList($cell); } elseif ($type === DataValidation::TYPE_WHOLE) { if (!is_numeric($cellValue) || fmod((float) $cellValue, 1) != 0) { $returnValue = false; } else { $returnValue = $this->numericOperator($dataValidation, (int) $cellValue, $cell); } } elseif ($type === DataValidation::TYPE_DECIMAL || $type === DataValidation::TYPE_DATE || $type === DataValidation::TYPE_TIME) { if (!is_numeric($cellValue)) { $returnValue = false; } else { $returnValue = $this->numericOperator($dataValidation, (float) $cellValue, $cell); } } elseif ($type === DataValidation::TYPE_TEXTLENGTH) { $returnValue = $this->numericOperator($dataValidation, mb_strlen($cell->getValueString()), $cell); } return $returnValue; } private const TWO_FORMULAS = [DataValidation::OPERATOR_BETWEEN, DataValidation::OPERATOR_NOTBETWEEN]; /** * @param mixed $formula * @return mixed */ private static function evaluateNumericFormula($formula, Cell $cell) { if (!is_numeric($formula)) { $calculation = Calculation::getInstance($cell->getWorksheet()->getParent()); try { $formula2 = StringHelper::convertToString($formula); $result = $calculation ->calculateFormula("=$formula2", $cell->getCoordinate(), $cell); while (is_array($result)) { $result = array_pop($result); } $formula = $result; } catch (Exception $exception) { // do nothing } } return $formula; } /** * @param int|float $cellValue */ private function numericOperator(DataValidation $dataValidation, $cellValue, Cell $cell): bool { $operator = $dataValidation->getOperator(); $formula1 = self::evaluateNumericFormula( $dataValidation->getFormula1(), $cell ); $formula2 = 0; if (in_array($operator, self::TWO_FORMULAS, true)) { $formula2 = self::evaluateNumericFormula( $dataValidation->getFormula2(), $cell ); } switch ($operator) { case DataValidation::OPERATOR_BETWEEN: return $cellValue >= $formula1 && $cellValue <= $formula2; case DataValidation::OPERATOR_NOTBETWEEN: return $cellValue < $formula1 || $cellValue > $formula2; case DataValidation::OPERATOR_EQUAL: return $cellValue == $formula1; case DataValidation::OPERATOR_NOTEQUAL: return $cellValue != $formula1; case DataValidation::OPERATOR_LESSTHAN: return $cellValue < $formula1; case DataValidation::OPERATOR_LESSTHANOREQUAL: return $cellValue <= $formula1; case DataValidation::OPERATOR_GREATERTHAN: return $cellValue > $formula1; case DataValidation::OPERATOR_GREATERTHANOREQUAL: return $cellValue >= $formula1; default: return false; } } /** * Does this cell contain valid value, based on list? * * @param Cell $cell Cell to check the value */ private function isValueInList(Cell $cell): bool { $cellValueString = $cell->getValueString(); $dataValidation = $cell->getDataValidation(); $formula1 = $dataValidation->getFormula1(); if (!empty($formula1)) { // inline values list if ($formula1[0] === '"') { return in_array(strtolower($cellValueString), explode(',', strtolower(trim($formula1, '"'))), true); } $calculation = Calculation::getInstance($cell->getWorksheet()->getParent()); try { $result = $calculation->calculateFormula("=$formula1", $cell->getCoordinate(), $cell); $result = is_array($result) ? Functions::flattenArray($result) : [$result]; foreach ($result as $oneResult) { if (is_scalar($oneResult) && strcasecmp((string) $oneResult, $cellValueString) === 0) { return true; } } } catch (Exception $exception) { // do nothing } return false; } return true; } } PK!woo7libraries/vendor/PhpSpreadsheet/Cell/DataValidation.phpnu[formula1; } /** * Set Formula 1. * * @return $this */ public function setFormula1(string $formula) { $this->formula1 = $formula; return $this; } /** * Get Formula 2. */ public function getFormula2(): string { return $this->formula2; } /** * Set Formula 2. * * @return $this */ public function setFormula2(string $formula) { $this->formula2 = $formula; return $this; } /** * Get Type. */ public function getType(): string { return $this->type; } /** * Set Type. * * @return $this */ public function setType(string $type) { $this->type = $type; return $this; } /** * Get Error style. */ public function getErrorStyle(): string { return $this->errorStyle; } /** * Set Error style. * * @param string $errorStyle see self::STYLE_* * * @return $this */ public function setErrorStyle(string $errorStyle) { $this->errorStyle = $errorStyle; return $this; } /** * Get Operator. */ public function getOperator(): string { return $this->operator; } /** * Set Operator. * * @return $this */ public function setOperator(string $operator) { $this->operator = ($operator === '') ? self::DEFAULT_OPERATOR : $operator; return $this; } /** * Get Allow Blank. */ public function getAllowBlank(): bool { return $this->allowBlank; } /** * Set Allow Blank. * * @return $this */ public function setAllowBlank(bool $allowBlank) { $this->allowBlank = $allowBlank; return $this; } /** * Get Show DropDown. */ public function getShowDropDown(): bool { return $this->showDropDown; } /** * Set Show DropDown. * * @return $this */ public function setShowDropDown(bool $showDropDown) { $this->showDropDown = $showDropDown; return $this; } /** * Get Show InputMessage. */ public function getShowInputMessage(): bool { return $this->showInputMessage; } /** * Set Show InputMessage. * * @return $this */ public function setShowInputMessage(bool $showInputMessage) { $this->showInputMessage = $showInputMessage; return $this; } /** * Get Show ErrorMessage. */ public function getShowErrorMessage(): bool { return $this->showErrorMessage; } /** * Set Show ErrorMessage. * * @return $this */ public function setShowErrorMessage(bool $showErrorMessage) { $this->showErrorMessage = $showErrorMessage; return $this; } /** * Get Error title. */ public function getErrorTitle(): string { return $this->errorTitle; } /** * Set Error title. * * @return $this */ public function setErrorTitle(string $errorTitle) { $this->errorTitle = $errorTitle; return $this; } /** * Get Error. */ public function getError(): string { return $this->error; } /** * Set Error. * * @return $this */ public function setError(string $error) { $this->error = $error; return $this; } /** * Get Prompt title. */ public function getPromptTitle(): string { return $this->promptTitle; } /** * Set Prompt title. * * @return $this */ public function setPromptTitle(string $promptTitle) { $this->promptTitle = $promptTitle; return $this; } /** * Get Prompt. */ public function getPrompt(): string { return $this->prompt; } /** * Set Prompt. * * @return $this */ public function setPrompt(string $prompt) { $this->prompt = $prompt; return $this; } /** * Get hash code. * * @return string Hash code */ public function getHashCode(): string { return md5( $this->formula1 . $this->formula2 . $this->type . $this->errorStyle . $this->operator . ($this->allowBlank ? 't' : 'f') . ($this->showDropDown ? 't' : 'f') . ($this->showInputMessage ? 't' : 'f') . ($this->showErrorMessage ? 't' : 'f') . $this->errorTitle . $this->error . $this->promptTitle . $this->prompt . $this->sqref . __CLASS__ ); } private ?string $sqref = null; public function getSqref(): ?string { return $this->sqref; } public function setSqref(?string $str): self { $this->sqref = $str; return $this; } } PK!7:libraries/vendor/PhpSpreadsheet/Cell/StringValueBinder.phpnu[setIgnoredErrors = $setIgnoredErrors; return $this; } public function setNullConversion(bool $suppressConversion = false): self { $this->convertNull = $suppressConversion; return $this; } public function setBooleanConversion(bool $suppressConversion = false): self { $this->convertBoolean = $suppressConversion; return $this; } public function getBooleanConversion(): bool { return $this->convertBoolean; } public function setNumericConversion(bool $suppressConversion = false): self { $this->convertNumeric = $suppressConversion; return $this; } public function setFormulaConversion(bool $suppressConversion = false): self { $this->convertFormula = $suppressConversion; return $this; } public function setConversionForAllValueTypes(bool $suppressConversion = false): self { $this->convertNull = $suppressConversion; $this->convertBoolean = $suppressConversion; $this->convertNumeric = $suppressConversion; $this->convertFormula = $suppressConversion; return $this; } /** * Bind value to a cell. * * @param Cell $cell Cell to bind value to * @param mixed $value Value to bind in cell */ public function bindValue(Cell $cell, $value): bool { if (is_object($value)) { return $this->bindObjectValue($cell, $value); } if ($value !== null && !is_scalar($value)) { throw new SpreadsheetException('Unable to bind unstringable ' . gettype($value)); } // sanitize UTF-8 strings if (is_string($value)) { $value = StringHelper::sanitizeUTF8($value); } $ignoredErrors = false; if ($value === null && $this->convertNull === false) { $cell->setValueExplicit($value, DataType::TYPE_NULL); } elseif (is_bool($value) && $this->convertBoolean === false) { $cell->setValueExplicit($value, DataType::TYPE_BOOL); } elseif ((is_int($value) || is_float($value)) && $this->convertNumeric === false) { $cell->setValueExplicit($value, DataType::TYPE_NUMERIC); } elseif (is_string($value) && strlen($value) > 1 && $value[0] === '=' && $this->convertFormula === false && parent::dataTypeForValue($value) === DataType::TYPE_FORMULA) { $cell->setValueExplicit($value, DataType::TYPE_FORMULA); } else { $ignoredErrors = is_numeric($value); $cell->setValueExplicit((string) $value, DataType::TYPE_STRING); } if ($this->setIgnoredErrors) { $cell->getIgnoredErrors()->setNumberStoredAsText($ignoredErrors); } return true; } protected function bindObjectValue(Cell $cell, object $value): bool { // Handle any objects that might be injected $ignoredErrors = false; if ($value instanceof DateTimeInterface) { $value = $value->format('Y-m-d H:i:s'); $cell->setValueExplicit($value, DataType::TYPE_STRING); } elseif ($value instanceof RichText) { $cell->setValueExplicit($value, DataType::TYPE_INLINE); $ignoredErrors = is_numeric($value->getPlainText()); } elseif ((is_object($value) && method_exists($value, '__toString'))) { $cell->setValueExplicit((string) $value, DataType::TYPE_STRING); $ignoredErrors = is_numeric((string) $value); } else { throw new SpreadsheetException('Unable to bind unstringable object of type ' . get_class($value)); } if ($this->setIgnoredErrors) { $cell->getIgnoredErrors()->setNumberStoredAsText($ignoredErrors); } return true; } } PK!1libraries/vendor/PhpSpreadsheet/Cell/RowRange.phpnu[ */ class RowRange implements AddressRange { protected ?Worksheet $worksheet; protected int $from; protected int $to; public function __construct(int $from, ?int $to = null, ?Worksheet $worksheet = null) { $this->validateFromTo($from, $to ?? $from); $this->worksheet = $worksheet; } public function __destruct() { $this->worksheet = null; } public static function fromArray(array $array, ?Worksheet $worksheet = null): self { [$from, $to] = $array; return new self($from, $to, $worksheet); } private function validateFromTo(int $from, int $to): void { // Identify actual top and bottom values (in case we've been given bottom and top) $this->from = min($from, $to); $this->to = max($from, $to); } public function from(): int { return $this->from; } public function to(): int { return $this->to; } public function rowCount(): int { return $this->to - $this->from + 1; } public function shiftRight(int $offset = 1): self { $newFrom = $this->from + $offset; $newFrom = ($newFrom < 1) ? 1 : $newFrom; $newTo = $this->to + $offset; $newTo = ($newTo < 1) ? 1 : $newTo; return new self($newFrom, $newTo, $this->worksheet); } public function shiftLeft(int $offset = 1): self { return $this->shiftRight(0 - $offset); } public function toCellRange(): CellRange { return new CellRange( CellAddress::fromColumnAndRow(Coordinate::columnIndexFromString('A'), $this->from, $this->worksheet), CellAddress::fromColumnAndRow(Coordinate::columnIndexFromString(AddressRange::MAX_COLUMN), $this->to) ); } public function __toString(): string { if ($this->worksheet !== null) { $title = str_replace("'", "''", $this->worksheet->getTitle()); return "'{$title}'!{$this->from}:{$this->to}"; } return "{$this->from}:{$this->to}"; } } PK!9YOVcc5libraries/vendor/PhpSpreadsheet/Cell/AddressRange.phpnu[ */ private static array $errorCodes = [ '#NULL!' => 0, '#DIV/0!' => 1, '#VALUE!' => 2, '#REF!' => 3, '#NAME?' => 4, '#NUM!' => 5, '#N/A' => 6, '#CALC!' => 7, ]; public const MAX_STRING_LENGTH = 32767; /** * Get list of error codes. * * @return array */ public static function getErrorCodes(): array { return self::$errorCodes; } /** * Check a string that it satisfies Excel requirements. * * @param null|RichText|string $textValue Value to sanitize to an Excel string * * @return RichText|string Sanitized value */ public static function checkString($textValue) { if ($textValue instanceof RichText) { // TODO: Sanitize Rich-Text string (max. character count is 32,767) return $textValue; } // string must never be longer than 32,767 characters, truncate if necessary $textValue = StringHelper::substring((string) $textValue, 0, self::MAX_STRING_LENGTH); // we require that newline is represented as "\n" in core, not as "\r\n" or "\r" $textValue = str_replace(["\r\n", "\r"], "\n", $textValue); return $textValue; } /** * Check a value that it is a valid error code. * * @param mixed $value Value to sanitize to an Excel error code * * @return string Sanitized value */ public static function checkErrorCode($value): string { $default = '#NULL!'; $value = ($value === null) ? $default : StringHelper::convertToString($value, false, $default); if (!isset(self::$errorCodes[$value])) { $value = $default; } return $value; } } PK!:8nn-libraries/vendor/PhpSpreadsheet/Cell/Cell.phpnu[ */ private ?array $formulaAttributes = null; private IgnoredErrors $ignoredErrors; /** * Update the cell into the cell collection. * * @throws SpreadsheetException */ public function updateInCollection(): self { $parent = $this->parent; if ($parent === null) { throw new SpreadsheetException('Cannot update when cell is not bound to a worksheet'); } $parent->update($this); return $this; } public function detach(): void { $this->parent = null; } public function attach(Cells $parent): void { $this->parent = $parent; } /** * Create a new Cell. * * @throws SpreadsheetException * @param mixed $value */ public function __construct($value, ?string $dataType, Worksheet $worksheet) { // Initialise cell value $this->value = $value; // Set worksheet cache $this->parent = $worksheet->getCellCollection(); // Set datatype? if ($dataType !== null) { if ($dataType == DataType::TYPE_STRING2) { $dataType = DataType::TYPE_STRING; } $this->dataType = $dataType; } else { $valueBinder = (($nullsafeVariable1 = $worksheet->getParent()) ? $nullsafeVariable1->getValueBinder() : null) ?? self::getValueBinder(); if ($valueBinder->bindValue($this, $value) === false) { throw new SpreadsheetException('Value could not be bound to cell.'); } } $this->ignoredErrors = new IgnoredErrors(); } /** * Get cell coordinate column. * * @throws SpreadsheetException */ public function getColumn(): string { $parent = $this->parent; if ($parent === null) { throw new SpreadsheetException('Cannot get column when cell is not bound to a worksheet'); } return $parent->getCurrentColumn(); } /** * Get cell coordinate row. * * @throws SpreadsheetException */ public function getRow(): int { $parent = $this->parent; if ($parent === null) { throw new SpreadsheetException('Cannot get row when cell is not bound to a worksheet'); } return $parent->getCurrentRow(); } /** * Get cell coordinate. * * @throws SpreadsheetException */ public function getCoordinate(): string { $parent = $this->parent; if ($parent !== null) { $coordinate = $parent->getCurrentCoordinate(); } else { $coordinate = null; } if ($coordinate === null) { throw new SpreadsheetException('Coordinate no longer exists'); } return $coordinate; } /** * Get cell value. * @return mixed */ public function getValue() { return $this->value; } public function getValueString(): string { return StringHelper::convertToString($this->value, false); } /** * Get cell value with formatting. */ public function getFormattedValue(): string { $currentCalendar = SharedDate::getExcelCalendar(); SharedDate::setExcelCalendar(($nullsafeVariable2 = $this->getWorksheet()->getParent()) ? $nullsafeVariable2->getExcelCalendar() : null); $formattedValue = (string) NumberFormat::toFormattedString( $this->getCalculatedValueString(), (string) $this->getStyle()->getNumberFormat()->getFormatCode(true) ); SharedDate::setExcelCalendar($currentCalendar); return $formattedValue; } /** * @param mixed $oldValue * @param mixed $newValue */ protected static function updateIfCellIsTableHeader(?Worksheet $workSheet, self $cell, $oldValue, $newValue): void { $oldValue = StringHelper::convertToString($oldValue, false); $newValue = StringHelper::convertToString($newValue, false); if (StringHelper::strToLower($oldValue) === StringHelper::strToLower($newValue) || $workSheet === null) { return; } foreach ($workSheet->getTableCollection() as $table) { /** @var Table $table */ if ($cell->isInRange($table->getRange())) { $rangeRowsColumns = Coordinate::getRangeBoundaries($table->getRange()); if ($cell->getRow() === (int) $rangeRowsColumns[0][1]) { Table\Column::updateStructuredReferences($workSheet, $oldValue, $newValue); } return; } } } /** * Set cell value. * * Sets the value for a cell, automatically determining the datatype using the value binder * * @param mixed $value Value * @param null|IValueBinder $binder Value Binder to override the currently set Value Binder * * @throws SpreadsheetException */ public function setValue($value, ?IValueBinder $binder = null): self { // Cells?->Worksheet?->Spreadsheet $binder ??= (($nullsafeVariable3 = ($nullsafeVariable4 = ($nullsafeVariable5 = $this->parent) ? $nullsafeVariable5->getParent() : null) ? $nullsafeVariable4->getParent() : null) ? $nullsafeVariable3->getValueBinder() : null) ?? self::getValueBinder(); if (!$binder->bindValue($this, $value)) { throw new SpreadsheetException('Value could not be bound to cell.'); } return $this; } /** * Set the value for a cell, with the explicit data type passed to the method (bypassing any use of the value binder). * * @param mixed $value Value * @param string $dataType Explicit data type, see DataType::TYPE_* * Note that PhpSpreadsheet does not validate that the value and datatype are consistent, in using this * method, then it is your responsibility as an end-user developer to validate that the value and * the datatype match. * If you do mismatch value and datatype, then the value you enter may be changed to match the datatype * that you specify. * * @throws SpreadsheetException */ public function setValueExplicit($value, string $dataType = DataType::TYPE_STRING): self { $oldValue = $this->value; $quotePrefix = false; // set the value according to data type switch ($dataType) { case DataType::TYPE_NULL: $this->value = null; break; case DataType::TYPE_STRING2: $dataType = DataType::TYPE_STRING; // no break case DataType::TYPE_STRING: // Synonym for string if (is_string($value) && strlen($value) > 1 && $value[0] === '=') { $quotePrefix = true; } // no break case DataType::TYPE_INLINE: // Rich text $value2 = StringHelper::convertToString($value, true); $this->value = DataType::checkString(($value instanceof RichText) ? $value : $value2); break; case DataType::TYPE_NUMERIC: if ($value !== null && !is_bool($value) && !is_numeric($value)) { throw new SpreadsheetException('Invalid numeric value for datatype Numeric'); } $this->value = 0 + $value; break; case DataType::TYPE_FORMULA: $this->value = StringHelper::convertToString($value, true); break; case DataType::TYPE_BOOL: $this->value = (bool) $value; break; case DataType::TYPE_ISO_DATE: $this->value = SharedDate::convertIsoDate($value); $dataType = DataType::TYPE_NUMERIC; break; case DataType::TYPE_ERROR: $this->value = DataType::checkErrorCode($value); break; default: throw new SpreadsheetException('Invalid datatype: ' . $dataType); } // set the datatype $this->dataType = $dataType; $this->updateInCollection(); $cellCoordinate = $this->getCoordinate(); self::updateIfCellIsTableHeader(($nullsafeVariable6 = $this->getParent()) ? $nullsafeVariable6->getParent() : null, $this, $oldValue, $value); $worksheet = $this->getWorksheet(); $spreadsheet = $worksheet->getParent(); if (isset($spreadsheet) && $spreadsheet->getIndex($worksheet, true) >= 0) { $originalSelected = $worksheet->getSelectedCells(); $activeSheetIndex = $spreadsheet->getActiveSheetIndex(); $style = $this->getStyle(); $oldQuotePrefix = $style->getQuotePrefix(); if ($oldQuotePrefix !== $quotePrefix) { $style->setQuotePrefix($quotePrefix); } $worksheet->setSelectedCells($originalSelected); if ($activeSheetIndex >= 0) { $spreadsheet->setActiveSheetIndex($activeSheetIndex); } } return (($nullsafeVariable7 = $this->getParent()) ? $nullsafeVariable7->get($cellCoordinate) : null) ?? $this; } public const CALCULATE_DATE_TIME_ASIS = 0; public const CALCULATE_DATE_TIME_FLOAT = 1; public const CALCULATE_TIME_FLOAT = 2; private static int $calculateDateTimeType = self::CALCULATE_DATE_TIME_ASIS; public static function getCalculateDateTimeType(): int { return self::$calculateDateTimeType; } /** @throws CalculationException */ public static function setCalculateDateTimeType(int $calculateDateTimeType): void { switch ($calculateDateTimeType) { case self::CALCULATE_DATE_TIME_ASIS: case self::CALCULATE_DATE_TIME_FLOAT: case self::CALCULATE_TIME_FLOAT: self::$calculateDateTimeType = $calculateDateTimeType; break; default: throw new CalculationException("Invalid value $calculateDateTimeType for calculated date time type"); } } /** * Convert date, time, or datetime from int to float if desired. * @param mixed $result * @return mixed */ private function convertDateTimeInt($result) { if (is_int($result)) { if (self::$calculateDateTimeType === self::CALCULATE_TIME_FLOAT) { if (SharedDate::isDateTime($this, $result, false)) { $result = (float) $result; } } elseif (self::$calculateDateTimeType === self::CALCULATE_DATE_TIME_FLOAT) { if (SharedDate::isDateTime($this, $result, true)) { $result = (float) $result; } } } return $result; } /** * Get calculated cell value converted to string. */ public function getCalculatedValueString(): string { $value = $this->getCalculatedValue(); while (is_array($value)) { $value = array_shift($value); } return StringHelper::convertToString($value, false); } /** * Get calculated cell value. * * @param bool $resetLog Whether the calculation engine logger should be reset or not * * @throws CalculationException * @return mixed */ public function getCalculatedValue(bool $resetLog = true) { $title = 'unknown'; $oldAttributes = $this->formulaAttributes; $oldAttributesT = $oldAttributes['t'] ?? ''; $coordinate = $this->getCoordinate(); $oldAttributesRef = $oldAttributes['ref'] ?? $coordinate; $originalValue = $this->value; $originalDataType = $this->dataType; $this->formulaAttributes = []; $spill = false; if ($this->dataType === DataType::TYPE_FORMULA) { try { $currentCalendar = SharedDate::getExcelCalendar(); SharedDate::setExcelCalendar(($nullsafeVariable8 = $this->getWorksheet()->getParent()) ? $nullsafeVariable8->getExcelCalendar() : null); $thisworksheet = $this->getWorksheet(); $index = $thisworksheet->getParentOrThrow()->getActiveSheetIndex(); $selected = $thisworksheet->getSelectedCells(); $title = $thisworksheet->getTitle(); $calculation = Calculation::getInstance($thisworksheet->getParent()); $result = $calculation->calculateCellValue($this, $resetLog); $result = $this->convertDateTimeInt($result); $thisworksheet->setSelectedCells($selected); $thisworksheet->getParentOrThrow()->setActiveSheetIndex($index); if (is_array($result) && $calculation->getInstanceArrayReturnType() !== Calculation::RETURN_ARRAY_AS_ARRAY) { while (is_array($result)) { $result = array_shift($result); } } if ( !is_array($result) && $calculation->getInstanceArrayReturnType() === Calculation::RETURN_ARRAY_AS_ARRAY && $oldAttributesT === 'array' && ($oldAttributesRef === $coordinate || $oldAttributesRef === "$coordinate:$coordinate") ) { $result = [$result]; } // if return_as_array for formula like '=sheet!cell' if (is_array($result) && count($result) === 1) { $resultKey = array_keys($result)[0]; $resultValue = $result[$resultKey]; if (is_int($resultKey) && is_array($resultValue) && count($resultValue) === 1) { $resultKey2 = array_keys($resultValue)[0]; $resultValue2 = $resultValue[$resultKey2]; if (is_string($resultKey2) && !is_array($resultValue2) && preg_match('/[a-zA-Z]{1,3}/', $resultKey2) === 1) { $result = $resultValue2; } } } $newColumn = $this->getColumn(); if (is_array($result)) { $this->formulaAttributes['t'] = 'array'; $this->formulaAttributes['ref'] = $maxCoordinate = $coordinate; $newRow = $row = $this->getRow(); $column = $this->getColumn(); foreach ($result as $resultRow) { if (is_array($resultRow)) { $newColumn = $column; foreach ($resultRow as $resultValue) { if ($row !== $newRow || $column !== $newColumn) { $maxCoordinate = $newColumn . $newRow; if ($thisworksheet->getCell($newColumn . $newRow)->getValue() !== null) { if (!Coordinate::coordinateIsInsideRange($oldAttributesRef, $newColumn . $newRow)) { $spill = true; break; } } } ++$newColumn; } ++$newRow; } else { if ($row !== $newRow || $column !== $newColumn) { $maxCoordinate = $newColumn . $newRow; if ($thisworksheet->getCell($newColumn . $newRow)->getValue() !== null) { if (!Coordinate::coordinateIsInsideRange($oldAttributesRef, $newColumn . $newRow)) { $spill = true; } } } ++$newColumn; } if ($spill) { break; } } if (!$spill) { $this->formulaAttributes['ref'] .= ":$maxCoordinate"; } $thisworksheet->getCell($column . $row); } if (is_array($result)) { if ($oldAttributes !== null && $calculation->getInstanceArrayReturnType() === Calculation::RETURN_ARRAY_AS_ARRAY) { if (($oldAttributesT) === 'array') { $thisworksheet = $this->getWorksheet(); $coordinate = $this->getCoordinate(); $ref = $oldAttributesRef; if (preg_match('/^([A-Z]{1,3})([0-9]{1,7})(:([A-Z]{1,3})([0-9]{1,7}))?$/', $ref, $matches) === 1) { if (isset($matches[3])) { $minCol = $matches[1]; $minRow = (int) $matches[2]; // https://github.com/phpstan/phpstan/issues/11602 $maxCol = $matches[4]; // @phpstan-ignore-line ++$maxCol; $maxRow = (int) $matches[5]; // @phpstan-ignore-line for ($row = $minRow; $row <= $maxRow; ++$row) { for ($col = $minCol; $col !== $maxCol; ++$col) { if ("$col$row" !== $coordinate) { $thisworksheet->getCell("$col$row")->setValue(null); } } } } } $thisworksheet->getCell($coordinate); } } } if ($spill) { $result = ExcelError::SPILL(); } if (is_array($result)) { $newRow = $row = $this->getRow(); $newColumn = $column = $this->getColumn(); foreach ($result as $resultRow) { if (is_array($resultRow)) { $newColumn = $column; foreach ($resultRow as $resultValue) { if ($row !== $newRow || $column !== $newColumn) { $thisworksheet->getCell($newColumn . $newRow)->setValue($resultValue); } ++$newColumn; } ++$newRow; } else { if ($row !== $newRow || $column !== $newColumn) { $thisworksheet->getCell($newColumn . $newRow)->setValue($resultRow); } ++$newColumn; } } $thisworksheet->getCell($column . $row); $this->value = $originalValue; $this->dataType = $originalDataType; } } catch (SpreadsheetException $ex) { SharedDate::setExcelCalendar($currentCalendar); if (($ex->getMessage() === 'Unable to access External Workbook') && ($this->calculatedValue !== null)) { return $this->calculatedValue; // Fallback for calculations referencing external files. } elseif (preg_match('/[Uu]ndefined (name|offset: 2|array key 2)/', $ex->getMessage()) === 1) { return ExcelError::NAME(); } throw new CalculationException( $title . '!' . $this->getCoordinate() . ' -> ' . $ex->getMessage(), $ex->getCode(), $ex ); } SharedDate::setExcelCalendar($currentCalendar); if ($result === Functions::NOT_YET_IMPLEMENTED) { $this->formulaAttributes = $oldAttributes; return $this->calculatedValue; // Fallback if calculation engine does not support the formula. } return $result; } elseif ($this->value instanceof RichText) { return $this->value->getPlainText(); } return $this->convertDateTimeInt($this->value); } /** * Set old calculated value (cached). * * @param mixed $originalValue Value */ public function setCalculatedValue($originalValue, bool $tryNumeric = true): self { if ($originalValue !== null) { $this->calculatedValue = ($tryNumeric && is_numeric($originalValue)) ? (0 + $originalValue) : $originalValue; } return $this->updateInCollection(); } /** * Get old calculated value (cached) * This returns the value last calculated by MS Excel or whichever spreadsheet program was used to * create the original spreadsheet file. * Note that this value is not guaranteed to reflect the actual calculated value because it is * possible that auto-calculation was disabled in the original spreadsheet, and underlying data * values used by the formula have changed since it was last calculated. * @return mixed */ public function getOldCalculatedValue() { return $this->calculatedValue; } /** * Get cell data type. */ public function getDataType(): string { return $this->dataType; } /** * Set cell data type. * * @param string $dataType see DataType::TYPE_* */ public function setDataType(string $dataType): self { $this->setValueExplicit($this->value, $dataType); return $this; } /** * Identify if the cell contains a formula. */ public function isFormula(): bool { return $this->dataType === DataType::TYPE_FORMULA && $this->getStyle()->getQuotePrefix() === false; } /** * Does this cell contain Data validation rules? * * @throws SpreadsheetException */ public function hasDataValidation(): bool { if (!isset($this->parent)) { throw new SpreadsheetException('Cannot check for data validation when cell is not bound to a worksheet'); } return $this->getWorksheet()->dataValidationExists($this->getCoordinate()); } /** * Get Data validation rules. * * @throws SpreadsheetException */ public function getDataValidation(): DataValidation { if (!isset($this->parent)) { throw new SpreadsheetException('Cannot get data validation for cell that is not bound to a worksheet'); } return $this->getWorksheet()->getDataValidation($this->getCoordinate()); } /** * Set Data validation rules. * * @throws SpreadsheetException */ public function setDataValidation(?DataValidation $dataValidation = null): self { if (!isset($this->parent)) { throw new SpreadsheetException('Cannot set data validation for cell that is not bound to a worksheet'); } $this->getWorksheet()->setDataValidation($this->getCoordinate(), $dataValidation); return $this->updateInCollection(); } /** * Does this cell contain valid value? */ public function hasValidValue(): bool { $validator = new DataValidator(); return $validator->isValid($this); } /** * Does this cell contain a Hyperlink? * * @throws SpreadsheetException */ public function hasHyperlink(): bool { if (!isset($this->parent)) { throw new SpreadsheetException('Cannot check for hyperlink when cell is not bound to a worksheet'); } return $this->getWorksheet()->hyperlinkExists($this->getCoordinate()); } /** * Get Hyperlink. * * @throws SpreadsheetException */ public function getHyperlink(): Hyperlink { if (!isset($this->parent)) { throw new SpreadsheetException('Cannot get hyperlink for cell that is not bound to a worksheet'); } return $this->getWorksheet()->getHyperlink($this->getCoordinate()); } /** * Set Hyperlink. * * @throws SpreadsheetException */ public function setHyperlink(?Hyperlink $hyperlink = null): self { if (!isset($this->parent)) { throw new SpreadsheetException('Cannot set hyperlink for cell that is not bound to a worksheet'); } $this->getWorksheet()->setHyperlink($this->getCoordinate(), $hyperlink); return $this->updateInCollection(); } /** * Get cell collection. */ public function getParent(): ?Cells { return $this->parent; } /** * Get parent worksheet. * * @throws SpreadsheetException */ public function getWorksheet(): Worksheet { $parent = $this->parent; if ($parent !== null) { $worksheet = $parent->getParent(); } else { $worksheet = null; } if ($worksheet === null) { throw new SpreadsheetException('Worksheet no longer exists'); } return $worksheet; } public function getWorksheetOrNull(): ?Worksheet { $parent = $this->parent; if ($parent !== null) { $worksheet = $parent->getParent(); } else { $worksheet = null; } return $worksheet; } /** * Is this cell in a merge range. */ public function isInMergeRange(): bool { return (bool) $this->getMergeRange(); } /** * Is this cell the master (top left cell) in a merge range (that holds the actual data value). */ public function isMergeRangeValueCell(): bool { if ($mergeRange = $this->getMergeRange()) { $mergeRange = Coordinate::splitRange($mergeRange); [$startCell] = $mergeRange[0]; return $this->getCoordinate() === $startCell; } return false; } /** * If this cell is in a merge range, then return the range. * * @return false|string */ public function getMergeRange() { foreach ($this->getWorksheet()->getMergeCells() as $mergeRange) { if ($this->isInRange($mergeRange)) { return $mergeRange; } } return false; } /** * Get cell style. */ public function getStyle(): Style { return $this->getWorksheet()->getStyle($this->getCoordinate()); } /** * Get cell style. */ public function getAppliedStyle(): Style { if ($this->getWorksheet()->conditionalStylesExists($this->getCoordinate()) === false) { return $this->getStyle(); } $range = $this->getWorksheet()->getConditionalRange($this->getCoordinate()); if ($range === null) { return $this->getStyle(); } $matcher = new CellStyleAssessor($this, $range); return $matcher->matchConditions($this->getWorksheet()->getConditionalStyles($this->getCoordinate())); } /** * Re-bind parent. */ public function rebindParent(Worksheet $parent): self { $this->parent = $parent->getCellCollection(); return $this->updateInCollection(); } /** * Is cell in a specific range? * * @param string $range Cell range (e.g. A1:A1) */ public function isInRange(string $range): bool { [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($range); // Translate properties $myColumn = Coordinate::columnIndexFromString($this->getColumn()); $myRow = $this->getRow(); // Verify if cell is in range return ($rangeStart[0] <= $myColumn) && ($rangeEnd[0] >= $myColumn) && ($rangeStart[1] <= $myRow) && ($rangeEnd[1] >= $myRow); } /** * Compare 2 cells. * * @param Cell $a Cell a * @param Cell $b Cell b * * @return int Result of comparison (always -1 or 1, never zero!) */ public static function compareCells(self $a, self $b): int { if ($a->getRow() < $b->getRow()) { return -1; } elseif ($a->getRow() > $b->getRow()) { return 1; } elseif (Coordinate::columnIndexFromString($a->getColumn()) < Coordinate::columnIndexFromString($b->getColumn())) { return -1; } return 1; } /** * Get value binder to use. */ public static function getValueBinder(): IValueBinder { if (self::$valueBinder === null) { self::$valueBinder = new DefaultValueBinder(); } return self::$valueBinder; } /** * Set value binder to use. */ public static function setValueBinder(IValueBinder $binder): void { self::$valueBinder = $binder; } /** * Implement PHP __clone to create a deep clone, not just a shallow copy. */ public function __clone() { $vars = get_object_vars($this); foreach ($vars as $propertyName => $propertyValue) { if ((is_object($propertyValue)) && ($propertyName !== 'parent')) { $this->$propertyName = clone $propertyValue; } else { $this->$propertyName = $propertyValue; } } } /** * Get index to cellXf. */ public function getXfIndex(): int { return $this->xfIndex; } /** * Set index to cellXf. */ public function setXfIndex(int $indexValue): self { $this->xfIndex = $indexValue; return $this->updateInCollection(); } /** * Set the formula attributes. * * @param $attributes null|array * * @return $this */ public function setFormulaAttributes(?array $attributes): self { $this->formulaAttributes = $attributes; return $this; } /** * Get the formula attributes. * * @return null|array */ public function getFormulaAttributes(): ?array { return $this->formulaAttributes; } /** * Convert to string. */ public function __toString(): string { $retVal = $this->value; return StringHelper::convertToString($retVal, false); } public function getIgnoredErrors(): IgnoredErrors { return $this->ignoredErrors; } public function isLocked(): bool { $protected = ($nullsafeVariable9 = ($nullsafeVariable10 = ($nullsafeVariable11 = $this->parent) ? $nullsafeVariable11->getParent() : null) ? $nullsafeVariable10->getProtection() : null) ? $nullsafeVariable9->getSheet() : null; if ($protected !== true) { return false; } $locked = $this->getStyle()->getProtection()->getLocked(); return $locked !== Protection::PROTECTION_UNPROTECTED; } public function isHiddenOnFormulaBar(): bool { if ($this->getDataType() !== DataType::TYPE_FORMULA) { return false; } $protected = ($nullsafeVariable12 = ($nullsafeVariable13 = ($nullsafeVariable14 = $this->parent) ? $nullsafeVariable14->getParent() : null) ? $nullsafeVariable13->getProtection() : null) ? $nullsafeVariable12->getSheet() : null; if ($protected !== true) { return false; } $hidden = $this->getStyle()->getProtection()->getHidden(); return $hidden !== Protection::PROTECTION_UNPROTECTED; } } PK!CDe4libraries/vendor/PhpSpreadsheet/Cell/CellAddress.phpnu[cellAddress = str_replace('$', '', $cellAddress); [$this->columnId, $this->rowId, $this->columnName] = Coordinate::indexesFromString($this->cellAddress); $this->worksheet = $worksheet; } public function __destruct() { unset($this->worksheet); } /** * @phpstan-assert int|numeric-string $columnId * @phpstan-assert int|numeric-string $rowId * @param int|string $columnId * @param int|string $rowId */ private static function validateColumnAndRow($columnId, $rowId): void { if (!is_numeric($columnId) || $columnId <= 0 || !is_numeric($rowId) || $rowId <= 0) { throw new Exception('Row and Column Ids must be positive integer values'); } } /** * @param int|string $columnId * @param int|string $rowId */ public static function fromColumnAndRow($columnId, $rowId, ?Worksheet $worksheet = null): self { self::validateColumnAndRow($columnId, $rowId); return new self(Coordinate::stringFromColumnIndex($columnId) . $rowId, $worksheet); } public static function fromColumnRowArray(array $array, ?Worksheet $worksheet = null): self { [$columnId, $rowId] = $array; return self::fromColumnAndRow($columnId, $rowId, $worksheet); } public static function fromCellAddress(string $cellAddress, ?Worksheet $worksheet = null): self { return new self($cellAddress, $worksheet); } /** * The returned address string will contain the worksheet name as well, if available, * (ie. if a Worksheet was provided to the constructor). * e.g. "'Mark''s Worksheet'!C5". */ public function fullCellAddress(): string { if ($this->worksheet !== null) { $title = str_replace("'", "''", $this->worksheet->getTitle()); return "'{$title}'!{$this->cellAddress}"; } return $this->cellAddress; } public function worksheet(): ?Worksheet { return $this->worksheet; } /** * The returned address string will contain just the column/row address, * (even if a Worksheet was provided to the constructor). * e.g. "C5". */ public function cellAddress(): string { return $this->cellAddress; } public function rowId(): int { return $this->rowId; } public function columnId(): int { return $this->columnId; } public function columnName(): string { return $this->columnName; } public function nextRow(int $offset = 1): self { $newRowId = $this->rowId + $offset; if ($newRowId < 1) { $newRowId = 1; } return self::fromColumnAndRow($this->columnId, $newRowId); } public function previousRow(int $offset = 1): self { return $this->nextRow(0 - $offset); } public function nextColumn(int $offset = 1): self { $newColumnId = $this->columnId + $offset; if ($newColumnId < 1) { $newColumnId = 1; } return self::fromColumnAndRow($newColumnId, $this->rowId); } public function previousColumn(int $offset = 1): self { return $this->nextColumn(0 - $offset); } /** * The returned address string will contain the worksheet name as well, if available, * (ie. if a Worksheet was provided to the constructor). * e.g. "'Mark''s Worksheet'!C5". */ public function __toString(): string { return $this->fullCellAddress(); } } PK!.libraries/vendor/PhpSpreadsheet/Cell/index.phpnu[setValueExplicit(true, DataType::TYPE_BOOL); return true; } elseif (StringHelper::strToUpper($value) === Calculation::getFALSE()) { $cell->setValueExplicit(false, DataType::TYPE_BOOL); return true; } // Check for fractions if (preg_match('~^([+-]?)\s*(\d+)\s*/\s*(\d+)$~', $value, $matches)) { return $this->setProperFraction($matches, $cell); } elseif (preg_match('~^([+-]?)(\d+)\s+(\d+)\s*/\s*(\d+)$~', $value, $matches)) { return $this->setImproperFraction($matches, $cell); } $decimalSeparatorNoPreg = StringHelper::getDecimalSeparator(); $decimalSeparator = preg_quote($decimalSeparatorNoPreg, '/'); $thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator(), '/'); // Check for percentage if (preg_match('/^\-?\d*' . $decimalSeparator . '?\d*\s?\%$/', (string) preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value))) { return $this->setPercentage((string) preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value), $cell); } // Check for currency if (preg_match(FormattedNumber::currencyMatcherRegexp(), (string) preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value), $matches, PREG_UNMATCHED_AS_NULL)) { // Convert value to number $sign = ($matches['PrefixedSign'] ?? $matches['PrefixedSign2'] ?? $matches['PostfixedSign']) ?? null; $currencyCode = $matches['PrefixedCurrency'] ?? $matches['PostfixedCurrency'] ?? ''; /** @var string */ $temp = str_replace([$decimalSeparatorNoPreg, $currencyCode, ' ', '-'], ['.', '', '', ''], (string) preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value)); $value = (float) ($sign . trim($temp)); return $this->setCurrency($value, $cell, $currencyCode); } // Check for time without seconds e.g. '9:45', '09:45' if (preg_match('/^(\d|[0-1]\d|2[0-3]):[0-5]\d$/', $value)) { return $this->setTimeHoursMinutes($value, $cell); } // Check for time with seconds '9:45:59', '09:45:59' if (preg_match('/^(\d|[0-1]\d|2[0-3]):[0-5]\d:[0-5]\d$/', $value)) { return $this->setTimeHoursMinutesSeconds($value, $cell); } // Check for datetime, e.g. '2008-12-31', '2008-12-31 15:59', '2008-12-31 15:59:10' if (($d = Date::stringToExcel($value)) !== false) { // Convert value to number $cell->setValueExplicit($d, DataType::TYPE_NUMERIC); // Determine style. Either there is a time part or not. Look for ':' if (str_contains($value, ':')) { $formatCode = 'yyyy-mm-dd h:mm'; } else { $formatCode = 'yyyy-mm-dd'; } $cell->getWorksheet()->getStyle($cell->getCoordinate()) ->getNumberFormat()->setFormatCode($formatCode); return true; } // Check for newline character "\n" if (str_contains($value, "\n")) { $cell->setValueExplicit($value, DataType::TYPE_STRING); // Set style $cell->getWorksheet()->getStyle($cell->getCoordinate()) ->getAlignment()->setWrapText(true); return true; } } // Not bound yet? Use parent... return parent::bindValue($cell, $value); } protected function setImproperFraction(array $matches, Cell $cell): bool { // Convert value to number $value = $matches[2] + ($matches[3] / $matches[4]); if ($matches[1] === '-') { $value = 0 - $value; } $cell->setValueExplicit((float) $value, DataType::TYPE_NUMERIC); // Build the number format mask based on the size of the matched values $dividend = str_repeat('?', strlen($matches[3])); $divisor = str_repeat('?', strlen($matches[4])); $fractionMask = "# {$dividend}/{$divisor}"; // Set style $cell->getWorksheet()->getStyle($cell->getCoordinate()) ->getNumberFormat()->setFormatCode($fractionMask); return true; } protected function setProperFraction(array $matches, Cell $cell): bool { // Convert value to number $value = $matches[2] / $matches[3]; if ($matches[1] === '-') { $value = 0 - $value; } $cell->setValueExplicit((float) $value, DataType::TYPE_NUMERIC); // Build the number format mask based on the size of the matched values $dividend = str_repeat('?', strlen($matches[2])); $divisor = str_repeat('?', strlen($matches[3])); $fractionMask = "{$dividend}/{$divisor}"; // Set style $cell->getWorksheet()->getStyle($cell->getCoordinate()) ->getNumberFormat()->setFormatCode($fractionMask); return true; } protected function setPercentage(string $value, Cell $cell): bool { // Convert value to number $value = ((float) str_replace('%', '', $value)) / 100; $cell->setValueExplicit($value, DataType::TYPE_NUMERIC); // Set style $cell->getWorksheet()->getStyle($cell->getCoordinate()) ->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_PERCENTAGE_00); return true; } protected function setCurrency(float $value, Cell $cell, string $currencyCode): bool { $cell->setValueExplicit($value, DataType::TYPE_NUMERIC); // Set style $cell->getWorksheet()->getStyle($cell->getCoordinate()) ->getNumberFormat()->setFormatCode( str_replace('$', '[$' . $currencyCode . ']', NumberFormat::FORMAT_CURRENCY_USD) ); return true; } protected function setTimeHoursMinutes(string $value, Cell $cell): bool { // Convert value to number [$hours, $minutes] = explode(':', $value); $hours = (int) $hours; $minutes = (int) $minutes; $days = ($hours / 24) + ($minutes / 1440); $cell->setValueExplicit($days, DataType::TYPE_NUMERIC); // Set style $cell->getWorksheet()->getStyle($cell->getCoordinate()) ->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_TIME3); return true; } protected function setTimeHoursMinutesSeconds(string $value, Cell $cell): bool { // Convert value to number [$hours, $minutes, $seconds] = explode(':', $value); $hours = (int) $hours; $minutes = (int) $minutes; $seconds = (int) $seconds; $days = ($hours / 24) + ($minutes / 1440) + ($seconds / 86400); $cell->setValueExplicit($days, DataType::TYPE_NUMERIC); // Set style $cell->getWorksheet()->getStyle($cell->getCoordinate()) ->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_TIME4); return true; } } PK!鍛r6libraries/vendor/PhpSpreadsheet/Cell/AddressHelper.phpnu[ */ class ColumnRange implements AddressRange { protected ?Worksheet $worksheet; protected int $from; protected int $to; public function __construct(string $from, ?string $to = null, ?Worksheet $worksheet = null) { $this->validateFromTo( Coordinate::columnIndexFromString($from), Coordinate::columnIndexFromString($to ?? $from) ); $this->worksheet = $worksheet; } public function __destruct() { $this->worksheet = null; } public static function fromColumnIndexes(int $from, int $to, ?Worksheet $worksheet = null): self { return new self(Coordinate::stringFromColumnIndex($from), Coordinate::stringFromColumnIndex($to), $worksheet); } /** * @param array $array */ public static function fromArray(array $array, ?Worksheet $worksheet = null): self { array_walk( $array, function (&$column): void { $column = is_numeric($column) ? Coordinate::stringFromColumnIndex((int) $column) : $column; } ); /** @var string $from */ /** @var string $to */ [$from, $to] = $array; return new self($from, $to, $worksheet); } private function validateFromTo(int $from, int $to): void { // Identify actual top and bottom values (in case we've been given bottom and top) $this->from = min($from, $to); $this->to = max($from, $to); } public function columnCount(): int { return $this->to - $this->from + 1; } public function shiftDown(int $offset = 1): self { $newFrom = $this->from + $offset; $newFrom = ($newFrom < 1) ? 1 : $newFrom; $newTo = $this->to + $offset; $newTo = ($newTo < 1) ? 1 : $newTo; return self::fromColumnIndexes($newFrom, $newTo, $this->worksheet); } public function shiftUp(int $offset = 1): self { return $this->shiftDown(0 - $offset); } public function from(): string { return Coordinate::stringFromColumnIndex($this->from); } public function to(): string { return Coordinate::stringFromColumnIndex($this->to); } public function fromIndex(): int { return $this->from; } public function toIndex(): int { return $this->to; } public function toCellRange(): CellRange { return new CellRange( CellAddress::fromColumnAndRow($this->from, 1, $this->worksheet), CellAddress::fromColumnAndRow($this->to, AddressRange::MAX_ROW) ); } public function __toString(): string { $from = $this->from(); $to = $this->to(); if ($this->worksheet !== null) { $title = str_replace("'", "''", $this->worksheet->getTitle()); return "'{$title}'!{$from}:{$to}"; } return "{$from}:{$to}"; } } PK!3\ý6libraries/vendor/PhpSpreadsheet/Cell/IgnoredErrors.phpnu[numberStoredAsText = $value; return $this; } public function getNumberStoredAsText(): bool { return $this->numberStoredAsText; } public function setFormula(bool $value): self { $this->formula = $value; return $this; } public function getFormula(): bool { return $this->formula; } public function setFormulaRange(bool $value): self { $this->formulaRange = $value; return $this; } public function getFormulaRange(): bool { return $this->formulaRange; } public function setTwoDigitTextYear(bool $value): self { $this->twoDigitTextYear = $value; return $this; } public function getTwoDigitTextYear(): bool { return $this->twoDigitTextYear; } public function setEvalError(bool $value): self { $this->evalError = $value; return $this; } public function getEvalError(): bool { return $this->evalError; } } PK!HVV3libraries/vendor/PhpSpreadsheet/Cell/Coordinate.phpnu[\$?[A-Z]{1,3})(?\$?\d{1,7})$/i'; public const FULL_REFERENCE_REGEX = '/^(?:(?[^!]*)!)?(?(?[$]?[A-Z]{1,3}[$]?\d{1,7})(?:\:(?[$]?[A-Z]{1,3}[$]?\d{1,7}))?)$/i'; /** * Default range variable constant. * * @var string */ const DEFAULT_RANGE = 'A1:A1'; /** * Convert string coordinate to [0 => int column index, 1 => int row index]. * * @param string $cellAddress eg: 'A1' * * @return array{0: string, 1: string} Array containing column and row (indexes 0 and 1) */ public static function coordinateFromString(string $cellAddress): array { if (preg_match(self::A1_COORDINATE_REGEX, $cellAddress, $matches)) { return [$matches['col'], $matches['row']]; } elseif (self::coordinateIsRange($cellAddress)) { throw new Exception('Cell coordinate string can not be a range of cells'); } elseif ($cellAddress == '') { throw new Exception('Cell coordinate can not be zero-length string'); } throw new Exception('Invalid cell coordinate ' . $cellAddress); } /** * Convert string coordinate to [0 => int column index, 1 => int row index, 2 => string column string]. * * @param string $coordinates eg: 'A1', '$B$12' * * @return array{0: int, 1: int, 2: string} Array containing column and row index, and column string */ public static function indexesFromString(string $coordinates): array { [$column, $row] = self::coordinateFromString($coordinates); $column = ltrim($column, '$'); return [ self::columnIndexFromString($column), (int) ltrim($row, '$'), $column, ]; } /** * Checks if a Cell Address represents a range of cells. * * @param string $cellAddress eg: 'A1' or 'A1:A2' or 'A1:A2,C1:C2' * * @return bool Whether the coordinate represents a range of cells */ public static function coordinateIsRange(string $cellAddress): bool { return str_contains($cellAddress, ':') || str_contains($cellAddress, ','); } /** * Make string row, column or cell coordinate absolute. * * @param int|string $cellAddress e.g. 'A' or '1' or 'A1' * Note that this value can be a row or column reference as well as a cell reference * * @return string Absolute coordinate e.g. '$A' or '$1' or '$A$1' */ public static function absoluteReference($cellAddress): string { $cellAddress = (string) $cellAddress; if (self::coordinateIsRange($cellAddress)) { throw new Exception('Cell coordinate string can not be a range of cells'); } // Split out any worksheet name from the reference [$worksheet, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true); if ($worksheet > '') { $worksheet .= '!'; } // Create absolute coordinate $cellAddress = "$cellAddress"; if (ctype_digit($cellAddress)) { return $worksheet . '$' . $cellAddress; } elseif (ctype_alpha($cellAddress)) { return $worksheet . '$' . strtoupper($cellAddress); } return $worksheet . self::absoluteCoordinate($cellAddress); } /** * Make string coordinate absolute. * * @param string $cellAddress e.g. 'A1' * * @return string Absolute coordinate e.g. '$A$1' */ public static function absoluteCoordinate(string $cellAddress): string { if (self::coordinateIsRange($cellAddress)) { throw new Exception('Cell coordinate string can not be a range of cells'); } // Split out any worksheet name from the coordinate [$worksheet, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true); if ($worksheet > '') { $worksheet .= '!'; } // Create absolute coordinate [$column, $row] = self::coordinateFromString($cellAddress ?? 'A1'); $column = ltrim($column, '$'); $row = ltrim($row, '$'); return $worksheet . '$' . $column . '$' . $row; } /** * Split range into coordinate strings. * * @param string $range e.g. 'B4:D9' or 'B4:D9,H2:O11' or 'B4' * * @return array Array containing one or more arrays containing one or two coordinate strings * e.g. ['B4','D9'] or [['B4','D9'], ['H2','O11']] * or ['B4'] */ public static function splitRange(string $range): array { // Ensure $pRange is a valid range if (empty($range)) { $range = self::DEFAULT_RANGE; } $exploded = explode(',', $range); $outArray = []; foreach ($exploded as $value) { $outArray[] = explode(':', $value); } return $outArray; } /** * Build range from coordinate strings. * * @param array $range Array containing one or more arrays containing one or two coordinate strings * * @return string String representation of $pRange */ public static function buildRange(array $range): string { // Verify range if (empty($range) || !is_array($range[0])) { throw new Exception('Range does not contain any information'); } // Build range $counter = count($range); for ($i = 0; $i < $counter; ++$i) { $range[$i] = implode(':', $range[$i]); } return implode(',', $range); } /** * Calculate range boundaries. * * @param string $range Cell range, Single Cell, Row/Column Range (e.g. A1:A1, B2, B:C, 2:3) * * @return array Range coordinates [Start Cell, End Cell] * where Start Cell and End Cell are arrays (Column Number, Row Number) */ public static function rangeBoundaries(string $range): array { // Ensure $pRange is a valid range if (empty($range)) { $range = self::DEFAULT_RANGE; } // Uppercase coordinate $range = strtoupper($range); // Extract range if (!str_contains($range, ':')) { $rangeA = $rangeB = $range; } else { [$rangeA, $rangeB] = explode(':', $range); } if (is_numeric($rangeA) && is_numeric($rangeB)) { $rangeA = 'A' . $rangeA; $rangeB = AddressRange::MAX_COLUMN . $rangeB; } if (ctype_alpha($rangeA) && ctype_alpha($rangeB)) { $rangeA = $rangeA . '1'; $rangeB = $rangeB . AddressRange::MAX_ROW; } // Calculate range outer borders $rangeStart = self::coordinateFromString($rangeA); $rangeEnd = self::coordinateFromString($rangeB); // Translate column into index $rangeStart[0] = self::columnIndexFromString($rangeStart[0]); $rangeEnd[0] = self::columnIndexFromString($rangeEnd[0]); return [$rangeStart, $rangeEnd]; } /** * Calculate range dimension. * * @param string $range Cell range, Single Cell, Row/Column Range (e.g. A1:A1, B2, B:C, 2:3) * * @return array Range dimension (width, height) */ public static function rangeDimension(string $range): array { // Calculate range outer borders [$rangeStart, $rangeEnd] = self::rangeBoundaries($range); return [($rangeEnd[0] - $rangeStart[0] + 1), ($rangeEnd[1] - $rangeStart[1] + 1)]; } /** * Calculate range boundaries. * * @param string $range Cell range, Single Cell, Row/Column Range (e.g. A1:A1, B2, B:C, 2:3) * * @return array Range coordinates [Start Cell, End Cell] * where Start Cell and End Cell are arrays [Column ID, Row Number] */ public static function getRangeBoundaries(string $range): array { [$rangeA, $rangeB] = self::rangeBoundaries($range); return [ [self::stringFromColumnIndex($rangeA[0]), $rangeA[1]], [self::stringFromColumnIndex($rangeB[0]), $rangeB[1]], ]; } /** * Check if cell or range reference is valid and return an array with type of reference (cell or range), worksheet (if it was given) * and the coordinate or the first coordinate and second coordinate if it is a range. * * @param string $reference Coordinate or Range (e.g. A1:A1, B2, B:C, 2:3) * * @return array reference data */ private static function validateReferenceAndGetData($reference): array { $data = []; if (1 !== preg_match(self::FULL_REFERENCE_REGEX, $reference, $matches)) { return ['type' => 'invalid']; } if (isset($matches['secondCoordinate'])) { $data['type'] = 'range'; $data['firstCoordinate'] = str_replace('$', '', $matches['firstCoordinate']); $data['secondCoordinate'] = str_replace('$', '', $matches['secondCoordinate']); } else { $data['type'] = 'coordinate'; $data['coordinate'] = str_replace('$', '', $matches['firstCoordinate']); } $worksheet = $matches['worksheet']; if ($worksheet !== '') { if (substr($worksheet, 0, 1) === "'" && substr($worksheet, -1, 1) === "'") { $worksheet = substr($worksheet, 1, -1); } $data['worksheet'] = strtolower($worksheet); } $data['localReference'] = str_replace('$', '', $matches['localReference']); return $data; } /** * Check if coordinate is inside a range. * * @param string $range Cell range, Single Cell, Row/Column Range (e.g. A1:A1, B2, B:C, 2:3) * @param string $coordinate Cell coordinate (e.g. A1) * * @return bool true if coordinate is inside range */ public static function coordinateIsInsideRange(string $range, string $coordinate): bool { $range = Validations::convertWholeRowColumn($range); $rangeData = self::validateReferenceAndGetData($range); if ($rangeData['type'] === 'invalid') { throw new Exception('First argument needs to be a range'); } $coordinateData = self::validateReferenceAndGetData($coordinate); if ($coordinateData['type'] === 'invalid') { throw new Exception('Second argument needs to be a single coordinate'); } if (isset($coordinateData['worksheet']) && !isset($rangeData['worksheet'])) { return false; } if (!isset($coordinateData['worksheet']) && isset($rangeData['worksheet'])) { return false; } if (isset($coordinateData['worksheet'], $rangeData['worksheet'])) { if ($coordinateData['worksheet'] !== $rangeData['worksheet']) { return false; } } $boundaries = self::rangeBoundaries($rangeData['localReference']); $coordinates = self::indexesFromString($coordinateData['localReference']); $columnIsInside = $boundaries[0][0] <= $coordinates[0] && $coordinates[0] <= $boundaries[1][0]; if (!$columnIsInside) { return false; } $rowIsInside = $boundaries[0][1] <= $coordinates[1] && $coordinates[1] <= $boundaries[1][1]; if (!$rowIsInside) { return false; } return true; } /** * Column index from string. * * @param ?string $columnAddress eg 'A' * * @return int Column index (A = 1) */ public static function columnIndexFromString(?string $columnAddress): int { // Using a lookup cache adds a slight memory overhead, but boosts speed // caching using a static within the method is faster than a class static, // though it's additional memory overhead static $indexCache = []; $columnAddress = $columnAddress ?? ''; if (isset($indexCache[$columnAddress])) { return $indexCache[$columnAddress]; } // It's surprising how costly the strtoupper() and ord() calls actually are, so we use a lookup array // rather than use ord() and make it case insensitive to get rid of the strtoupper() as well. // Because it's a static, there's no significant memory overhead either. static $columnLookup = [ 'A' => 1, 'B' => 2, 'C' => 3, 'D' => 4, 'E' => 5, 'F' => 6, 'G' => 7, 'H' => 8, 'I' => 9, 'J' => 10, 'K' => 11, 'L' => 12, 'M' => 13, 'N' => 14, 'O' => 15, 'P' => 16, 'Q' => 17, 'R' => 18, 'S' => 19, 'T' => 20, 'U' => 21, 'V' => 22, 'W' => 23, 'X' => 24, 'Y' => 25, 'Z' => 26, 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5, 'f' => 6, 'g' => 7, 'h' => 8, 'i' => 9, 'j' => 10, 'k' => 11, 'l' => 12, 'm' => 13, 'n' => 14, 'o' => 15, 'p' => 16, 'q' => 17, 'r' => 18, 's' => 19, 't' => 20, 'u' => 21, 'v' => 22, 'w' => 23, 'x' => 24, 'y' => 25, 'z' => 26, ]; // We also use the language construct isset() rather than the more costly strlen() function to match the // length of $columnAddress for improved performance if (isset($columnAddress[0])) { if (!isset($columnAddress[1])) { $indexCache[$columnAddress] = $columnLookup[$columnAddress]; return $indexCache[$columnAddress]; } elseif (!isset($columnAddress[2])) { $indexCache[$columnAddress] = $columnLookup[$columnAddress[0]] * 26 + $columnLookup[$columnAddress[1]]; return $indexCache[$columnAddress]; } elseif (!isset($columnAddress[3])) { $indexCache[$columnAddress] = $columnLookup[$columnAddress[0]] * 676 + $columnLookup[$columnAddress[1]] * 26 + $columnLookup[$columnAddress[2]]; return $indexCache[$columnAddress]; } } throw new Exception( 'Column string index can not be ' . ((isset($columnAddress[0])) ? 'longer than 3 characters' : 'empty') ); } /** * String from column index. * * @param int|numeric-string $columnIndex Column index (A = 1) */ public static function stringFromColumnIndex($columnIndex): string { static $indexCache = []; static $lookupCache = ' ABCDEFGHIJKLMNOPQRSTUVWXYZ'; if (!isset($indexCache[$columnIndex])) { $indexValue = $columnIndex; $base26 = ''; do { $characterValue = ($indexValue % 26) ?: 26; $indexValue = ($indexValue - $characterValue) / 26; $base26 = $lookupCache[$characterValue] . $base26; } while ($indexValue > 0); $indexCache[$columnIndex] = $base26; } return $indexCache[$columnIndex]; } /** * Extract all cell references in range, which may be comprised of multiple cell ranges. * * @param string $cellRange Range: e.g. 'A1' or 'A1:C10' or 'A1:E10,A20:E25' or 'A1:E5 C3:G7' or 'A1:C1,A3:C3 B1:C3' * * @return array Array containing single cell references */ public static function extractAllCellReferencesInRange(string $cellRange): array { if (substr_count($cellRange, '!') > 1) { throw new Exception('3-D Range References are not supported'); } [$worksheet, $cellRange] = Worksheet::extractSheetTitle($cellRange, true); $quoted = ''; if ($worksheet) { $quoted = Worksheet::nameRequiresQuotes($worksheet) ? "'" : ''; if (str_starts_with($worksheet, "'") && str_ends_with($worksheet, "'")) { $worksheet = substr($worksheet, 1, -1); } $worksheet = str_replace("'", "''", $worksheet); } [$ranges, $operators] = self::getCellBlocksFromRangeString($cellRange ?? 'A1'); $cells = []; foreach ($ranges as $range) { $cells[] = self::getReferencesForCellBlock($range); } $cells = self::processRangeSetOperators($operators, $cells); if (empty($cells)) { return []; } $cellList = array_merge(...$cells); return array_map( fn ($cellAddress) => ($worksheet !== '') ? "{$quoted}{$worksheet}{$quoted}!{$cellAddress}" : $cellAddress, self::sortCellReferenceArray($cellList) ); } private static function processRangeSetOperators(array $operators, array $cells): array { $operatorCount = count($operators); for ($offset = 0; $offset < $operatorCount; ++$offset) { $operator = $operators[$offset]; if ($operator !== ' ') { continue; } $cells[$offset] = array_intersect($cells[$offset], $cells[$offset + 1]); unset($operators[$offset], $cells[$offset + 1]); $operators = array_values($operators); $cells = array_values($cells); --$offset; --$operatorCount; } return $cells; } private static function sortCellReferenceArray(array $cellList): array { // Sort the result by column and row $sortKeys = []; foreach ($cellList as $coordinate) { $column = ''; $row = 0; sscanf($coordinate, '%[A-Z]%d', $column, $row); $key = (--$row * 16384) + self::columnIndexFromString((string) $column); $sortKeys[$key] = $coordinate; } ksort($sortKeys); return array_values($sortKeys); } /** * Get all cell references applying union and intersection. * * @param string $cellBlock A cell range e.g. A1:B5,D1:E5 B2:C4 * * @return string A string without intersection operator. * If there was no intersection to begin with, return original argument. * Otherwise, return cells and/or cell ranges in that range separated by comma. */ public static function resolveUnionAndIntersection(string $cellBlock, string $implodeCharacter = ','): string { $cellBlock = preg_replace('/ +/', ' ', trim($cellBlock)) ?? $cellBlock; $cellBlock = preg_replace('/ ,/', ',', $cellBlock) ?? $cellBlock; $cellBlock = preg_replace('/, /', ',', $cellBlock) ?? $cellBlock; $array1 = []; $blocks = explode(',', $cellBlock); foreach ($blocks as $block) { $block0 = explode(' ', $block); if (count($block0) === 1) { $array1 = array_merge($array1, $block0); } else { $blockIdx = -1; $array2 = []; foreach ($block0 as $block00) { ++$blockIdx; if ($blockIdx === 0) { $array2 = self::getReferencesForCellBlock($block00); } else { $array2 = array_intersect($array2, self::getReferencesForCellBlock($block00)); } } $array1 = array_merge($array1, $array2); } } return implode($implodeCharacter, $array1); } /** * Get all cell references for an individual cell block. * * @param string $cellBlock A cell range e.g. A4:B5 * * @return array All individual cells in that range */ private static function getReferencesForCellBlock(string $cellBlock): array { $returnValue = []; // Single cell? if (!self::coordinateIsRange($cellBlock)) { return (array) $cellBlock; } // Range... $ranges = self::splitRange($cellBlock); foreach ($ranges as $range) { // Single cell? if (!isset($range[1])) { $returnValue[] = $range[0]; continue; } // Range... [$rangeStart, $rangeEnd] = $range; [$startColumn, $startRow] = self::coordinateFromString($rangeStart); [$endColumn, $endRow] = self::coordinateFromString($rangeEnd); $startColumnIndex = self::columnIndexFromString($startColumn); $endColumnIndex = self::columnIndexFromString($endColumn); ++$endColumnIndex; // Current data $currentColumnIndex = $startColumnIndex; $currentRow = $startRow; self::validateRange($cellBlock, $startColumnIndex, $endColumnIndex, (int) $currentRow, (int) $endRow); // Loop cells while ($currentColumnIndex < $endColumnIndex) { while ($currentRow <= $endRow) { $returnValue[] = self::stringFromColumnIndex($currentColumnIndex) . $currentRow; ++$currentRow; } ++$currentColumnIndex; $currentRow = $startRow; } } return $returnValue; } /** * Convert an associative array of single cell coordinates to values to an associative array * of cell ranges to values. Only adjacent cell coordinates with the same * value will be merged. If the value is an object, it must implement the method getHashCode(). * * For example, this function converts: * * [ 'A1' => 'x', 'A2' => 'x', 'A3' => 'x', 'A4' => 'y' ] * * to: * * [ 'A1:A3' => 'x', 'A4' => 'y' ] * * @param array $coordinateCollection associative array mapping coordinates to values * * @return array associative array mapping coordinate ranges to valuea */ public static function mergeRangesInCollection(array $coordinateCollection): array { $hashedValues = []; $mergedCoordCollection = []; foreach ($coordinateCollection as $coord => $value) { if (self::coordinateIsRange($coord)) { $mergedCoordCollection[$coord] = $value; continue; } [$column, $row] = self::coordinateFromString($coord); $row = (int) (ltrim($row, '$')); $hashCode = $column . '-' . ((is_object($value) && method_exists($value, 'getHashCode')) ? $value->getHashCode() : $value); if (!isset($hashedValues[$hashCode])) { $hashedValues[$hashCode] = (object) [ 'value' => $value, 'col' => $column, 'rows' => [$row], ]; } else { $hashedValues[$hashCode]->rows[] = $row; } } ksort($hashedValues); foreach ($hashedValues as $hashedValue) { sort($hashedValue->rows); $rowStart = null; $rowEnd = null; $ranges = []; foreach ($hashedValue->rows as $row) { if ($rowStart === null) { $rowStart = $row; $rowEnd = $row; } elseif ($rowEnd === $row - 1) { $rowEnd = $row; } else { if ($rowStart == $rowEnd) { $ranges[] = $hashedValue->col . $rowStart; } else { $ranges[] = $hashedValue->col . $rowStart . ':' . $hashedValue->col . $rowEnd; } $rowStart = $row; $rowEnd = $row; } } if ($rowStart !== null) { // @phpstan-ignore-line if ($rowStart == $rowEnd) { $ranges[] = $hashedValue->col . $rowStart; } else { $ranges[] = $hashedValue->col . $rowStart . ':' . $hashedValue->col . $rowEnd; } } foreach ($ranges as $range) { $mergedCoordCollection[$range] = $hashedValue->value; } } return $mergedCoordCollection; } /** * Get the individual cell blocks from a range string, removing any $ characters. * then splitting by operators and returning an array with ranges and operators. * * @return array[] */ private static function getCellBlocksFromRangeString(string $rangeString): array { $rangeString = str_replace('$', '', strtoupper($rangeString)); // split range sets on intersection (space) or union (,) operators $tokens = preg_split('/([ ,])/', $rangeString, -1, PREG_SPLIT_DELIM_CAPTURE) ?: []; $split = array_chunk($tokens, 2); $ranges = array_column($split, 0); $operators = array_column($split, 1); return [$ranges, $operators]; } /** * Check that the given range is valid, i.e. that the start column and row are not greater than the end column and * row. * * @param string $cellBlock The original range, for displaying a meaningful error message */ private static function validateRange(string $cellBlock, int $startColumnIndex, int $endColumnIndex, int $currentRow, int $endRow): void { if ($startColumnIndex >= $endColumnIndex || $currentRow > $endRow) { throw new Exception('Invalid range: "' . $cellBlock . '"'); } } } PK!^f ;libraries/vendor/PhpSpreadsheet/Cell/DefaultValueBinder.phpnu[format('Y-m-d H:i:s'); } elseif ((is_object($value) && method_exists($value, '__toString'))) { $value = (string) $value; } else { throw new SpreadsheetException('Unable to bind unstringable ' . gettype($value)); } // Set value explicit $cell->setValueExplicit($value, static::dataTypeForValue($value)); // Done! return true; } /** * DataType for value. * @param mixed $value */ public static function dataTypeForValue($value): string { // Match the value against a few data types if ($value === null) { return DataType::TYPE_NULL; } if (is_float($value) || is_int($value)) { return DataType::TYPE_NUMERIC; } if (is_bool($value)) { return DataType::TYPE_BOOL; } if ($value === '') { return DataType::TYPE_STRING; } if ($value instanceof RichText) { return DataType::TYPE_INLINE; } if ((is_object($value) && method_exists($value, '__toString'))) { $value = (string) $value; } if (!is_string($value)) { $gettype = is_object($value) ? get_class($value) : gettype($value); throw new SpreadsheetException("unusable type $gettype"); } if (strlen($value) > 1 && $value[0] === '=') { $calculation = new Calculation(); $calculation->disableBranchPruning(); try { if (empty($calculation->parseFormula($value))) { return DataType::TYPE_STRING; } } catch (CalculationException $e) { $message = $e->getMessage(); if ( $message === 'Formula Error: An unexpected error occurred' || str_contains($message, 'has no operands') ) { return DataType::TYPE_STRING; } } return DataType::TYPE_FORMULA; } if (preg_match('/^[\+\-]?(\d+\.?\d*|\d*\.?\d+)([Ee][\-\+]?[0-2]?\d{1,3})?$/', $value)) { $tValue = ltrim($value, '+-'); if (strlen($tValue) > 1 && $tValue[0] === '0' && $tValue[1] !== '.') { return DataType::TYPE_STRING; } elseif ((!str_contains($value, '.')) && ($value > PHP_INT_MAX)) { return DataType::TYPE_STRING; } elseif (!is_numeric($value)) { return DataType::TYPE_STRING; } return DataType::TYPE_NUMERIC; } $errorCodes = DataType::getErrorCodes(); if (isset($errorCodes[$value])) { return DataType::TYPE_ERROR; } return DataType::TYPE_STRING; } } PK!Q'P!/libraries/vendor/PhpSpreadsheet/IComparable.phpnu[fillType = null; } $this->startColor = new Color(Color::COLOR_WHITE, $isSupervisor, $isConditional); $this->endColor = new Color(Color::COLOR_BLACK, $isSupervisor, $isConditional); // bind parent if we are a supervisor if ($isSupervisor) { $this->startColor->bindParent($this, 'startColor'); $this->endColor->bindParent($this, 'endColor'); } } /** * Get the shared style component for the currently active cell in currently active sheet. * Only used for style supervisor. */ public function getSharedComponent(): self { /** @var Style $parent */ $parent = $this->parent; return $parent->getSharedComponent()->getFill(); } /** * Build style array from subcomponents. */ public function getStyleArray(array $array): array { return ['fill' => $array]; } /** * Apply styles from array. * * * $spreadsheet->getActiveSheet()->getStyle('B2')->getFill()->applyFromArray( * [ * 'fillType' => Fill::FILL_GRADIENT_LINEAR, * 'rotation' => 0.0, * 'startColor' => [ * 'rgb' => '000000' * ], * 'endColor' => [ * 'argb' => 'FFFFFFFF' * ] * ] * ); * * * @param array $styleArray Array containing style information * * @return $this */ public function applyFromArray(array $styleArray) { if ($this->isSupervisor) { $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($styleArray)); } else { if (isset($styleArray['fillType'])) { $this->setFillType($styleArray['fillType']); } if (isset($styleArray['rotation'])) { $this->setRotation($styleArray['rotation']); } if (isset($styleArray['startColor'])) { $this->getStartColor()->applyFromArray($styleArray['startColor']); } if (isset($styleArray['endColor'])) { $this->getEndColor()->applyFromArray($styleArray['endColor']); } if (isset($styleArray['color'])) { $this->getStartColor()->applyFromArray($styleArray['color']); $this->getEndColor()->applyFromArray($styleArray['color']); } } return $this; } /** * Get Fill Type. */ public function getFillType(): ?string { if ($this->isSupervisor) { return $this->getSharedComponent()->getFillType(); } return $this->fillType; } /** * Set Fill Type. * * @param string $fillType Fill type, see self::FILL_* * * @return $this */ public function setFillType(string $fillType) { if ($this->isSupervisor) { $styleArray = $this->getStyleArray(['fillType' => $fillType]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); } else { $this->fillType = $fillType; } return $this; } /** * Get Rotation. */ public function getRotation(): float { if ($this->isSupervisor) { return $this->getSharedComponent()->getRotation(); } return $this->rotation; } /** * Set Rotation. * * @return $this */ public function setRotation(float $angleInDegrees) { if ($this->isSupervisor) { $styleArray = $this->getStyleArray(['rotation' => $angleInDegrees]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); } else { $this->rotation = $angleInDegrees; } return $this; } /** * Get Start Color. */ public function getStartColor(): Color { return $this->startColor; } /** * Set Start Color. * * @return $this */ public function setStartColor(Color $color) { $this->colorChanged = true; // make sure parameter is a real color and not a supervisor $color = $color->getIsSupervisor() ? $color->getSharedComponent() : $color; if ($this->isSupervisor) { $styleArray = $this->getStartColor()->getStyleArray(['argb' => $color->getARGB()]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); } else { $this->startColor = $color; } return $this; } /** * Get End Color. */ public function getEndColor(): Color { return $this->endColor; } /** * Set End Color. * * @return $this */ public function setEndColor(Color $color) { $this->colorChanged = true; // make sure parameter is a real color and not a supervisor $color = $color->getIsSupervisor() ? $color->getSharedComponent() : $color; if ($this->isSupervisor) { $styleArray = $this->getEndColor()->getStyleArray(['argb' => $color->getARGB()]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); } else { $this->endColor = $color; } return $this; } public function getColorsChanged(): bool { if ($this->isSupervisor) { $changed = $this->getSharedComponent()->colorChanged; } else { $changed = $this->colorChanged; } return $changed || $this->startColor->getHasChanged() || $this->endColor->getHasChanged(); } /** * Get hash code. * * @return string Hash code */ public function getHashCode(): string { if ($this->isSupervisor) { return $this->getSharedComponent()->getHashCode(); } // Note that we don't care about colours for fill type NONE, but could have duplicate NONEs with // different hashes if we don't explicitly prevent this return md5( $this->getFillType() . $this->getRotation() . ($this->getFillType() !== self::FILL_NONE ? $this->getStartColor()->getHashCode() : '') . ($this->getFillType() !== self::FILL_NONE ? $this->getEndColor()->getHashCode() : '') . ((string) $this->getColorsChanged()) . __CLASS__ ); } protected function exportArray1(): array { $exportedArray = []; $this->exportArray2($exportedArray, 'fillType', $this->getFillType()); $this->exportArray2($exportedArray, 'rotation', $this->getRotation()); if ($this->getColorsChanged()) { $this->exportArray2($exportedArray, 'endColor', $this->getEndColor()); $this->exportArray2($exportedArray, 'startColor', $this->getStartColor()); } return $exportedArray; } } PK!4;;1libraries/vendor/PhpSpreadsheet/Style/RgbTint.phpnu[= 0.0) ? $hue : (1.0 + $hue); } /** * Convert red/green/blue to HLSMAX-based hue/luminance/saturation. * * @return int[] */ private static function rgbToMsHls(int $red, int $green, int $blue): array { $red01 = $red / self::RGBMAX; $green01 = $green / self::RGBMAX; $blue01 = $blue / self::RGBMAX; [$hue, $luminance, $saturation] = self::rgbToHls($red01, $green01, $blue01); return [ (int) round($hue * self::HLSMAX), (int) round($luminance * self::HLSMAX), (int) round($saturation * self::HLSMAX), ]; } /** * Converts HLSMAX based HLS values to rgb values in the range (0,1). * * @return float[] */ private static function msHlsToRgb(int $hue, int $lightness, int $saturation): array { return self::hlsToRgb($hue / self::HLSMAX, $lightness / self::HLSMAX, $saturation / self::HLSMAX); } /** * Tints HLSMAX based luminance. * * @see http://ciintelligence.blogspot.co.uk/2012/02/converting-excel-theme-color-and-tint.html */ private static function tintLuminance(float $tint, float $luminance): int { if ($tint < 0) { return (int) round($luminance * (1.0 + $tint)); } return (int) round($luminance * (1.0 - $tint) + (self::HLSMAX - self::HLSMAX * (1.0 - $tint))); } /** * Return result of tinting supplied rgb as 6 hex digits. */ public static function rgbAndTintToRgb(int $red, int $green, int $blue, float $tint): string { [$hue, $luminance, $saturation] = self::rgbToMsHls($red, $green, $blue); [$red, $green, $blue] = self::msHlsToRgb($hue, self::tintLuminance($tint, $luminance), $saturation); return sprintf( '%02X%02X%02X', (int) round($red * self::RGBMAX), (int) round($green * self::RGBMAX), (int) round($blue * self::RGBMAX) ); } } PK!u((Flibraries/vendor/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.phpnu[ 0); return [ implode('.', $masks), implode('.', array_reverse($postDecimalMasks)), ]; } /** * @param mixed $number */ private static function processComplexNumberFormatMask($number, string $mask): string { /** @var string $result */ $result = $number; $maskingBlockCount = preg_match_all('/0+/', $mask, $maskingBlocks, PREG_OFFSET_CAPTURE); if ($maskingBlockCount > 1) { $maskingBlocks = array_reverse($maskingBlocks[0]); $offset = 0; foreach ($maskingBlocks as $block) { $size = strlen($block[0]); $divisor = 10 ** $size; $offset = $block[1]; /** @var float $numberFloat */ $numberFloat = $number; $blockValue = sprintf("%0{$size}d", fmod($numberFloat, $divisor)); $number = floor($numberFloat / $divisor); $mask = substr_replace($mask, $blockValue, $offset, $size); } /** @var string $numberString */ $numberString = $number; if ($number > 0) { $mask = substr_replace($mask, $numberString, $offset, 0); } $result = $mask; } return self::makeString($result); } /** * @param mixed $number */ private static function complexNumberFormatMask($number, string $mask, bool $splitOnPoint = true): string { /** @var float $numberFloat */ $numberFloat = $number; if ($splitOnPoint) { $masks = explode('.', $mask); if (count($masks) <= 2) { $decmask = $masks[1] ?? ''; $decpos = substr_count($decmask, '0'); $numberFloat = round($numberFloat, $decpos); } } $sign = ($numberFloat < 0.0) ? '-' : ''; $number = self::f2s(abs($numberFloat)); if ($splitOnPoint && str_contains($mask, '.') && str_contains($number, '.')) { $numbers = explode('.', $number); $masks = explode('.', $mask); if (count($masks) > 2) { $masks = self::mergeComplexNumberFormatMasks($numbers, $masks); } $integerPart = self::complexNumberFormatMask($numbers[0], $masks[0], false); $numlen = strlen($numbers[1]); $msklen = strlen($masks[1]); if ($numlen < $msklen) { $numbers[1] .= str_repeat('0', $msklen - $numlen); } $decimalPart = strrev(self::complexNumberFormatMask(strrev($numbers[1]), strrev($masks[1]), false)); $decimalPart = substr($decimalPart, 0, $msklen); return "{$sign}{$integerPart}.{$decimalPart}"; } if (strlen($number) < strlen($mask)) { $number = str_repeat('0', strlen($mask) - strlen($number)) . $number; } $result = self::processComplexNumberFormatMask($number, $mask); return "{$sign}{$result}"; } public static function f2s(float $f): string { return self::floatStringConvertScientific((string) $f); } public static function floatStringConvertScientific(string $s): string { // convert only normalized form of scientific notation: // optional sign, single digit 1-9, // decimal point and digits (allowed to be omitted), // E (e permitted), optional sign, one or more digits if (preg_match('/^([+-])?([1-9])([.]([0-9]+))?[eE]([+-]?[0-9]+)$/', $s, $matches) === 1) { $exponent = (int) $matches[5]; $sign = ($matches[1] === '-') ? '-' : ''; if ($exponent >= 0) { $exponentPlus1 = $exponent + 1; $out = $matches[2] . $matches[4]; $len = strlen($out); if ($len < $exponentPlus1) { $out .= str_repeat('0', $exponentPlus1 - $len); } $out = substr($out, 0, $exponentPlus1) . ((strlen($out) === $exponentPlus1) ? '' : ('.' . substr($out, $exponentPlus1))); $s = "$sign$out"; } else { $s = $sign . '0.' . str_repeat('0', -$exponent - 1) . $matches[2] . $matches[4]; } } return $s; } /** * @param mixed $value */ private static function formatStraightNumericValue($value, string $format, array $matches, bool $useThousands): string { /** @var float $valueFloat */ $valueFloat = $value; $left = $matches[1]; $dec = $matches[2]; $right = $matches[3]; // minimun width of formatted number (including dot) $minWidth = strlen($left) + strlen($dec) + strlen($right); if ($useThousands) { $value = number_format( $valueFloat, strlen($right), StringHelper::getDecimalSeparator(), StringHelper::getThousandsSeparator() ); return self::pregReplace(self::NUMBER_REGEX, $value, $format); } if (preg_match('/[0#]E[+-]0/i', $format)) { // Scientific format $decimals = strlen($right); $size = $decimals + 3; return sprintf("%{$size}.{$decimals}E", $valueFloat); } if (preg_match('/0([^\d\.]+)0/', $format) || substr_count($format, '.') > 1) { if ($valueFloat == floor($valueFloat) && substr_count($format, '.') === 1) { $value *= 10 ** strlen(explode('.', $format)[1]); //* @phpstan-ignore-line } $result = self::complexNumberFormatMask($value, $format); if (str_contains($result, 'E')) { // This is a hack and doesn't match Excel. // It will, at least, be an accurate representation, // even if formatted incorrectly. // This is needed for absolute values >=1E18. $result = self::f2s($valueFloat); } return $result; } $sprintf_pattern = "%0$minWidth." . strlen($right) . 'F'; /** @var float $valueFloat */ $valueFloat = $value; $value = self::adjustSeparators(sprintf($sprintf_pattern, round($valueFloat, strlen($right)))); return self::pregReplace(self::NUMBER_REGEX, $value, $format); } /** @param mixed $value value to be formatted */ public static function format($value, string $format): string { // The "_" in this string has already been stripped out, // so this test is never true. Furthermore, testing // on Excel shows this format uses Euro symbol, not "EUR". // if ($format === NumberFormat::FORMAT_CURRENCY_EUR_SIMPLE) { // return 'EUR ' . sprintf('%1.2f', $value); // } $baseFormat = $format; $useThousands = self::areThousandsRequired($format); $scale = self::scaleThousandsMillions($format); if (preg_match('/[#\?0]?.*[#\?0]\/(\?+|\d+|#)/', $format)) { // It's a dirty hack; but replace # and 0 digit placeholders with ? $format = (string) preg_replace('/[#0]+\//', '?/', $format); $format = (string) preg_replace('/\/[#0]+/', '/?', $format); $value = FractionFormatter::format($value, $format); } else { // Handle the number itself // scale number $value = $value / $scale; $paddingPlaceholder = (str_contains($format, '?')); // Replace # or ? with 0 $format = self::pregReplace('/[\#\?](?=(?:[^"]*"[^"]*")*[^"]*\Z)/', '0', $format); // Remove locale code [$-###] for an LCID $format = self::pregReplace('/\[\$\-.*\]/', '', $format); $n = '/\[[^\]]+\]/'; $m = self::pregReplace($n, '', $format); // Some non-number strings are quoted, so we'll get rid of the quotes, likewise any positional * symbols $format = self::makeString(str_replace(['"', '*'], '', $format)); if (preg_match(self::NUMBER_REGEX, $m, $matches)) { // There are placeholders for digits, so inject digits from the value into the mask $value = self::formatStraightNumericValue($value, $format, $matches, $useThousands); if ($paddingPlaceholder === true) { $value = self::padValue($value, $baseFormat); } } elseif ($format !== NumberFormat::FORMAT_GENERAL) { // Yes, I know that this is basically just a hack; // if there's no placeholders for digits, just return the format mask "as is" $value = self::makeString(str_replace('?', '', $format)); } } if (preg_match('/\[\$(.*)\]/u', $format, $m)) { // Currency or Accounting $value = preg_replace('/-0+(( |\xc2\xa0))?\[/', '- [', (string) $value) ?? $value; $currencyCode = $m[1]; [$currencyCode] = explode('-', $currencyCode); if ($currencyCode == '') { $currencyCode = StringHelper::getCurrencyCode(); } $value = self::pregReplace('/\[\$([^\]]*)\]/u', $currencyCode, (string) $value); } if ( (str_contains((string) $value, '0.')) && ((str_contains($baseFormat, '#.')) || (str_contains($baseFormat, '?.'))) ) { $value = preg_replace('/(\b)0\.|([^\d])0\./', '${2}.', (string) $value); } return (string) $value; } /** * @param mixed[]|string $value */ private static function makeString($value): string { return is_array($value) ? '' : "$value"; } private static function pregReplace(string $pattern, string $replacement, string $subject): string { return self::makeString(preg_replace($pattern, $replacement, $subject) ?? ''); } public static function padValue(string $value, string $baseFormat): string { $preDecimal = $postDecimal = ''; $pregArray = preg_split('/\.(?=(?:[^"]*"[^"]*")*[^"]*\Z)/miu', $baseFormat . '.?'); if (is_array($pregArray)) { $preDecimal = $pregArray[0] ?? ''; $postDecimal = $pregArray[1] ?? ''; } $length = strlen($value); if (str_contains($postDecimal, '?')) { $value = str_pad(rtrim($value, '0. '), $length, ' ', STR_PAD_RIGHT); } if (str_contains($preDecimal, '?')) { $value = str_pad(ltrim($value, '0, '), $length, ' ', STR_PAD_LEFT); } return $value; } /** * Find out if we need thousands separator * This is indicated by a comma enclosed by a digit placeholders: #, 0 or ? */ public static function areThousandsRequired(string &$format): bool { $useThousands = (bool) preg_match('/([#\?0]),([#\?0])/', $format); if ($useThousands) { $format = self::pregReplace('/([#\?0]),([#\?0])/', '${1}${2}', $format); } return $useThousands; } /** * Scale thousands, millions,... * This is indicated by a number of commas after a digit placeholder: #, or 0.0,, or ?,. */ public static function scaleThousandsMillions(string &$format): int { $scale = 1; // same as no scale if (preg_match('/(#|0|\?)(,+)/', $format, $matches)) { $scale = 1000 ** strlen($matches[2]); // strip the commas $format = self::pregReplace('/([#\?0]),+/', '${1}', $format); } return $scale; } } PK! ;zzJlibraries/vendor/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.phpnu[ 0); $replacement = "0{$wholePartSize}.{$decimalPartSize}"; $mask = (string) preg_replace('/[#0,]+\.?[?#0,]*/ui', "%{$replacement}F{$placeHolders}", $format); /** @var float $valueFloat */ $valueFloat = $value; return self::adjustSeparators(sprintf($mask, round($valueFloat, $decimalPartSize))); } } PK!2 @libraries/vendor/PhpSpreadsheet/Style/NumberFormat/Formatter.phpnu[': return $value > $comparisonValue; case '<': return $value < $comparisonValue; case '<=': return $value <= $comparisonValue; case '<>': return $value != $comparisonValue; case '=': return $value == $comparisonValue; default: return $value >= $comparisonValue; } } /** @param mixed $value value to be formatted */ private static function splitFormatForSectionSelection(array $sections, $value): array { // Extract the relevant section depending on whether number is positive, negative, or zero? // Text not supported yet. // Here is how the sections apply to various values in Excel: // 1 section: [POSITIVE/NEGATIVE/ZERO/TEXT] // 2 sections: [POSITIVE/ZERO/TEXT] [NEGATIVE] // 3 sections: [POSITIVE/TEXT] [NEGATIVE] [ZERO] // 4 sections: [POSITIVE] [NEGATIVE] [ZERO] [TEXT] $sectionCount = count($sections); // Colour could be a named colour, or a numeric index entry in the colour-palette $color_regex = '/\[(' . implode('|', Color::NAMED_COLORS) . '|color\s*(\d+))\]/mui'; $cond_regex = '/\[(>|>=|<|<=|=|<>)([+-]?\d+([.]\d+)?)\]/'; $colors = ['', '', '', '', '']; $conditionOperations = ['', '', '', '', '']; $conditionComparisonValues = [0, 0, 0, 0, 0]; for ($idx = 0; $idx < $sectionCount; ++$idx) { if (preg_match($color_regex, $sections[$idx], $matches)) { if (isset($matches[2])) { $colors[$idx] = '#' . BIFF8::lookup((int) $matches[2] + 7)['rgb']; } else { $colors[$idx] = $matches[0]; } $sections[$idx] = (string) preg_replace($color_regex, '', $sections[$idx]); } if (preg_match($cond_regex, $sections[$idx], $matches)) { $conditionOperations[$idx] = $matches[1]; $conditionComparisonValues[$idx] = $matches[2]; $sections[$idx] = (string) preg_replace($cond_regex, '', $sections[$idx]); } } $color = $colors[0]; $format = $sections[0]; $absval = $value; switch ($sectionCount) { case 2: $absval = abs($value + 0); if (!self::splitFormatComparison($value, $conditionOperations[0], $conditionComparisonValues[0], '>=', 0)) { $color = $colors[1]; $format = $sections[1]; } break; case 3: case 4: $absval = abs($value + 0); if (!self::splitFormatComparison($value, $conditionOperations[0], $conditionComparisonValues[0], '>', 0)) { if (self::splitFormatComparison($value, $conditionOperations[1], $conditionComparisonValues[1], '<', 0)) { $color = $colors[1]; $format = $sections[1]; } else { $color = $colors[2]; $format = $sections[2]; } } break; } return [$color, $format, $absval]; } /** * Convert a value in a pre-defined format to a PHP string. * * @param null|array|bool|float|int|RichText|string $value Value to format * @param string $format Format code: see = self::FORMAT_* for predefined values; * or can be any valid MS Excel custom format string * @param null|array|callable $callBack Callback function for additional formatting of string * * @return string Formatted string */ public static function toFormattedString($value, string $format, $callBack = null): string { while (is_array($value)) { $value = array_shift($value); } if (is_bool($value)) { return $value ? Calculation::getTRUE() : Calculation::getFALSE(); } // For now we do not treat strings in sections, although section 4 of a format code affects strings // Process a single block format code containing @ for text substitution $formatx = str_replace('\"', self::QUOTE_REPLACEMENT, $format); if (preg_match(self::SECTION_SPLIT, $format) === 0 && preg_match(self::SYMBOL_AT, $formatx) === 1) { if (!str_contains($format, '"')) { return str_replace('@', StringHelper::convertToString($value), $format); } //escape any dollar signs on the string, so they are not replaced with an empty value $value = str_replace( ['$', '"'], ['\$', self::QUOTE_REPLACEMENT], StringHelper::convertToString($value) ); return str_replace( ['"', self::QUOTE_REPLACEMENT], ['', '"'], preg_replace(self::SYMBOL_AT, $value, $formatx) ?? $value ); } // If we have a text value, return it "as is" if (!is_numeric($value)) { return StringHelper::convertToString($value); } // For 'General' format code, we just pass the value although this is not entirely the way Excel does it, // it seems to round numbers to a total of 10 digits. if (($format === NumberFormat::FORMAT_GENERAL) || ($format === NumberFormat::FORMAT_TEXT)) { return self::adjustSeparators((string) $value); } // Ignore square-$-brackets prefix in format string, like "[$-411]ge.m.d", "[$-010419]0%", etc $format = (string) preg_replace('/^\[\$-[^\]]*\]/', '', $format); $format = (string) preg_replace_callback( '/(["])(?:(?=(\\\?))\2.)*?\1/u', fn (array $matches): string => str_replace('.', chr(0x00), $matches[0]), $format ); // Convert any other escaped characters to quoted strings, e.g. (\T to "T") $format = (string) preg_replace('/(\\\(((.)(?!((AM\/PM)|(A\/P))))|([^ ])))(?=(?:[^"]|"[^"]*")*$)/ui', '"${2}"', $format); // Get the sections, there can be up to four sections, separated with a semi-colon (but only if not a quoted literal) $sections = preg_split(self::SECTION_SPLIT, $format) ?: []; [$colors, $format, $value] = self::splitFormatForSectionSelection($sections, $value); // In Excel formats, "_" is used to add spacing, // The following character indicates the size of the spacing, which we can't do in HTML, so we just use a standard space $format = (string) preg_replace('/_.?/ui', ' ', $format); // Let's begin inspecting the format and converting the value to a formatted string if ( // Check for date/time characters (not inside quotes) (preg_match('/(\[\$[A-Z]*-[0-9A-F]*\])*[hmsdy](?=(?:[^"]|"[^"]*")*$)/miu', $format)) // Look out for Currency formats Issue 4124 && !(preg_match('/\[\$[A-Z]{3}\]/miu', $format)) // A date/time with a decimal time shouldn't have a digit placeholder before the decimal point && (preg_match('/[0\?#]\.(?![^\[]*\])/miu', $format) === 0) ) { // datetime format $value = DateFormatter::format($value, $format); } else { if (str_starts_with($format, '"') && str_ends_with($format, '"') && substr_count($format, '"') === 2) { $value = substr($format, 1, -1); } elseif (preg_match('/[0#, ]%/', $format)) { // % number format - avoid weird '-0' problem $value = PercentageFormatter::format(0 + (float) $value, $format); } else { $value = NumberFormatter::format($value, $format); } } // Additional formatting provided by callback function if (is_callable($callBack)) { $value = $callBack($value, $colors); } return str_replace(chr(0x00), '.', $value); } } PK!vDlibraries/vendor/PhpSpreadsheet/Style/NumberFormat/BaseFormatter.phpnu[ '', // 12-hour suffix 'am/pm' => 'A', // 4-digit year 'e' => 'Y', 'yyyy' => 'Y', // 2-digit year 'yy' => 'y', // first letter of month - no php equivalent 'mmmmm' => 'M', // full month name 'mmmm' => 'F', // short month name 'mmm' => 'M', // mm is minutes if time, but can also be month w/leading zero // so we try to identify times be the inclusion of a : separator in the mask // It isn't perfect, but the best way I know how ':mm' => ':i', 'mm:' => 'i:', // full day of week name 'dddd' => 'l', // short day of week name 'ddd' => 'D', // days leading zero 'dd' => 'd', // days no leading zero 'd' => 'j', // fractional seconds - no php equivalent '.s' => '', ]; /** * Search/replace values to convert Excel date/time format masks hours to PHP format masks (24 hr clock). */ private const DATE_FORMAT_REPLACEMENTS24 = [ 'hh' => 'H', 'h' => 'G', // month leading zero 'mm' => 'm', // month no leading zero 'm' => 'n', // seconds 'ss' => 's', ]; /** * Search/replace values to convert Excel date/time format masks hours to PHP format masks (12 hr clock). */ private const DATE_FORMAT_REPLACEMENTS12 = [ 'hh' => 'h', 'h' => 'g', // month leading zero 'mm' => 'm', // month no leading zero 'm' => 'n', // seconds 'ss' => 's', ]; private const HOURS_IN_DAY = 24; private const MINUTES_IN_DAY = 60 * self::HOURS_IN_DAY; private const SECONDS_IN_DAY = 60 * self::MINUTES_IN_DAY; private const INTERVAL_PRECISION = 10; private const INTERVAL_LEADING_ZERO = [ '[hh]', '[mm]', '[ss]', ]; private const INTERVAL_ROUND_PRECISION = [ // hours and minutes truncate '[h]' => self::INTERVAL_PRECISION, '[hh]' => self::INTERVAL_PRECISION, '[m]' => self::INTERVAL_PRECISION, '[mm]' => self::INTERVAL_PRECISION, // seconds round '[s]' => 0, '[ss]' => 0, ]; private const INTERVAL_MULTIPLIER = [ '[h]' => self::HOURS_IN_DAY, '[hh]' => self::HOURS_IN_DAY, '[m]' => self::MINUTES_IN_DAY, '[mm]' => self::MINUTES_IN_DAY, '[s]' => self::SECONDS_IN_DAY, '[ss]' => self::SECONDS_IN_DAY, ]; /** @param mixed $value */ private static function tryInterval(bool &$seekingBracket, string &$block, $value, string $format): void { if ($seekingBracket) { if (str_contains($block, $format)) { $hours = (string) (int) round( self::INTERVAL_MULTIPLIER[$format] * $value, self::INTERVAL_ROUND_PRECISION[$format] ); if (strlen($hours) === 1 && in_array($format, self::INTERVAL_LEADING_ZERO, true)) { $hours = "0$hours"; } $block = str_replace($format, $hours, $block); $seekingBracket = false; } } } /** @param mixed $value value to be formatted */ public static function format($value, string $format): string { // strip off first part containing e.g. [$-F800] or [$USD-409] // general syntax: [$-] // language info is in hexadecimal // strip off chinese part like [DBNum1][$-804] $format = (string) preg_replace('/^(\[DBNum\d\])*(\[\$[^\]]*\])/i', '', $format); // OpenOffice.org uses upper-case number formats, e.g. 'YYYY', convert to lower-case; // but we don't want to change any quoted strings /** @var callable $callable */ $callable = [self::class, 'setLowercaseCallback']; $format = (string) preg_replace_callback('/(?:^|")([^"]*)(?:$|")/', $callable, $format); // Only process the non-quoted blocks for date format characters $blocks = explode('"', $format); foreach ($blocks as $key => &$block) { if ($key % 2 == 0) { $block = strtr($block, self::DATE_FORMAT_REPLACEMENTS); if (!strpos($block, 'A')) { // 24-hour time format // when [h]:mm format, the [h] should replace to the hours of the value * 24 $seekingBracket = true; self::tryInterval($seekingBracket, $block, $value, '[h]'); self::tryInterval($seekingBracket, $block, $value, '[hh]'); self::tryInterval($seekingBracket, $block, $value, '[mm]'); self::tryInterval($seekingBracket, $block, $value, '[m]'); self::tryInterval($seekingBracket, $block, $value, '[s]'); self::tryInterval($seekingBracket, $block, $value, '[ss]'); $block = strtr($block, self::DATE_FORMAT_REPLACEMENTS24); } else { // 12-hour time format $block = strtr($block, self::DATE_FORMAT_REPLACEMENTS12); } } } $format = implode('"', $blocks); // escape any quoted characters so that DateTime format() will render them correctly /** @var callable $callback */ $callback = [self::class, 'escapeQuotesCallback']; $format = (string) preg_replace_callback('/"(.*)"/U', $callback, $format); $dateObj = Date::excelToDateTimeObject($value); // If the colon preceding minute had been quoted, as happens in // Excel 2003 XML formats, m will not have been changed to i above. // Change it now. $format = (string) \preg_replace('/\\\:m/', ':i', $format); $microseconds = (int) $dateObj->format('u'); if (str_contains($format, ':s.000')) { $milliseconds = (int) round($microseconds / 1000.0); if ($milliseconds === 1000) { $milliseconds = 0; $dateObj->modify('+1 second'); } $dateObj->modify("-$microseconds microseconds"); $format = str_replace(':s.000', ':s.' . sprintf('%03d', $milliseconds), $format); } elseif (str_contains($format, ':s.00')) { $centiseconds = (int) round($microseconds / 10000.0); if ($centiseconds === 100) { $centiseconds = 0; $dateObj->modify('+1 second'); } $dateObj->modify("-$microseconds microseconds"); $format = str_replace(':s.00', ':s.' . sprintf('%02d', $centiseconds), $format); } elseif (str_contains($format, ':s.0')) { $deciseconds = (int) round($microseconds / 100000.0); if ($deciseconds === 10) { $deciseconds = 0; $dateObj->modify('+1 second'); } $dateObj->modify("-$microseconds microseconds"); $format = str_replace(':s.0', ':s.' . sprintf('%1d', $deciseconds), $format); } else { // no fractional second if ($microseconds >= 500000) { $dateObj->modify('+1 second'); } $dateObj->modify("-$microseconds microseconds"); } return $dateObj->format($format); } private static function setLowercaseCallback(array $matches): string { return mb_strtolower($matches[0]); } private static function escapeQuotesCallback(array $matches): string { return '\\' . implode('\\', mb_str_split($matches[1], 1, 'UTF-8')); } } PK!ZM1,AAFlibraries/vendor/PhpSpreadsheet/Style/NumberFormat/Wizard/Duration.phpnu[ self::DAYS_DURATION, self::HOURS_DURATION => self::HOURS_SHORT, self::MINUTES_DURATION => self::MINUTES_LONG, self::SECONDS_DURATION => self::SECONDS_LONG, ]; protected const DURATION_DEFAULTS = [ self::HOURS_LONG => self::HOURS_DURATION, self::HOURS_SHORT => self::HOURS_DURATION, self::MINUTES_LONG => self::MINUTES_DURATION, self::MINUTES_SHORT => self::MINUTES_DURATION, self::SECONDS_LONG => self::SECONDS_DURATION, self::SECONDS_SHORT => self::SECONDS_DURATION, ]; public const SEPARATOR_COLON = ':'; public const SEPARATOR_SPACE_NONBREAKING = "\u{a0}"; public const SEPARATOR_SPACE = ' '; public const DURATION_DEFAULT = [ self::HOURS_DURATION, self::MINUTES_LONG, self::SECONDS_LONG, ]; /** * @var string[] */ protected array $separators; /** * @var string[] */ protected array $formatBlocks; protected bool $durationIsSet = false; /** * @param null|string|string[] $separators * If you want to use the same separator for all format blocks, then it can be passed as a string literal; * if you wish to use different separators, then they should be passed as an array. * If you want to use only a single format block, then pass a null as the separator argument */ public function __construct($separators = self::SEPARATOR_COLON, string ...$formatBlocks) { $separators ??= self::SEPARATOR_COLON; $formatBlocks = (count($formatBlocks) === 0) ? self::DURATION_DEFAULT : $formatBlocks; $this->separators = $this->padSeparatorArray( is_array($separators) ? $separators : [$separators], count($formatBlocks) - 1 ); $this->formatBlocks = array_map([$this, 'mapFormatBlocks'], $formatBlocks); if ($this->durationIsSet === false) { // We need at least one duration mask, so if none has been set we change the first mask element // to a duration. $this->formatBlocks[0] = self::DURATION_DEFAULTS[mb_strtolower($this->formatBlocks[0])]; } } private function mapFormatBlocks(string $value): string { // Any duration masking codes are returned as lower case values if (in_array(mb_strtolower($value), self::DURATION_BLOCKS, true)) { if (array_key_exists(mb_strtolower($value), self::DURATION_MASKS)) { if ($this->durationIsSet) { // We should only have a single duration mask, the first defined in the mask set, // so convert any additional duration masks to standard time masks. $value = self::DURATION_MASKS[mb_strtolower($value)]; } $this->durationIsSet = true; } return mb_strtolower($value); } // Wrap any string literals in quotes, so that they're clearly defined as string literals return $this->wrapLiteral($value); } public function format(): string { return implode('', array_map([$this, 'intersperse'], $this->formatBlocks, $this->separators)); } } PK!uRHlibraries/vendor/PhpSpreadsheet/Style/NumberFormat/Wizard/Percentage.phpnu[setDecimals($decimals); $this->setLocale($locale); } protected function getLocaleFormat(): string { $formatter = new Locale($this->fullLocale, NumberFormatter::PERCENT); return $this->decimals > 0 ? str_replace('0', '0.' . str_repeat('0', $this->decimals), $formatter->format()) : $formatter->format(); } public function format(): string { if ($this->localeFormat !== null) { return $this->localeFormat; } return sprintf('0%s%%', $this->decimals > 0 ? '.' . str_repeat('0', $this->decimals) : null); } } PK!` 4Hlibraries/vendor/PhpSpreadsheet/Style/NumberFormat/Wizard/Accounting.phpnu[fullLocale, NumberFormatter::CURRENCY_ACCOUNTING); $mask = $formatter->format($this->stripLeadingRLM); if ($this->decimals === 0) { $mask = (string) preg_replace('/\.0+/miu', '', $mask); } return str_replace('¤', $this->formatCurrencyCode(), $mask); } public static function icuVersion(): float { [$major, $minor] = explode('.', INTL_ICU_VERSION); return (float) "{$major}.{$minor}"; } private function formatCurrencyCode(): string { if ($this->locale === null) { return $this->currencyCode . '*'; } return "[\${$this->currencyCode}-{$this->locale}]"; } } PK!yBBHlibraries/vendor/PhpSpreadsheet/Style/NumberFormat/Wizard/NumberBase.phpnu[decimals = ($decimals > self::MAX_DECIMALS) ? self::MAX_DECIMALS : max($decimals, 0); } /** * Setting a locale will override any settings defined in this class. * * @throws Exception If the locale code is not a valid format */ public function setLocale(?string $locale = null): void { if ($locale === null) { $this->localeFormat = $this->locale = $this->fullLocale = null; return; } $this->locale = $this->validateLocale($locale); if (class_exists(NumberFormatter::class)) { $this->localeFormat = $this->getLocaleFormat(); } } /** * Stub: should be implemented as a concrete method in concrete wizards. */ abstract protected function getLocaleFormat(): string; /** * @throws Exception If the locale code is not a valid format */ private function validateLocale(string $locale): string { if (preg_match(Locale::STRUCTURE, $locale, $matches, PREG_UNMATCHED_AS_NULL) !== 1) { throw new Exception("Invalid locale code '{$locale}'"); } ['language' => $language, 'script' => $script, 'country' => $country] = $matches; // Set case and separator to match standardised locale case $language = strtolower($language); $script = ($script === null) ? null : ucfirst(strtolower($script)); $country = ($country === null) ? null : strtoupper($country); $this->fullLocale = implode('-', array_filter([$language, $script, $country])); return $country === null ? $language : "{$language}-{$country}"; } public function format(): string { return NumberFormat::FORMAT_GENERAL; } public function __toString(): string { return $this->format(); } } PK!Clibraries/vendor/PhpSpreadsheet/Style/NumberFormat/Wizard/index.phpnu[setCurrencyCode($currencyCode); $this->setThousandsSeparator($thousandsSeparator); $this->setDecimals($decimals); $this->setCurrencySymbolPosition($currencySymbolPosition); $this->setCurrencySymbolSpacing($currencySymbolSpacing); $this->setLocale($locale); $this->stripLeadingRLM = $stripLeadingRLM; $this->negative = $negative; } public function setCurrencyCode(string $currencyCode): void { $this->currencyCode = $currencyCode; } public function setCurrencySymbolPosition(bool $currencySymbolPosition = self::LEADING_SYMBOL): void { $this->currencySymbolPosition = $currencySymbolPosition; } public function setCurrencySymbolSpacing(bool $currencySymbolSpacing = self::SYMBOL_WITHOUT_SPACING): void { $this->currencySymbolSpacing = $currencySymbolSpacing; } public function setStripLeadingRLM(bool $stripLeadingRLM): void { $this->stripLeadingRLM = $stripLeadingRLM; } /** * @param mixed $negative */ public function setNegative($negative): void { $this->negative = $negative; } protected function getLocaleFormat(): string { $formatter = new Locale($this->fullLocale, NumberFormatter::CURRENCY); $mask = $formatter->format($this->stripLeadingRLM); if ($this->decimals === 0) { $mask = (string) preg_replace('/\.0+/miu', '', $mask); } return str_replace('¤', $this->formatCurrencyCode(), $mask); } private function formatCurrencyCode(): string { if ($this->locale === null) { return $this->currencyCode; } return "[\${$this->currencyCode}-{$this->locale}]"; } public function format(): string { if ($this->localeFormat !== null) { return $this->localeFormat; } $symbolWithSpacing = $this->overrideSpacing ?? ($this->currencySymbolSpacing === self::SYMBOL_WITH_SPACING); $negative = $this->overrideNegative ?? $this->negative; // format if positive $format = '_('; if ($this->currencySymbolPosition === self::LEADING_SYMBOL) { $format .= '"' . $this->currencyCode . '"'; if (preg_match('/^[A-Z]{3}$/i', $this->currencyCode) === 1) { $format .= $this->spaceOrNbsp; } if (preg_match('/^[A-Z]{3}$/i', $this->currencyCode) === 1) { $format .= $this->spaceOrNbsp; } if ($symbolWithSpacing) { $format .= '*' . $this->spaceOrNbsp; } } $format .= $this->thousandsSeparator ? '#,##0' : '0'; if ($this->decimals > 0) { $format .= '.' . str_repeat('0', $this->decimals); } if ($this->currencySymbolPosition === self::TRAILING_SYMBOL) { if ($symbolWithSpacing) { $format .= $this->spaceOrNbsp; } elseif (preg_match('/^[A-Z]{3}$/i', $this->currencyCode) === 1) { $format .= $this->spaceOrNbsp; } $format .= '[$' . $this->currencyCode . ']'; } $format .= '_)'; // format if negative $format .= ';_('; $format .= $negative->color(); $negativeStart = $negative->start(); if ($this->currencySymbolPosition === self::LEADING_SYMBOL) { if ($negativeStart === '-' && !$symbolWithSpacing) { $format .= $negativeStart; } $format .= '"' . $this->currencyCode . '"'; if (preg_match('/^[A-Z]{3}$/i', $this->currencyCode) === 1) { $format .= $this->spaceOrNbsp; } if ($symbolWithSpacing) { $format .= '*' . $this->spaceOrNbsp; } if ($negativeStart === '\(' || ($symbolWithSpacing && $negativeStart === '-')) { $format .= $negativeStart; } } else { $format .= $negative->start(); } $format .= $this->thousandsSeparator ? '#,##0' : '0'; if ($this->decimals > 0) { $format .= '.' . str_repeat('0', $this->decimals); } $format .= $negative->end(); if ($this->currencySymbolPosition === self::TRAILING_SYMBOL) { if ($symbolWithSpacing) { // Do nothing - I can't figure out how to get // everything to align if I put any kind of space here. //$format .= "\u{2009}"; } elseif (preg_match('/^[A-Z]{3}$/i', $this->currencyCode) === 1) { $format .= $this->spaceOrNbsp; } $format .= '[$' . $this->currencyCode . ']'; } if ($this->currencySymbolPosition === self::TRAILING_SYMBOL) { $format .= '_)'; } elseif ($symbolWithSpacing && $negativeStart === '-') { $format .= ' '; } // format if zero $format .= ';_('; if ($this->currencySymbolPosition === self::LEADING_SYMBOL) { $format .= '"' . $this->currencyCode . '"'; } if ($symbolWithSpacing) { if ($this->currencySymbolPosition === self::LEADING_SYMBOL) { $format .= '*' . $this->spaceOrNbsp; } $format .= '"-"'; if ($this->decimals > 0) { $format .= str_repeat('?', $this->decimals); } } else { if (preg_match('/^[A-Z]{3}$/i', $this->currencyCode) === 1) { $format .= $this->spaceOrNbsp; } $format .= '0'; if ($this->decimals > 0) { $format .= '.' . str_repeat('0', $this->decimals); } } if ($this->currencySymbolPosition === self::TRAILING_SYMBOL) { if ($symbolWithSpacing) { $format .= $this->spaceOrNbsp; } $format .= '[$' . $this->currencyCode . ']'; } $format .= '_)'; // format if text $format .= ';_(@_)'; return $format; } } PK!8Dlibraries/vendor/PhpSpreadsheet/Style/NumberFormat/Wizard/Number.phpnu[setDecimals($decimals); $this->setThousandsSeparator($thousandsSeparator); $this->setLocale($locale); } public function setThousandsSeparator(bool $thousandsSeparator = self::WITH_THOUSANDS_SEPARATOR): void { $this->thousandsSeparator = $thousandsSeparator; } /** * As MS Excel cannot easily handle Lakh, which is the only locale-specific Number format variant, * we don't use locale with Numbers. */ protected function getLocaleFormat(): string { return $this->format(); } public function format(): string { return sprintf( '%s0%s', $this->thousandsSeparator ? '#,##' : null, $this->decimals > 0 ? '.' . str_repeat('0', $this->decimals) : null ); } } PK!F77Llibraries/vendor/PhpSpreadsheet/Style/NumberFormat/Wizard/DateTimeWizard.phpnu[= "; protected function padSeparatorArray(array $separators, int $count): array { $lastSeparator = array_pop($separators); return $separators + array_fill(0, $count, $lastSeparator); } protected function escapeSingleCharacter(string $value): string { if (str_contains(self::NO_ESCAPING_NEEDED, $value)) { return $value; } return "\\{$value}"; } protected function wrapLiteral(string $value): string { if (mb_strlen($value, 'UTF-8') === 1) { return $this->escapeSingleCharacter($value); } // Wrap any other string literals in quotes, so that they're clearly defined as string literals return '"' . str_replace('"', '""', $value) . '"'; } protected function intersperse(string $formatBlock, ?string $separator): string { return "{$formatBlock}{$separator}"; } public function __toString(): string { return $this->format(); } } PK!p Blibraries/vendor/PhpSpreadsheet/Style/NumberFormat/Wizard/Time.phpnu[separators = $this->padSeparatorArray( is_array($separators) ? $separators : [$separators], count($formatBlocks) - 1 ); $this->formatBlocks = array_map([$this, 'mapFormatBlocks'], $formatBlocks); } private function mapFormatBlocks(string $value): string { // Any date masking codes are returned as lower case values // except for AM/PM, which is set to uppercase if (in_array(mb_strtolower($value), self::TIME_BLOCKS, true)) { return mb_strtolower($value); } elseif (mb_strtoupper($value) === self::MORNING_AFTERNOON) { return mb_strtoupper($value); } // Wrap any string literals in quotes, so that they're clearly defined as string literals return $this->wrapLiteral($value); } public function format(): string { return implode('', array_map([$this, 'intersperse'], $this->formatBlocks, $this->separators)); } } PK!JPW\\Flibraries/vendor/PhpSpreadsheet/Style/NumberFormat/Wizard/DateTime.phpnu[ */ protected array $formatBlocks; /** * @param null|string|string[] $separators * If you want to use only a single format block, then pass a null as the separator argument * @param DateTimeWizard|string ...$formatBlocks */ public function __construct($separators, ...$formatBlocks) { $this->separators = $this->padSeparatorArray( is_array($separators) ? $separators : [$separators], count($formatBlocks) - 1 ); $this->formatBlocks = array_map([$this, 'mapFormatBlocks'], $formatBlocks); } /** * @param \TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard\DateTimeWizard|string $value */ private function mapFormatBlocks($value): string { // Any date masking codes are returned as lower case values if (is_object($value)) { // We can't explicitly test for Stringable until PHP >= 8.0 return $value; } // Wrap any string literals in quotes, so that they're clearly defined as string literals return $this->wrapLiteral($value); } public function format(): string { return implode('', array_map([$this, 'intersperse'], $this->formatBlocks, $this->separators)); } } PK!(C C Blibraries/vendor/PhpSpreadsheet/Style/NumberFormat/Wizard/Date.phpnu[separators = $this->padSeparatorArray( is_array($separators) ? $separators : [$separators], count($formatBlocks) - 1 ); $this->formatBlocks = array_map([$this, 'mapFormatBlocks'], $formatBlocks); } private function mapFormatBlocks(string $value): string { // Any date masking codes are returned as lower case values if (in_array(mb_strtolower($value), self::DATE_BLOCKS, true)) { return mb_strtolower($value); } // Wrap any string literals in quotes, so that they're clearly defined as string literals return $this->wrapLiteral($value); } public function format(): string { return implode('', array_map([$this, 'intersperse'], $this->formatBlocks, $this->separators)); } } PK!_X..Hlibraries/vendor/PhpSpreadsheet/Style/NumberFormat/Wizard/Scientific.phpnu[setDecimals($decimals); $this->setLocale($locale); } protected function getLocaleFormat(): string { return $this->format(); } public function format(): string { return sprintf('0%sE+00', $this->decimals > 0 ? '.' . str_repeat('0', $this->decimals) : null); } } PK!llDlibraries/vendor/PhpSpreadsheet/Style/NumberFormat/Wizard/Locale.phpnu[[a-z]{2})([-_](?P