From c5eeb1751013eb05dd7ed3742e89359dd6be50bc Mon Sep 17 00:00:00 2001 From: Ohad Date: Sun, 20 Oct 2024 13:30:56 +0300 Subject: [PATCH 01/65] Initial refactor commit --- classes/database/database-constants.php | 73 ++ classes/database/entry.php | 386 ++++++++++ .../exceptions/missing-table-exception.php | 18 + classes/database/table.php | 574 +++++++++++++++ classes/logger.php | 37 + classes/module-base.php | 267 +++++++ classes/rest/route.php | 387 ++++++++++ classes/utils.php | 37 + classes/utils/assets.php | 142 ++++ includes/manager.php | 57 ++ includes/pojo-a11y-customizer.php | 285 -------- includes/pojo-a11y-settings.php | 645 ----------------- .../legacy/components/admin.php | 24 +- modules/legacy/components/customizer.php | 298 ++++++++ .../legacy/components/elementor.php | 11 +- .../legacy/components/frontend.php | 112 +-- modules/legacy/components/settings.php | 671 ++++++++++++++++++ modules/legacy/module.php | 67 ++ plugin.php | 111 +++ pojo-accessibility.php | 100 +-- 20 files changed, 3241 insertions(+), 1061 deletions(-) create mode 100644 classes/database/database-constants.php create mode 100644 classes/database/entry.php create mode 100644 classes/database/exceptions/missing-table-exception.php create mode 100644 classes/database/table.php create mode 100644 classes/logger.php create mode 100644 classes/module-base.php create mode 100644 classes/rest/route.php create mode 100644 classes/utils.php create mode 100644 classes/utils/assets.php create mode 100644 includes/manager.php delete mode 100644 includes/pojo-a11y-customizer.php delete mode 100644 includes/pojo-a11y-settings.php rename includes/pojo-a11y-admin-ui.php => modules/legacy/components/admin.php (93%) create mode 100644 modules/legacy/components/customizer.php rename includes/pojo-a11y-elementor.php => modules/legacy/components/elementor.php (66%) rename includes/pojo-a11y-frontend.php => modules/legacy/components/frontend.php (89%) create mode 100644 modules/legacy/components/settings.php create mode 100644 modules/legacy/module.php create mode 100644 plugin.php diff --git a/classes/database/database-constants.php b/classes/database/database-constants.php new file mode 100644 index 0000000..1af5f37 --- /dev/null +++ b/classes/database/database-constants.php @@ -0,0 +1,73 @@ + value entries for direct comparison + * joined with the AND logical operator, or in the format of column => [column => + * string, value =>string|int|array, comparison operator => string, + * relation_before=>string, optional, relation_after=>string, optional]. + * + * @return Entry Returns the entry with the data found. + */ + private function init_by( string $by, $value ): Entry { + $data = static::get_by( $by, $value ); + + return $this->init_by_data( $data ); + } + + /** + * get_by + * + * Returns the first row in the table that satisfies the conditions set by the parameters. + * + * @param string $by The name of the field to compare the value parameter to in case the + * /value/ parameter is a string. + * Optional. + * Defaults to an empty string. + * @param array|string $value If a string, the value to compare against the column specified + * by the /by/ parameter. + * If an array, can be either a list of column => value entries for direct comparison + * joined with the AND logical operator, or in the format of column => [column => + * string, value =>string|int|array, comparison operator => string, + * relation_before=>string, optional, relation_after=>string, optional]. Optional. + * Defaults to an empty string. + * + * @return mixed|\stdClass|null An object representing the row or NULL in case of an error. + */ + protected function get_by( string $by = '', $value = '' ) { + $fields = '*'; + $where = is_array( $value ) ? $value : [ $by => $value ]; + + return $this->db_table::first( $fields, $where ); + } + + /** + * init_by_data + * + * Returns an Entry object with its data set to the data passed by the /data/ parameter. + * + * @param array|object $data The data to set the entry with. + * + * @return $this Returns an entry loaded with the data passes in the parameter, or en Empty entry (representing + * null) on error + */ + protected function init_by_data( $data ): Entry { + if ( ! $data || is_wp_error( $data ) ) { + return $this->return_empty(); + } + + return $this->set_data( $data ); + } + + /** + * return_empty + * + * Returns an empty entry project, representing an empty set/null. + * + * NOTE: An empty entry cannot be saved in the database unless you either unset the + * /id/ field or set it for a valid value for an update. + * + * @return Entry Returns the object back + */ + private function return_empty(): Entry { + $this->set_data( [] ); + + return $this->set( 'id', 0 ); + } + + /** + * set_data + * + * Sets the data of this entry object + * + * @param array|object $data The data to set in the current object in the format of key => value + * + * @return $this Returns the object back + */ + public function set_data( $data ): Entry { + $this->entry_data = (array) $data; + + return $this; + } + + /** + * init_by_id + * + * Loads the Entry with data from the database fetched by the /id/ column compared to the value passed with the + * /id/ parameter. + * + * @param string $id The value of the /id/ field + * + * @return $this The Entry loaded with the data gotten from the database by the specified ID. + */ + protected function init_by_id( $id ): Entry { + $data = static::get_by( 'id', $id ); + + return $this->init_by_data( $data ); + } + + /** + * __get + * magic get properties + * + * Returns an Entry's data field by key name + * + * @param string $name The name of the field to return + * + * @return mixed|null The value of the field or null if it doesn't exist' + */ + public function __get( $name ) { + return $this->entry_data[ $name ] ?? null; + } + + /** + * __set + * magic set properties + * + * Sets an Entry's data field value by the specified key + * + * @param string $name The key of the field being set + * @param mixed $value The new value for the field + * + * @return $this Returns the current object back + */ + public function __set( string $name, $value ) { + return $this->set( $name, $value ); + } + + /** + * set + * + * Sets and entry data field value by the specified key + * + * @param string $name The key of the field being set + * @param mixed $value The new value for the field + * + * @return $this Returns the current object back + */ + public function set( string $name, $value ): Entry { + $this->entry_data[ $name ] = $value; + + return $this; + } + + /** + * class_short_name + * + * Returns just the name of the class, without its namespace. Used for hooks. + * Taken from https://coderwall.com/p/cpxxxw/php-get-class-name-without-namespace + * + * NOTE: Called on a class without a namespace will return the name of the class + * without the first letter in the name + * @return string The name of the current class + */ + private function class_short_name(): string { + $class_name = get_called_class(); + + return ( substr( $class_name, strrpos( $class_name, '\\' ) + 1 ) ); + } + + /** + * trigger_change + * + * Raises action hooks following the create, delete and update operations. + * Raises the hook [ea11y/db/class_name>/change]. If the /event/ + * parameter is not null, then it also raises the hook [ea11y/db/class_name/event]. + * + * The parameters sent to both hooks is a reference to the current object and the value of the /data/ parameter. + * + * @param int|bool $data Numbers of rows changed or FALSE on database action failure + * @param string|null $event The name of the custom event hook to raise in addition to the /changed/ event hook + * Optional. + * Defaults to null. In this case, will raise only the defaults /changed/ event. + */ + private function trigger_change( $data, string $event = null ): void { + if ( $event ) { + /** + * event specific + * @var Entry $this + * @var false|int $data + */ + do_action( 'ea11y/db/' . $this->class_short_name() . '/' . $event, $this, $data ); + } + + /** + * entity change + * @var Entry $this + * @var false|int $data + */ + do_action( 'ea11y/db/' . $this->class_short_name() . '/change', $this, $data ); + } + + /** + * save + * + * Writes the entry to the database. + * If the entry has a field called /id/ set, will + * perform an update, if it doesn't, will perform an insert. + * Update and create function triggers the change action hooks and the respective custom event + * @return false|int The number of rows inserted or FALSE on error. + */ + public function save() { + if ( isset( $this->entry_data[ 'id' ] ) ) { + return $this->update( [ 'id' => $this->entry_data[ 'id' ] ] ); + } + + return $this->create(); + } + + + /** + * delete + * + * Delete entries from the table. + * Bases on the field specified by the /by/ parameter and its current value. + * Triggers change action hooks and raises the /delete/ custom event. + * + * @param string $by The field name to delete by. + * Optional. + * Defaults to 'id'. + * + * @return false|int The number of rows deleted or false on error. + */ + public function delete( string $by = 'id' ) { + $results = $this->db_table::delete( [ $by => $this->{$by} ] ); + + $this->trigger_change( $results, 'delete' ); + + return $results; + } + + /** + * update + * + * Updates the database with the data of this entry based on the conditions passed + * by the /where/ parameter. + * Triggers change action hooks and raises the /update/ custom event. + * + * NOTE: If no conditions are supplied, the update is going to be performed on all rows, + * + * @param array $where Array of column => (raw) values as a group of AND WHERE conditionals for the UPDATE + * statement. Optional. Defaults to an empty array. + * + * @return false|int The number of rows updated or false on error. + */ + public function update( array $where = [] ) { + $results = $this->db_table::update( $this->entry_data, $where ); + + $this->trigger_change( $results, 'update' ); + + return $results; + } + + /** + * create + * + * Inserts the entry to the database table. + * Trigger change action hooks and raises the /create/ custom event, + * + * @param string $id On successful insertion, will set the field passes in the /id/ parameter + * ot the value of the last inserted ID as returned from the database. + * Optional. + * Defaults to 'id' + * + * @return false|int The numbers of rows affected or FALSE on error + */ + public function create( string $id = 'id' ) { + $results = $this->db_table::insert( $this->entry_data ); + if ( $results ) { + // Set row id once created + $this->set( $id, $this->db_table::db()->insert_id ); + } + + $this->trigger_change( $results, 'create' ); + + return $results; + } + + /** + * reset + * + * Clears all of this entry's data. + */ + public function reset(): void { + $this->entry_data = []; + } + + /** + * @return bool + */ + public function exists(): bool { + return isset( $this->entry_data['id'] ) && 0 < $this->entry_data['id']; + } + + /** + * DB_Entry_Base constructor. + * Uses the passed on arguments to initialize/set the Entry. + * Will through an exception in case the Entry's table property is not set correctly, and the /Table/ class + * linking this entry to a database table is not found. + * + * @param array $args The arguments to set this Entry by. + * If arguments /by/ and /value/ are set, + * will fetch the data according to the column name specified by /by/ and the value(s) specified + * by /value/. See doc for /init_by/ for details. + * + * if argument /data/ is set, will set the Entry's data with the data specified. + * See doc for /init_by_data/ for details. + * + * if argument /id/ is specified, will load the data of the row whose /id/ column has the value specified by the + * argument. + * + * Optional. + * Defaults to an empty array which will make this entry as the Empty Entry representing null, + * which cannot be saved unless properly modified to a non-null entry. + * + * @throws Missing_Table_Exception If the Entry table property is not found. + */ + public function __construct( array $args = [] ) { + $this->db_table = static::get_helper_class(); + + if ( empty( $this->db_table ) ) { + throw new Missing_Table_Exception(); + } + if ( isset( $args[ 'by' ] ) && isset( $args[ 'value' ] ) ) { + return $this->init_by( $args[ 'by' ], $args[ 'value' ] ); + } + + if ( isset( $args[ 'data' ] ) ) { + return $this->init_by_data( $args[ 'data' ] ); + } + + if ( isset( $args[ 'id' ] ) ) { + return $this->init_by_id( $args[ 'id' ] ); + } + + return $this->return_empty(); + } +} diff --git a/classes/database/exceptions/missing-table-exception.php b/classes/database/exceptions/missing-table-exception.php new file mode 100644 index 0000000..5a9a249 --- /dev/null +++ b/classes/database/exceptions/missing-table-exception.php @@ -0,0 +1,18 @@ +prefix . static::$table_name; + } + + /** + * set_table_prefix + * + * Saves the table name as a property in the WP database object and + * sets it value to the table name and its prefix. + */ + protected static function set_table_prefix(): void { + static::db()->{static::$table_name} = static::table_name(); + } + + /** + * get_columns + * + * Should return an array of table columns details in the format of + * column_name => [ type => db_type, key => key_data (optional), flags => other_modifiers (optional) ] + * + * NOTE: A primary key column named /id/ which is an auto-incremented int/big int is assumed to exist and must be one + * of the columns this function returns. + * @return array The table column data. + */ + public static function get_columns(): array { + return []; + } + + /** + * get_extra_keys + * + * Extra keys to the table definitions to be merged with column key definitions + * @return string[] SQL table key definitions + */ + protected static function get_extra_keys(): array { + return []; + } + + /** + * get_keys + * + * Extracts the key definitions from the table's columns and merges with + * any extra key definitions + * @return string[] SQL table key definitions + */ + protected static function get_keys(): array { + $columns = static::get_columns(); + $keys = []; + foreach ( $columns as $column ) { + if ( ! isset( $column['key'] ) ) { + continue; + } + $keys[] = $column['key']; + } + return array_merge( $keys, static::get_extra_keys() ); + } + + /** + * install + * + * This function compares the version of the installed table and the current version as reported by + * the class. + * If the versions are different, the table will be installed or updated, and the option + * will be set to the current version. + */ + public static function install(): void { + $installed_ver = get_option( static::DB_VERSION_FLAG_NAME, -1 ); + + if ( static::DB_VERSION !== $installed_ver ) { + + $sql = static::get_create_table_sql(); + + require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + dbDelta( $sql ); + + update_option( static::DB_VERSION_FLAG_NAME, static::DB_VERSION, false ); + } + + static::set_table_prefix(); + } + + /** + * get_create_table_sql + * + * Generates the SQL command to run in the database to create the table + * based on the definitions of columns and keys. + * @return string The SQL command to create the table + */ + protected static function get_create_table_sql(): string { + $table = static::table_name(); + $keys = static::get_keys(); + $charset_collate = static::db()->get_charset_collate(); + $table_columns = []; + $sql = []; + $sql[] = 'CREATE TABLE ' . $table . ' ('; + $columns = static::get_columns(); + foreach ( $columns as $column_name => $column ) { + $table_columns[] = sprintf( '`%s` %s %s,', + $column_name, + $column['type'], + $column['flags'] ?? '' + ); + } + + $sql[] = "\t" . implode( "\n\t", $table_columns ); + $sql[] = "\t" . implode( ",\n\t", $keys ); + $sql[] = ") AUTO_INCREMENT=11 {$charset_collate};"; + return implode( "\n", $sql ); + } + + /** + * where + * + * Generates a proper WHERE clause for an SQL query. + * @param string|array $where Either a string of where clause (returns as is) or an array of + * conditions join with an AND, and in the format of column => (int|string) for exact value comparison + * or in the format of column => [column => string, value =>string|int|array, + * comparison operator => string, relation_before=>string, optional, relation_after=>string, optional] + * + * @return string WHERE clause built from the function input + */ + public static function where( $where ): string { + if ( ! is_array( $where ) ) { + return $where; + } + $needs_relationship = false; + $where_string = ''; + foreach ( $where as $key => $filter ) { + if ( ! is_array( $filter ) ) { + if ( $needs_relationship ) { + $where_string .= ' AND'; + } + $where_string .= ' ' . self::get_where_string( $key, $filter ); + $needs_relationship = true; + continue; + } + + $where_string .= self::maybe_add_relation( $filter ); + $where_string .= self::get_where_string( $filter['column'], $filter['value'], $filter['operator'] ); + $where_string .= self::maybe_add_relation( $filter, false ); + + } + return $where_string; + + } + + /** + * maybe_add_relation + * + * Adds a logical relation ship (AND, OR...) if exists, based on the position (before|after) related to the condition. + * @param array $filter Object array describing the where condition that may contain the keys /relation_before/ + * or /relation_after/ containing the logical relationship to add to the main WHERE condition. + * @param bool $is_before Whether the current position in the text is before the condition the object describes. + * Optional. + * Defaults to TRUE. + * + * @return string If the logical relationship exists, returns it. Otherwise - an empty string. + */ + private static function maybe_add_relation( array $filter, bool $is_before = true ): string { + $key_to_check = $is_before ? 'relation_before' : 'relation_after'; + return isset( $filter[ $key_to_check ] ) ? ' ' . $filter[ $key_to_check ] : ' '; + } + + /** + * get_where_string + * + * @param string $key The column name in the condition + * @param string|int|array $value The value being compared. If an array will be translated to a set. + * @param string $operator The comparison operator. + * Optional. + * Defaults to '='. + * @param null $format Unused. + * + * @return string An SQL condition based on the parameters. + */ + private static function get_where_string( string $key, $value, string $operator = '=', $format = null ): string { + $param_string = is_int( $value ) ? '%d' : '%s'; + if ( is_array( $value ) ) { + $param_string = '('; + $count = count( $value ); + for ( $i = 0; $i < $count; $i++ ) { + $param_string .= is_int( $value[ $i ] ) ? '%d' : '%s'; + $param_string .= ( $i !== $count - 1 ) ? ', ' : ''; + } + $param_string .= ')'; + } + return static::db()->prepare( "$key $operator $param_string", $value ); + } + + /** + * get_columns_for_insert + * This function tries to get the column names for an INSERT operation based on the table column + * definition, and if that fails based on the /data/ parameter. + * The function will remove any column called /id/. + * @param mixed $data If a two-dimensional array, where the elements are in the form of column => value, + * the function will try to get the names of the columns off the first element. + * + * @return false|string A string representing the list of columns, comma separated and surrounded by parenthesis; + * or false in case of function failure. + */ + private static function get_columns_for_insert( $data ) { + $cols = static::get_columns(); + if ( count( $cols ) ) { + $columns = array_keys( $cols ); + } elseif ( is_array( $data ) ) { + //try to get from data + $columns = array_keys( $data[0] ); + } + + if ( empty( $columns ) || ! is_array( $columns ) ) { + return false; + } + + // remove id $column + if ( ! empty( $columns['id'] ) ) { + unset( $columns['id'] ); + } + $index = array_search( 'id', $columns, true ); + if ( false !== $index ) { + unset( $columns[ $index ] ); + } + + return ' (`' . implode( '`,`', $columns ) . '`) '; + } + + /** + * select_var + * + * Selects a single cell in the table and returns its value as string. + * Will return the first cell of the first row in the result set. + * @param string|array $fields A string of comma-separated list, or an array of columns from the table. + * Optional. + * Defaults to '*' (all table columns) + * @param string|array $where A string of WHERE conditions or an array of column => value entries connected with the + * AND logical operator. Or in the format of column => [column => string, value =>string|int|array, + * comparison operator => string, relation_before=>string, optional, relation_after=>string, optional] + * Optional. + * Defaults tp '1', which is evaluated to true and will bring all records (no condition). + * @param int|null $limit Limit the number of results to return to this number, or NULL for no limit. + * Optional. + * Defaults to NULL (no limit) + * @param int|null $offset Skip this number of results or NULL for no skip + * Optional. + * Defaults to NULL (no offset) + * @param string $join JOIN table clause. + * Optional. + * Defaults to an empty string (no join) + * + * @return string|null The query result or NULL on error. + */ + public static function select_var( $fields = '*', $where = '1', int $limit = null, int $offset = null, string $join = '' ): ?string { + return static::db()->get_var( static::build_sql_string( $fields, $where, $limit, $offset, $join ) ); + } + + /** + * build_sql_string + * + * Generates a SELECT query based on the function input. + * @param string|array $fields A string of comma-separated list, or an array of columns from the table. + * Optional. + * Defaults to '*' (all table columns) + * @param string|array $where A string of WHERE conditions or an array of column => value entries connected with + * the AND logical operator. r in the format of column => [column => string, value =>string|int|array, + * comparison operator => string, relation_before=>string, optional, relation_after=>string, optional] + * Optional. + * Defaults to '1', which is evaluated to true and will bring all records (no condition). + * @param int|null $limit Maximum number of results to return. + * Optional. + * Defaults to NULL (no limit) + * @param int|null $offset Start the results from a certain ordinal position. + * Optional. + * Defaults to NULL (no offset) + * @param string $join A table JOIN clause. + * Optional. + * Defaults to an empty string (no join) + * @param array $order_by an array of column => direction (asc|desc) to sort the results by. + * Optional. + * Defaults to an empty array (Default sort). + * + * @return string The SQL SELECT statement built according to the function parameters. + */ + private static function build_sql_string( $fields = '*', $where = '1', int $limit = null, int $offset = null, string $join = '', array $order_by = [] ): string { + if ( is_array( $fields ) ) { + $fields = implode( ', ', $fields ); + } + + $db = static::db(); + $query_string = 'SELECT %s FROM %s %s WHERE %s'; + $query_string = sprintf( $query_string, + $fields, + static::table_name(), + $join, + static::where( $where ) + ); + + if ( $order_by ) { + $query_string .= static::build_order_by_sql_string( $order_by ); + } + + if ( $limit ) { + $query_string .= $db->prepare( ' LIMIT %d', $limit ); + } + + if ( $offset ) { + $query_string .= $db->prepare( ' OFFSET %d', $offset ); + } + + return $query_string; + } + + /** + * build_order_by_sql_string + * + * Generates the ORDER BY clause of the query based on the passed on parameter + * @param array $order_by An array of column => direction (asc/desc) pairs + * + * @return string The ORDER BY clause for a query + */ + public static function build_order_by_sql_string( array $order_by ): string { + return ' ORDER BY ' . implode( ', ', array_map( function( $column, $direction ) { + return "{$column} {$direction}"; + }, array_keys( $order_by ), $order_by ) ); + } + + /** + * select + * + * Runs a SELECT query and returns the results as an array of objects, each object represents a row, + * @param string|array $fields A string of comma-separated list, or an array of columns from the table. + * Optional. + * Defaults to '*' (all table columns) + * @param string|array $where A string of WHERE conditions, or an array of column => value enteries + * for direct comparison joined with the AND logical operator, or in the format of column => [column => string, value =>string|int|array, + * comparison operator => string, relation_before=>string, optional, relation_after=>string, optional] + * Optional. + * Defaults to '1', which is evaluated to true and will bring all records (no condition). + * @param int|null $limit Maximum number of results to return. + * Optional. + * Defaults to NULL (no limit) + * @param int|null $offset Start the results from a certain ordinal position. + * Optional. + * Defaults to NULL (no offset) + * @param string|array $join A table JOIN clause. + * Optional. + * Defaults to an empty string (no join) + * @param array $order_by an array of column => direction (asc|desc) to sort the results by. + * Optional. + * Defaults to an empty array (Default sort). + * + * @return array|object|\stdClass[]|null On success, an array of objects. Null on error. + */ + public static function select( $fields = '*', $where = '1', int $limit = null, int $offset = null, $join = '', array $order_by = [] ) { + // TODO: handle $wpdb->last_error + $query = static::build_sql_string( $fields, $where, $limit, $offset, $join, $order_by ); + return static::db()->get_results( $query ); + } + + /** + * get_col + * + * Returns the first column of the result set. + * @param string $column The column to return. + * Optional. + * Defaults to an empty string. + * @param string|array $where A string of WHERE conditions or an array of column => value entries + * for direct comparison joined with the AND logical operator or in the format of column => [column => string, value =>string|int|array, + * comparison operator => string, relation_before=>string, optional, relation_after=>string, optional] + * Optional. + * Defaults to '1', which is evaluated to true and will bring all records (no condition). + * @param int|null $limit Maximum number of results to return. + * Optional. + * Defaults to NULL (no limit) + * @param int|null $offset Start the results from a certain ordinal position. + * Optional. + * Defaults to NULL (no offset) + * @param string $join A table JOIN clause. + * Optional. + * Defaults to an empty string (no join) + * @param array $order_by an array of column => direction (asc|desc) to sort the results by. + * Optional. + * Defaults to an empty array (Default sort). + * + * @return string[] Array of the values of the column as strings, or an empty one on error. + */ + public static function get_col( string $column = '', $where = '1', int $limit = null, int $offset = null, string $join = '', array $order_by = [] ) : array { + return static::db()->get_col( static::build_sql_string( $column, $where, $limit, $offset, $join, $order_by ) ); + } + + /** + * first + * + * Returns the first row in the table according to the query filters. + * @param string|array $fields A string of comma-separated list, or an array of columns from the table. + * Optional. + * Defaults to '*' (all table columns) + * @param string|array $where A string of WHERE conditions or an array of column => value entries + * for direct comparison joined with the logical AND operator, + * or in the format of column => [column => string, value =>string|int|array, + * comparison operator => string, relation_before=>string, optional, relation_after=>string, optional] + * Optional. + * Defaults to '1', which is evaluated to true and will bring all records (no condition). + * @param int $limit Unnecessary since we are only returning the first row. + * Optional. + * Defaults to 1. + * @param null $offset Start the results from a certain ordinal position. + * Optional. + * Defaults to NULL (no offset) + * @param string $join A table JOIN clause. + * Optional. + * Defaults to an empty string (no join) + * + * @return \stdClass|null An object representing the first row, or null on error + */ + public static function first( $fields = '*', $where = '1', int $limit = 1, $offset = null, string $join = '' ): ?\stdClass { + $result = static::select( $fields, $where, $limit, $offset, $join ); + return ( ! empty( $result[0] ) ) ? $result[0] : null; + } + + /** + * replace + * + * Replace a row in a table if it exists or insert a new row in a table if the row does not already exist. + * @param array $data Array of data in the form of column => (raw) value. + * Optional. + * Defaults to an empty array. + * + * @return false|int The number of rows affected or FALSE on error. + */ + public static function replace( array $data = [] ) { + return static::db()->replace( static::table_name(), $data ); + } + + /** + * insert + * + * Insert a single row into the table. + * @param array $data Array of data to insert in column => (raw) value format. + * Optional, defaults to an empty array. + * + * @return false|int The number of rows affected or FALSE on error + */ + public static function insert( array $data = [] ) { + return static::db()->insert( static::table_name(), $data ); + } + + /** + * insert_many + * Performs a bulk INSERT of many datasets/rows to the table + * + * @param array $data Optional. Defaults to an empty array. + * An array of datasets to be INSERTed into the table. Each value needs to be seperated by a comma, + * each data set needs to be surrounded by parenthesis. + * @param string|null $columns Optional. The columns being inserted. Defaults to NULL. + * Either a string of comma-separated column names surrounded by parenthesis, or NULL for the + * function to try guessing based on the data and column definitions. + * + * @return false|int Number of rows affected or false on error + */ + public static function insert_many( array $data = [], string $columns = null ) { + if ( null === $columns ) { + $columns = static::get_columns_for_insert( $data ); + if ( ! $columns ) { + return false; + } + } + $insert_sql = 'INSERT INTO ' . static::table_name() . $columns . ' VALUES ' . implode( ",\n", $data ) . ';'; + return static::db()->query( $insert_sql ); // no params so no need for `prepare`. + } + + /** + * update + * + * Updates data in the table, based on where conditionals + * @param array $data Optional. Array of column => (raw) values to be updated. + * Defaults to an empty array. + * @param array $where Optional. Array of column => (raw) values as a group of AND + * WHERE conditionals for the UPDATE statement. Defaults to an empty array. + * + * @return false|int The numbers of rows affected, or FALSE on error + */ + public static function update( array $data = [], array $where = [] ) { + return static::db()->update( static::table_name(), $data, $where ); + } + + /** + * delete + * + * Delete rows from this table based on optional where conditions. + * + * @param array $where Optional. And array of column => (raw) values + * as a group of AND conditions for the DELETE statement. Defaults to an empty array. + * + * @return false|int The number of rows updated, or false on error. + */ + public static function delete( array $where = [] ) { + return static::db()->delete( static::table_name(), $where ); + } + + /** + * query + * + * Execute any SQL query on the database. + * It is best used when there is a need for specific, + * custom, or otherwise complex SQL queries. + * @param string $query The query to be executed. Defaults to an empty string + * + * @return false|int Boolean true for CREATE, ALTER, TRUNCATE and DROP queries. + * Number of rows affected/selected for all other queries. Boolean false on error. + */ + public static function query( string $query = '' ) { + return static::db()->query( $query ); + } + + /** + * get_class_name + * + * Returns the name of this /Table/ class (or its derivative) + * @return string - The name of the current class + */ + public static function get_class_name(): string { + return get_called_class(); + } +} diff --git a/classes/logger.php b/classes/logger.php new file mode 100644 index 0000000..0216187 --- /dev/null +++ b/classes/logger.php @@ -0,0 +1,37 @@ +reflection ) { + try { + $this->reflection = new ReflectionClass( $this ); + } catch ( ReflectionException $e ) { + if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log + error_log( $e->getMessage() ); + } + } + } + + return $this->reflection; + } + + /** + * Add module component. + * + * Add new component to the current module. + * @access public + * + * @param string $id Component ID. + * @param mixed $instance An instance of the component. + */ + public function add_component( $id, $instance ) { + $this->components[ $id ] = $instance; + } + + /** + * Add module route. + * + * Add new route to the current module. + * @access public + * + * @param string $id Route ID. + * @param mixed $instance An instance of the route. + */ + public function add_route( string $id, $instance ) { + $this->routes[ $id ] = $instance; + } + + /** + * @access public + * @return Module[] + */ + public function get_components() { + return $this->components; + } + + /** + * Get module component. + * + * Retrieve the module component. + * @access public + * + * @param string $id Component ID. + * + * @return mixed An instance of the component, or `false` if the component + * doesn't exist. + * @codeCoverageIgnore + */ + public function get_component( $id ) { + if ( isset( $this->components[ $id ] ) ) { + return $this->components[ $id ]; + } + + return false; + } + + /** + * Retrieve the namespace of the class + * + * @access public + * @static + */ + public static function namespace_name() { + $class_name = static::class_name(); + return substr( $class_name, 0, strrpos( $class_name, '\\' ) ); + } + + + + public static function routes_list() : array { + return []; + } + + public static function component_list() : array { + return []; + } + + /** + * Adds an array of components. + * Assumes namespace structure contains `\Components\` + * + * @param array $components_ids => component's class name. + */ + public function register_components( $components_ids = null ) { + $namespace = static::namespace_name(); + $components_ids = $components_ids ?? static::component_list(); + foreach ( $components_ids as $component_id ) { + $class_name = $namespace . '\\Components\\' . $component_id; + $this->add_component( $component_id, new $class_name() ); + } + } + + /** + * Adds an array of routes. + * Assumes namespace structure contains `\Rest\` + */ + public function register_routes() { + $namespace = static::namespace_name(); + $routes_ids = static::routes_list(); + + foreach ( $routes_ids as $route_id ) { + $class_name = $namespace . '\\Rest\\' . $route_id; + $this->add_route( $route_id, new $class_name() ); + } + } +} + diff --git a/classes/rest/route.php b/classes/rest/route.php new file mode 100644 index 0000000..921b8a4 --- /dev/null +++ b/classes/rest/route.php @@ -0,0 +1,387 @@ +get_methods(); + if ( empty( $methods ) ) { + return; + } + + //$callbacks = false; + $callbacks = []; + foreach ( (array) $methods as $method ) { + if ( ! in_array( $method, $this->valid_http_methods, true ) ) { + continue; + } + $callbacks[] = $this->build_endpoint_method_config( $method ); + } + + $arguments = $this->get_arguments(); + + if ( ! $callbacks && empty( $arguments ) ) { + return; + } + + $arguments = array_merge( $arguments, (array) $callbacks ); + register_rest_route( $this->namespace, '/' . $this->get_endpoint() . '/', $arguments, $this->override ); + } + + /** + * get_methods + * Rest Endpoint methods + * + * Returns an array of the supported REST methods for this route + * @return array REST methods being configured for this route. + */ + abstract public function get_methods(): array; + + /** + * get_callback + * + * Returns a reference to the callback function to handle the REST method specified by the /method/ parameter. + * @param string $method The REST method name + * + * @return callable A reference to a member function with the same name as the REST method being passed as a parameter, + * or a reference to the default function /callback/. + */ + public function get_callback_method( string $method ): callable { + $method_name = strtolower( $method ); + $callback = $this->method_exists_in_current_class( $method_name ) ? $method_name : 'callback'; + return [ $this, $callback ]; + } + + /** + * get_permission_callback_method + * + * Returns a reference to the permission callback for the method if exists or the default one if it doesn't. + * @param string $method The REST method name + * + * @return callable If a method called (rest-method)_permission_callback exists, returns a reference to it, otherwise + * returns a reference to the default member method /permission_callback/. + */ + public function get_permission_callback_method( string $method ): callable { + $method_name = strtolower( $method ); + $permission_callback_method = $method_name . '_permission_callback'; + $permission_callback = $this->method_exists_in_current_class( $permission_callback_method ) ? $permission_callback_method : 'permission_callback'; + return [ $this, $permission_callback ]; + } + + /** + * maybe_add_args_to_config + * + * Checks if the class has a method call (rest-method)_args. + * If it does, the function calls it and adds its response to the config object passed to the function, under the /args/ key. + * @param string $method The REST method name being configured + * @param array $config The configuration object for the method + * + * @return array The configuration object for the method, possibly after being amended + */ + public function maybe_add_args_to_config( string $method, array $config ): array { + $method_name = strtolower( $method ); + $method_args = $method_name . '_args'; + if ( $this->method_exists_in_current_class( $method_args ) ) { + $config['args'] = $this->{$method_args}(); + } + return $config; + } + + /** + * maybe_add_response_to_swagger + * + * If the function method /(rest-method)_response_callback/ exists, adds the filter + * /swagger_api_response_(namespace with slashes replaced with underscores)_(endpoint with slashes replaced with underscores)/ + * with the aforementioned function method. + * This filter is used with the WP API Swagger UI plugin to create documentation for the API. + * The value being passed is an array: [ + '200' => ['description' => 'OK'], + '404' => ['description' => 'Not Found'], + '400' => ['description' => 'Bad Request'] + ] + * @param string $method REST method name + */ + public function maybe_add_response_to_swagger( string $method ): void { + $method_name = strtolower( $method ); + $method_response_callback = $method_name . '_response_callback'; + if ( $this->method_exists_in_current_class( $method_response_callback ) ) { + $response_filter = $method_name . '_' . str_replace( + '/', + '_', + $this->namespace . '/' . $this->get_endpoint() + ); + add_filter( 'swagger_api_responses_' . $response_filter, [ $this, $method_response_callback ] ); + } + } + + /** + * build_endpoint_method_config + * + * Builds a configuration array for the endpoint based on the presence of the callback, permission, additional parameters, + * and response to Swagger member functions. + * @param string $method The REST method for the endpoint + * + * @return array The endpoint configuration for the method specified by the parameter + */ + private function build_endpoint_method_config( string $method ): array { + $config = [ + 'methods' => $method, + 'callback' => $this->get_callback_method( $method ), + 'permission_callback' => $this->get_permission_callback_method( $method ), + ]; + $config = $this->maybe_add_args_to_config( $method, $config ); + return $config; + } + + /** + * method_exists_in_current_class + * + * Uses reflection to check if this class has the /method/ method. + * @param string $method The name of the method being checked. + * + * @return bool TRUE if the class has the /method/ method, FALSE otherwise. + */ + private function method_exists_in_current_class( string $method ): bool { + $class_name = get_class( $this ); + try { + $reflection = new ReflectionClass( $class_name ); + } catch ( \ReflectionException $e ) { + return false; + } + if ( ! $reflection->hasMethod( $method ) ) { + return false; + } + $method_ref = $reflection->getMethod( $method ); + + return ( $method_ref && $class_name === $method_ref->class ); + } + + /** + * @param $data + * + * @return WP_REST_Response + */ + public function respond_success_json( $data = [] ): WP_REST_Response { + return new WP_REST_Response([ + 'success' => true, + 'data' => $data, + ]); + } + + /** + * @param array{message: string, code: string} $data + * + * @return WP_Error + */ + public function respond_error_json( array $data ): WP_Error { + if ( ! isset( $data['message'] ) || ! isset( $data['code'] ) ) { + _doing_it_wrong( + __FUNCTION__, + esc_html__( 'Both `message` and `code` keys must be provided', 'pojo-accessibility' ), + '1.0.0' + ); // @codeCoverageIgnore + } + + return new WP_Error( + $data['code'] ?? 'internal_server_error', + $data['message'] ?? esc_html__( 'Internal server error', 'pojo-accessibility' ), + ); + } + + public function verify_capability( $capability = 'manage_options' ) { + if ( ! current_user_can( $capability ) ) { + return $this->respond_error_json([ + 'message' => esc_html__( 'You do not have sufficient permissions to access this data.', 'pojo-accessibility' ), + 'code' => '401 Unauthorized', + ]); + } + } + + /** + * permission_callback + * Permissions callback fallback for the endpoint + * Gets the current user ID and sets the /current_user_id/ property. + * If the /auth/ property is set to /true/ will make sure that the user is logged in (has an id greater than 0) + * + * @param WP_REST_Request $request unused + * + * @return bool TRUE, if permission granted, FALSE otherwise + */ + public function permission_callback( WP_REST_Request $request ): bool { + // try to get current user + $this->current_user_id = get_current_user_id(); + if ( $this->auth ) { + return $this->current_user_id > 0; + } + + return true; + } + + /** + * callback + * Fallback callback function, returns a response consisting of the string /ok/. + * + * @param WP_REST_Request $request unused + * + * @return WP_REST_Response Default Response of the string 'ok'. + */ + public function callback( WP_REST_Request $request ): WP_REST_Response { + return rest_ensure_response( [ 'OK' ] ); + } + + /** + * respond_wrong_method + * + * Creates a WordPress error object with the /rest_no_route/ code and the message and code supplied or the defaults. + * @param null $message The error message for the wrong method. + * Optional. + * Defaults to null, which makes sets the message to /No route was found matching the URL and request method/ + * @param int $code The HTTP status code. + * Optional. + * Defaults to 404 (Not found). + * + * @return WP_Error The WordPress error object with the error message and status code supplied + */ + public function respond_wrong_method( $message = null, int $code = 404 ): WP_Error { + if ( null === $message ) { + $message = __( 'No route was found matching the URL and request method', 'pojo-accessibility' ); + } + + return new WP_Error( 'rest_no_route', $message, [ 'status' => $code ] ); + } + + /** + * respond_with_code + * Create a new /WP_REST_Response/ object with the specified data and HTTP response code. + * + * @param array|null $data The data to return in this response + * @param int $code The HTTP response code. + * Optional. + * Defaults to 200 (OK). + * + * @return WP_REST_Response The WordPress response object loaded with the data and the response code. + */ + public function respond_with_code( ?array $data = null, int $code = 200 ): WP_REST_Response { + return new WP_REST_Response( $data, $code ); + } + + /** + * get_user_from_request + * + * Returns the current user object. + * Depends on the property /current_user_id/ to be set. + * @return WP_User|false The user object or false if not found or on error. + */ + public function get_user_from_request() { + return get_user_by( 'id', $this->current_user_id ); + } + + /** + * get_arguments + * Rest Endpoint extra arguments + * @return array Additional arguments for the route configuration + */ + public function get_arguments(): array { + return []; + } + + /** + * get_endpoint + * Rest route Endpoint + * @return string Endpoint uri component (comes after the route namespace) + */ + abstract public function get_endpoint(): string; + + /** + * get_name + * @return string The name of the route + */ + abstract public function get_name(): string; + + /** + * get_self_url + * + * @param string $endpoint + * + * @return string + */ + public function get_self_url( string $endpoint = '' ): string { + return rest_url( $this->namespace . '/' . $endpoint ); + } + + public function verify_nonce( $nonce = '', $name = '' ) { + if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $nonce ) ), $name ) ) { + return $this->respond_error_json([ + 'message' => esc_html__( 'Invalid nonce', 'pojo-accessibility' ), + 'code' => 'bad_request', + ]); + } + } + + public function verify_nonce_and_capability( $nonce = '', $name = '', $capability = 'manage_options' ) { + $this->verify_nonce( $nonce, $name ); + + if ( ! current_user_can( $capability ) ) { + return $this->respond_error_json([ + 'message' => esc_html__( 'You do not have sufficient permissions to access this data.', 'pojo-accessibility' ), + 'code' => 'bad_request', + ]); + } + } + +} diff --git a/classes/utils.php b/classes/utils.php new file mode 100644 index 0000000..ee12487 --- /dev/null +++ b/classes/utils.php @@ -0,0 +1,37 @@ +id, 'ea11y-' ); + } + public static function user_is_admin(): bool { + return current_user_can( 'manage_options' ); + } + public static function is_wp_dashboard_page(): bool { + $current_screen = get_current_screen(); + + return str_contains( $current_screen->id, 'dashboard' ); + } + + public static function is_wp_settings_page(): bool { + $current_screen = get_current_screen(); + + return str_contains( $current_screen->id, 'options-' ); + } + + public static function is_elementor_installed() { + $file_path = 'elementor/elementor.php'; + $installed_plugins = get_plugins(); + return isset( $installed_plugins[ $file_path ] ); + } + +} diff --git a/classes/utils/assets.php b/classes/utils/assets.php new file mode 100644 index 0000000..37aeec7 --- /dev/null +++ b/classes/utils/assets.php @@ -0,0 +1,142 @@ + self::get_assets_version( $version ), + 'suffix' => self::get_assets_suffix(), + ]; + } + + /** + * get_assets_path + * + * @param string $asset_name + * @param string $asset_type + * @param string $suffix + * + * @return string + */ + private static function get_assets_path( string $asset_name, string $asset_type, string $suffix = '' ) : string { + return \SITE_MAILER_ASSETS_URL . '/build/' . $asset_name . $suffix . '.' . $asset_type; + //TODO: Check if we need this + //return \SITE_MAILER_ASSETS_URL . $asset_type . '/' . ( '' === $suffix ? 'dev/' : '' ) . $asset_name . $suffix . '.' . $asset_type; + } + + /** + * enqueue_app_assets + * + * @param string $handle + * @param bool $with_css + */ + public static function enqueue_app_assets( string $handle = '', bool $with_css = true ) : void { + $dir = \SITE_MAILER_ASSETS_PATH . 'build/'; + $url = \SITE_MAILER_ASSETS_URL . 'build/'; + + $script_asset_path = $dir . $handle . '.asset.php'; + if ( ! file_exists( $script_asset_path ) ) { + throw new \Error( + 'You need to run `npm start` or `npm run build` for the "' . esc_html( $handle ) . '" script first.' + ); + } + + // enqueue js + $script_asset = require $script_asset_path; + wp_enqueue_script( + $handle, + $url . $handle . '.js', + array_merge( $script_asset['dependencies'], [ 'wp-util' ] ), + $script_asset['version'], + true, + ); + + // add translation support + wp_set_script_translations( $handle, 'elementor-sw' ); + + if ( ! $with_css ) { + return; + } + // enqueue css + $css_file_name = 'style-' . $handle . '.css'; + $css_version = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? filemtime( $dir . $css_file_name ) : \SITE_MAILER_VERSION; + wp_enqueue_style( + $handle, + $url . $css_file_name, + [ 'wp-components' ], + $css_version + ); + } +} diff --git a/includes/manager.php b/includes/manager.php new file mode 100644 index 0000000..a8b29d6 --- /dev/null +++ b/includes/manager.php @@ -0,0 +1,57 @@ +modules[ $module_name ] = $class_name::instance(); + } + } + } + + /** + * @param string $module_name + * + * @return Module_Base|Module_Base[] + */ + public function get_modules( string $module_name ) { + if ( $module_name ) { + if ( isset( $this->modules[ $module_name ] ) ) { + return $this->modules[ $module_name ]; + } + + return null; + } + + return $this->modules; + } +} diff --git a/includes/pojo-a11y-customizer.php b/includes/pojo-a11y-customizer.php deleted file mode 100644 index ed8985f..0000000 --- a/includes/pojo-a11y-customizer.php +++ /dev/null @@ -1,285 +0,0 @@ - 'a11y_toolbar_icon', - 'title' => __( 'Toolbar Icon', 'pojo-accessibility' ), - 'type' => 'select', - 'choices' => array( - 'one-click' => __( 'One Click', 'pojo-accessibility' ), - 'wheelchair' => __( 'Wheelchair', 'pojo-accessibility' ), - 'accessibility' => __( 'Accessibility', 'pojo-accessibility' ), - ), - 'std' => 'one-click', - 'description' => __( 'Set Toolbar Icon', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'a11y_toolbar_position', - 'title' => __( 'Toolbar Position', 'pojo-accessibility' ), - 'type' => 'select', - 'choices' => array( - 'left' => __( 'Left', 'pojo-accessibility' ), - 'right' => __( 'Right', 'pojo-accessibility' ), - ), - 'std' => is_rtl() ? 'right' : 'left', - 'description' => __( 'Set Toolbar Position', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'a11y_toolbar_distance_top', - 'title' => __( 'Offset from Top (Desktop)', 'pojo-accessibility' ), - 'type' => 'text', - 'std' => '100px', - 'description' => __( 'Set Toolbar top offset (Desktop)', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'a11y_toolbar_distance_top_mobile', - 'title' => __( 'Offset from Top (Mobile)', 'pojo-accessibility' ), - 'type' => 'text', - 'std' => '50px', - 'description' => __( 'Set Toolbar top offset (Mobile)', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'a11y_bg_toolbar', - 'title' => __( 'Toolbar Background', 'pojo-accessibility' ), - 'type' => 'color', - 'std' => '#ffffff', - 'selector' => '#pojo-a11y-toolbar .pojo-a11y-toolbar-overlay', - 'change_type' => 'bg_color', - 'description' => __( 'Set Toolbar background color', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'a11y_color_toolbar', - 'title' => __( 'Toolbar Color', 'pojo-accessibility' ), - 'type' => 'color', - 'std' => '#333333', - 'selector' => '#pojo-a11y-toolbar .pojo-a11y-toolbar-overlay ul.pojo-a11y-toolbar-items li.pojo-a11y-toolbar-item a, #pojo-a11y-toolbar .pojo-a11y-toolbar-overlay p.pojo-a11y-toolbar-title', - 'change_type' => 'color', - 'description' => __( 'Set Toolbar text color', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'a11y_toggle_button_bg_color', - 'title' => __( 'Toggle Button Background', 'pojo-accessibility' ), - 'type' => 'color', - 'std' => '#4054b2', - 'description' => __( 'Set Toolbar toggle button background color', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'a11y_toggle_button_color', - 'title' => __( 'Toggle Button Color', 'pojo-accessibility' ), - 'type' => 'color', - 'std' => '#ffffff', - 'selector' => '#pojo-a11y-toolbar .pojo-a11y-toolbar-toggle a', - 'change_type' => 'color', - 'description' => __( 'Set Toolbar toggle button icon color', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'a11y_bg_active', - 'title' => __( 'Active Background', 'pojo-accessibility' ), - 'type' => 'color', - 'std' => '#4054b2', - 'selector' => '#pojo-a11y-toolbar .pojo-a11y-toolbar-overlay ul.pojo-a11y-toolbar-items li.pojo-a11y-toolbar-item a.active', - 'change_type' => 'bg_color', - 'description' => __( 'Set Toolbar active background color', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'a11y_color_active', - 'title' => __( 'Active Color', 'pojo-accessibility' ), - 'type' => 'color', - 'std' => '#ffffff', - 'selector' => '#pojo-a11y-toolbar .pojo-a11y-toolbar-overlay ul.pojo-a11y-toolbar-items li.pojo-a11y-toolbar-item a.active', - 'change_type' => 'color', - 'description' => __( 'Set Toolbar active text color', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'a11y_focus_outline_style', - 'title' => __( 'Focus Outline Style', 'pojo-accessibility' ), - 'type' => 'select', - 'choices' => array( - 'solid' => __( 'Solid', 'pojo-accessibility' ), - 'dotted' => __( 'Dotted', 'pojo-accessibility' ), - 'dashed' => __( 'Dashed', 'pojo-accessibility' ), - 'double' => __( 'Double', 'pojo-accessibility' ), - 'groove' => __( 'Groove', 'pojo-accessibility' ), - 'ridge' => __( 'Ridge', 'pojo-accessibility' ), - 'outset' => __( 'Outset', 'pojo-accessibility' ), - 'initial' => __( 'Initial', 'pojo-accessibility' ), - ), - 'std' => 'solid', - 'description' => __( 'Set Focus outline style', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'a11y_focus_outline_width', - 'title' => __( 'Focus Outline Width', 'pojo-accessibility' ), - 'type' => 'select', - 'choices' => array( - '1px' => '1px', - '2px' => '2px', - '3px' => '3px', - '4px' => '4px', - '5px' => '5px', - '6px' => '6px', - '7px' => '7px', - '8px' => '8px', - '9px' => '9px', - '10px' => '10px', - ), - 'std' => '1px', - 'description' => __( 'Set Focus outline width', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'a11y_focus_outline_color', - 'title' => __( 'Focus Outline Color', 'pojo-accessibility' ), - 'type' => 'color', - 'std' => '#FF0000', - 'description' => __( 'Set Focus outline color', 'pojo-accessibility' ), - ); - - return $fields; - } - - public function customize_a11y( $wp_customize ) { - $fields = $this->get_customizer_fields(); - - $section_description = '

' . __( 'Use the control below to customize the appearance and layout of the Accessibility Toolbar', 'pojo-accessibility' ) . '

' . - sprintf( __( 'Additional Toolbar settings can be configured at the %s page.', 'pojo-accessibility' ), - '' . __( 'Accessibility Toolbar', 'pojo-accessibility' ) . '' - ) . '

'; - - $wp_customize->add_section( 'accessibility', array( - 'title' => __( 'Accessibility', 'pojo-accessibility' ), - 'priority' => 30, - 'description' => $section_description, - ) ); - - foreach ( $fields as $field ) { - $customizer_id = POJO_A11Y_CUSTOMIZER_OPTIONS . '[' . $field['id'] . ']'; - $wp_customize->add_setting( $customizer_id, array( - 'default' => $field['std'] ? $field['std'] : null, - 'type' => 'option', - ) ); - switch ( $field['type'] ) { - case 'color': - $wp_customize->add_control( new WP_Customize_Color_Control( $wp_customize, $field['id'], array( - 'label' => $field['title'], - 'section' => 'accessibility', - 'settings' => $customizer_id, - 'description' => isset( $field['description'] ) ? $field['description'] : null, - ) ) ); - break; - case 'select': - case 'text': - $wp_customize->add_control( $field['id'], array( - 'label' => $field['title'], - 'section' => 'accessibility', - 'settings' => $customizer_id, - 'type' => $field['type'], - 'choices' => isset( $field['choices'] ) ? $field['choices'] : null, - 'description' => isset( $field['description'] ) ? $field['description'] : null, - ) ); - break; - } - } - } - - public function get_custom_css_code() { - $options = $this->get_customizer_options(); - $bg_color = $options['a11y_toggle_button_bg_color']; // get_theme_mod( 'a11y_toggle_button_bg_color', '#4054b2' ); - if ( ! empty( $bg_color ) ) { - $this->add_css_rule( '#pojo-a11y-toolbar .pojo-a11y-toolbar-toggle a', 'background-color', $bg_color ); - $this->add_css_rule( '#pojo-a11y-toolbar .pojo-a11y-toolbar-overlay, #pojo-a11y-toolbar .pojo-a11y-toolbar-overlay ul.pojo-a11y-toolbar-items.pojo-a11y-links', 'border-color', $bg_color ); - } - - $outline_style = $options['a11y_focus_outline_style']; //get_theme_mod( 'a11y_focus_outline_style', 'solid' ); - if ( ! empty( $outline_style ) ) { - $this->add_css_rule( 'body.pojo-a11y-focusable a:focus', 'outline-style', $outline_style . ' !important' ); - } - - $outline_width = $options['a11y_focus_outline_width']; // get_theme_mod( 'a11y_focus_outline_width', '1px' ); - if ( ! empty( $outline_width ) ) { - $this->add_css_rule( 'body.pojo-a11y-focusable a:focus', 'outline-width', $outline_width . ' !important' ); - } - - $outline_color = $options['a11y_focus_outline_color']; //get_theme_mod( 'a11y_focus_outline_color', '#FF0000' ); - if ( ! empty( $outline_color ) ) { - $this->add_css_rule( 'body.pojo-a11y-focusable a:focus', 'outline-color', $outline_color . ' !important' ); - } - - $distance_top = $options['a11y_toolbar_distance_top']; //get_theme_mod( 'a11y_toolbar_distance_top', '100px' ); - if ( ! empty( $distance_top ) ) { - $this->add_css_rule( '#pojo-a11y-toolbar', 'top', $distance_top . ' !important' ); - } - - $distance_top_mobile = $options['a11y_toolbar_distance_top_mobile']; // get_theme_mod( 'a11y_toolbar_distance_top_mobile', '50px' ); - if ( ! empty( $distance_top_mobile ) ) { - $this->add_css_code( "@media (max-width: 767px) { #pojo-a11y-toolbar { top: {$distance_top_mobile} !important; } }" ); - } - - $fields = $this->get_customizer_fields(); - foreach ( $fields as $field ) { - if ( empty( $field['selector'] ) || empty( $field['change_type'] ) ) { - continue; - } - - $option = $options[ $field['id'] ]; - - if ( 'color' === $field['change_type'] ) { - $this->add_css_rule( $field['selector'], 'color', $option ); - } elseif ( 'bg_color' === $field['change_type'] ) { - $this->add_css_rule( $field['selector'], 'background-color', $option ); - } - } - } - - private function get_customizer_options() { - static $options = false; - if ( false === $options ) { - $options = get_option( POJO_A11Y_CUSTOMIZER_OPTIONS ); - } - return $options; - } - - private function add_css_rule( $selector, $rule, $value ) { - if ( ! isset( $this->css_rules[ $selector ] ) ) { - $this->css_rules[ $selector ] = array(); - } - $this->css_rules[ $selector ][] = $rule . ': ' . $value . ';'; - } - - private function add_css_code( $code ) { - $this->css_code .= "\n" . $code; - } - - public function print_css_code() { - $this->get_custom_css_code(); - $css = ''; - foreach ( $this->css_rules as $selector => $css_rules ) { - $css .= "\n" . $selector . '{ ' . implode( "\t", $css_rules ) . '}'; - } - echo ''; - } - - public function __construct() { - add_filter( 'customize_register', array( &$this, 'customize_a11y' ), 610 ); - add_filter( 'wp_head', array( &$this, 'print_css_code' ) ); - } -} \ No newline at end of file diff --git a/includes/pojo-a11y-settings.php b/includes/pojo-a11y-settings.php deleted file mode 100644 index f2e43c2..0000000 --- a/includes/pojo-a11y-settings.php +++ /dev/null @@ -1,645 +0,0 @@ - 'pojo_a11y_toolbar', - 'title' => __( 'Display Toolbar', 'pojo-accessibility' ), - 'type' => self::FIELD_SELECT, - 'options' => array( - 'enable' => __( 'Show on all devices', 'pojo-accessibility' ), - 'visible-desktop' => __( 'Visible Desktop', 'pojo-accessibility' ), - 'visible-tablet' => __( 'Visible Tablet', 'pojo-accessibility' ), - 'visible-phone' => __( 'Visible Phone', 'pojo-accessibility' ), - 'hidden-desktop' => __( 'Hidden Desktop', 'pojo-accessibility' ), - 'hidden-tablet' => __( 'Hidden Tablet', 'pojo-accessibility' ), - 'hidden-phone' => __( 'Hidden Phone', 'pojo-accessibility' ), - 'disable' => __( 'Disable', 'pojo-accessibility' ), - ), - 'std' => 'enable', - ); - - $toolbar_options_classes = 'pojo-a11y-toolbar-button'; - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_title', - 'title' => __( 'Title', 'pojo-accessibility' ), - 'type' => self::FIELD_TEXT, - 'desc' => __( 'Title top of the toolbar (recommended).', 'pojo-accessibility' ), - 'class' => $toolbar_options_classes, - 'std' => __( 'Accessibility Tools', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_resize_font', - 'title' => __( 'Resize Font', 'pojo-accessibility' ), - 'type' => self::FIELD_SELECT, - 'class' => $toolbar_options_classes, - 'options' => array( - 'enable' => __( 'Enable', 'pojo-accessibility' ), - 'disable' => __( 'Disable', 'pojo-accessibility' ), - ), - 'std' => 'enable', - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_resize_font_add_title', - 'title' => __( 'Increase Text', 'pojo-accessibility' ), - 'type' => self::FIELD_TEXT, - 'class' => $toolbar_options_classes . ' pojo-settings-child-row no-border', - 'std' => __( 'Increase Text', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_resize_font_less_title', - 'title' => __( 'Decrease Text', 'pojo-accessibility' ), - 'type' => self::FIELD_TEXT, - 'class' => $toolbar_options_classes . ' pojo-settings-child-row', - 'std' => __( 'Decrease Text', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_grayscale', - 'title' => __( 'Grayscale', 'pojo-accessibility' ), - 'type' => self::FIELD_SELECT, - 'class' => $toolbar_options_classes, - 'options' => array( - 'enable' => __( 'Enable', 'pojo-accessibility' ), - 'disable' => __( 'Disable', 'pojo-accessibility' ), - ), - 'std' => 'enable', - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_grayscale_title', - 'title' => __( 'Grayscale Title', 'pojo-accessibility' ), - 'type' => self::FIELD_TEXT, - 'class' => $toolbar_options_classes . ' pojo-settings-child-row', - 'std' => __( 'Grayscale', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_high_contrast', - 'title' => __( 'High Contrast', 'pojo-accessibility' ), - 'type' => self::FIELD_SELECT, - 'class' => $toolbar_options_classes, - 'options' => array( - 'enable' => __( 'Enable', 'pojo-accessibility' ), - 'disable' => __( 'Disable', 'pojo-accessibility' ), - ), - 'std' => 'enable', - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_high_contrast_title', - 'title' => __( 'High Contrast Title', 'pojo-accessibility' ), - 'type' => self::FIELD_TEXT, - 'class' => $toolbar_options_classes . ' pojo-settings-child-row', - 'std' => __( 'High Contrast', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_negative_contrast', - 'title' => __( 'Negative Contrast', 'pojo-accessibility' ), - 'type' => self::FIELD_SELECT, - 'class' => $toolbar_options_classes, - 'options' => array( - 'enable' => __( 'Enable', 'pojo-accessibility' ), - 'disable' => __( 'Disable', 'pojo-accessibility' ), - ), - 'std' => 'enable', - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_negative_contrast_title', - 'title' => __( 'Negative Contrast Title', 'pojo-accessibility' ), - 'type' => self::FIELD_TEXT, - 'class' => $toolbar_options_classes . ' pojo-settings-child-row', - 'std' => __( 'Negative Contrast', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_light_bg', - 'title' => __( 'Light Background', 'pojo-accessibility' ), - 'type' => self::FIELD_SELECT, - 'class' => $toolbar_options_classes, - 'options' => array( - 'enable' => __( 'Enable', 'pojo-accessibility' ), - 'disable' => __( 'Disable', 'pojo-accessibility' ), - ), - 'std' => 'enable', - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_light_bg_title', - 'title' => __( 'Light Background Title', 'pojo-accessibility' ), - 'type' => self::FIELD_TEXT, - 'class' => $toolbar_options_classes . ' pojo-settings-child-row', - 'std' => __( 'Light Background', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_links_underline', - 'title' => __( 'Links Underline', 'pojo-accessibility' ), - 'type' => self::FIELD_SELECT, - 'class' => $toolbar_options_classes, - 'options' => array( - 'enable' => __( 'Enable', 'pojo-accessibility' ), - 'disable' => __( 'Disable', 'pojo-accessibility' ), - ), - 'std' => 'enable', - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_links_underline_title', - 'title' => __( 'Links Underline Title', 'pojo-accessibility' ), - 'type' => self::FIELD_TEXT, - 'class' => $toolbar_options_classes . ' pojo-settings-child-row', - 'std' => __( 'Links Underline', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_readable_font', - 'title' => __( 'Readable Font', 'pojo-accessibility' ), - 'type' => self::FIELD_SELECT, - 'class' => $toolbar_options_classes, - 'options' => array( - 'enable' => __( 'Enable', 'pojo-accessibility' ), - 'disable' => __( 'Disable', 'pojo-accessibility' ), - ), - 'std' => 'enable', - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_readable_font_title', - 'title' => __( 'Readable Font Title', 'pojo-accessibility' ), - 'type' => self::FIELD_TEXT, - 'class' => $toolbar_options_classes . ' pojo-settings-child-row', - 'std' => __( 'Readable Font', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_sitemap_title', - 'title' => __( 'Sitemap Title', 'pojo-accessibility' ), - 'type' => self::FIELD_TEXT, - 'class' => $toolbar_options_classes, - 'std' => __( 'Sitemap', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_sitemap_link', - 'title' => __( 'Sitemap Link', 'pojo-accessibility' ), - 'type' => self::FIELD_TEXT, - 'placeholder' => 'http://your-domain.com/sitemap', - 'desc' => __( 'Link for sitemap page in your website. Leave blank to disable.', 'pojo-accessibility' ), - 'class' => $toolbar_options_classes . ' pojo-settings-child-row', - 'std' => '', - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_help_title', - 'title' => __( 'Help Title', 'pojo-accessibility' ), - 'type' => self::FIELD_TEXT, - 'class' => $toolbar_options_classes, - 'std' => __( 'Help', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_help_link', - 'title' => __( 'Help Link', 'pojo-accessibility' ), - 'type' => self::FIELD_TEXT, - 'placeholder' => 'http://your-domain.com/help', - 'desc' => __( 'Link for help page in your website. Leave blank to disable.', 'pojo-accessibility' ), - 'class' => $toolbar_options_classes . ' pojo-settings-child-row', - 'std' => '', - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_feedback_title', - 'title' => __( 'Feedback Title', 'pojo-accessibility' ), - 'type' => self::FIELD_TEXT, - 'class' => $toolbar_options_classes, - 'std' => __( 'Feedback', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_feedback_link', - 'title' => __( 'Feedback Link', 'pojo-accessibility' ), - 'type' => self::FIELD_TEXT, - 'placeholder' => 'http://your-domain.com/feedback', - 'desc' => __( 'Link for feedback page in your website. Leave blank to disable.', 'pojo-accessibility' ), - 'class' => $toolbar_options_classes . ' pojo-settings-child-row', - 'std' => '', - ); - - $sections[] = array( - 'id' => 'section-a11y-toolbar', - 'page' => self::TOOLBAR_PAGE, - 'title' => __( 'Toolbar Settings', 'pojo-accessibility' ), - 'intro' => '', - 'fields' => $fields, - ); - - $sections[] = array( - 'id' => 'section-a11y-styles', - 'page' => self::TOOLBAR_PAGE, - 'title' => __( 'Style Settings', 'pojo-accessibility' ), - 'intro' => sprintf( __( 'For style settings of accessibility tools go to > Customize > Accessibility.', 'pojo-accessibility' ), $this->get_admin_url( 'customizer' ) ), - 'fields' => array(), - ); - - return $sections; - } - - public function section_a11y_settings( $sections ) { - $fields = array(); - - $fields[] = array( - 'id' => 'pojo_a11y_focusable', - 'title' => __( 'Add Outline Focus', 'pojo-accessibility' ), - 'type' => self::FIELD_SELECT, - 'desc' => __( 'Add outline to elements on keyboard focus.', 'pojo-accessibility' ), - 'options' => array( - 'enable' => __( 'Enable', 'pojo-accessibility' ), - 'disable' => __( 'Disable', 'pojo-accessibility' ), - ), - 'std' => 'disable', - ); - - $fields[] = array( - 'id' => 'pojo_a11y_skip_to_content_link', - 'title' => __( 'Skip to Content link', 'pojo-accessibility' ), - 'type' => self::FIELD_SELECT, - 'desc' => __( 'Add skip to content link when using keyboard.', 'pojo-accessibility' ), - 'options' => array( - 'enable' => __( 'Enable', 'pojo-accessibility' ), - 'disable' => __( 'Disable', 'pojo-accessibility' ), - ), - 'std' => 'enable', - ); - - $fields[] = array( - 'id' => 'pojo_a11y_skip_to_content_link_element_id', - 'title' => __( 'Skip to Content Element ID', 'pojo-accessibility' ), - 'placeholder' => 'content', - 'type' => self::FIELD_TEXT, - 'std' => 'content', - ); - - $fields[] = array( - 'id' => 'pojo_a11y_remove_link_target', - 'title' => __( 'Remove target attribute from links', 'pojo-accessibility' ), - 'type' => self::FIELD_SELECT, - 'desc' => __( 'This option will reset all your target links to open in the same window or tab.', 'pojo-accessibility' ), - 'options' => array( - 'enable' => __( 'Enable', 'pojo-accessibility' ), - 'disable' => __( 'Disable', 'pojo-accessibility' ), - ), - 'std' => 'disable', - ); - - $fields[] = array( - 'id' => 'pojo_a11y_add_role_links', - 'title' => __( 'Add landmark roles to all links', 'pojo-accessibility' ), - 'type' => self::FIELD_SELECT, - 'desc' => __( 'This option will add role="link" to all links on the page.', 'pojo-accessibility' ), - 'options' => array( - 'enable' => __( 'Enable', 'pojo-accessibility' ), - 'disable' => __( 'Disable', 'pojo-accessibility' ), - ), - 'std' => 'enable', - ); - - $fields[] = array( - 'id' => 'pojo_a11y_save', - 'title' => __( 'Sitewide Accessibility', 'pojo-accessibility' ), - 'desc' => __( 'Consistent accessibility throughout your site visit. Site remembers you and stays accessible.', 'pojo-accessibility' ), - 'type' => self::FIELD_SELECT, - 'options' => array( - 'enable' => __( 'Enable', 'pojo-accessibility' ), - 'disable' => __( 'Disable', 'pojo-accessibility' ), - ), - 'std' => 'enable', - ); - - $fields[] = array( - 'id' => 'pojo_a11y_save_expiration', - 'title' => __( 'Remember user for', 'pojo-accessibility' ), - 'type' => self::FIELD_SELECT, - 'desc' => __( 'Define how long your toolbar settings will be remembered', 'pojo-accessibility' ), - 'options' => array( - '1' => __( '1 Hour', 'pojo-accessibility' ), - '6' => __( '6 Hours', 'pojo-accessibility' ), - '12' => __( '12 Hours', 'pojo-accessibility' ), - '24' => __( '1 Day', 'pojo-accessibility' ), - '48' => __( '2 Days', 'pojo-accessibility' ), - '72' => __( '3 Days', 'pojo-accessibility' ), - '168' => __( '1 Week', 'pojo-accessibility' ), - '720' => __( '1 Month', 'pojo-accessibility' ), - ), - 'std' => '12', - ); - - $sections[] = array( - 'id' => 'section-a11y-settings', - 'page' => self::SETTINGS_PAGE, - 'title' => __( 'General Settings', 'pojo-accessibility' ), - 'intro' => '', - 'fields' => $fields, - ); - - return $sections; - } - - public function print_js() { - // TODO: Maybe need to move to other file - ?> - - section_a11y_toolbar( $sections ); - $sections = $this->section_a11y_settings( $sections ); - $this->_sections = $sections; - return $this->_sections; - } - - public function add_settings_section( $args = array() ) { - $args = wp_parse_args( $args, array( - 'id' => '', - 'title' => '', - ) ); - - foreach ( $this->_sections as $section ) { - if ( $args['id'] !== $section['id'] ) { - continue; - } - if ( empty( $section['intro'] ) ) { - return; - } - printf( '

%s

', $section['intro'] ); - break; - } - } - - public function add_settings_field( $args = array() ) { - if ( empty( $args ) ) { - return; - } - - $args = wp_parse_args( $args, array( - 'id' => '', - 'std' => '', - 'type' => self::FIELD_TEXT, - ) ); - - if ( empty( $args['id'] ) || empty( $args['type'] ) ) { - return; - } - - $field_callback = 'render_' . $args['type'] . '_field'; - if ( method_exists( $this, $field_callback ) ) { - call_user_func( array( $this, $field_callback ), $args ); - } - } - - public function render_select_field( $field ) { - $options = array(); - foreach ( $field['options'] as $option_key => $option_value ) { - $options[] = sprintf( - '', - esc_attr( $option_key ), - selected( get_option( $field['id'], $field['std'] ), $option_key, false ), - $option_value - ); - } - ?> - - - -

- - - /> - - -

- - get_settings_sections() as $section_key => $section ) { - add_settings_section( - $section['id'], - $section['title'], - array( &$this, 'add_settings_section' ), - $section['page'] - ); - - if ( empty( $section['fields'] ) ) { - continue; - } - - foreach ( $section['fields'] as $field ) { - add_settings_field( - $field['id'], - $field['title'], - array( &$this, 'add_settings_field' ), - $section['page'], - $section['id'], - $field - ); - - $sanitize_callback = array( $this, 'field_html' ); - if ( ! empty( $field['type'] ) && self::FIELD_CHECKBOX_LIST === $field['type'] ) { - $sanitize_callback = array( $this, 'field_checkbox_list' ); - } - if ( ! empty( $field['sanitize_callback'] ) ) { - $sanitize_callback = $field['sanitize_callback']; - } - - register_setting( $section['page'], $field['id'], $sanitize_callback ); - } - } - } - - public static function field_html( $input ) { - return stripslashes( wp_filter_post_kses( addslashes( $input ) ) ); - } - - public static function field_checkbox_list( $input ) { - if ( empty( $input ) ) { - $input = array(); - } - - return $input; - } - - public function display_settings_page() { - $screen = get_current_screen(); - $screen_id = $screen->id; - if ( false !== strpos( $screen_id, 'toolbar' ) ) { - $screen_id = self::TOOLBAR_PAGE; - } - ?> -
-

_page_title; ?>

- -
- -
- -
- menu_slug = add_menu_page( - __( 'Accessibility', 'pojo-accessibility' ), - __( 'Accessibility', 'pojo-accessibility' ), - 'manage_options', - 'accessibility-settings', - array( &$this, 'display_settings_page' ), - 'dashicons-universal-access-alt' - ); - add_submenu_page( - 'accessibility-settings', - __( 'Accessibility Settings', 'pojo-accessibility' ), - __( 'Settings', 'pojo-accessibility' ), - 'manage_options', - 'accessibility-settings', - array( &$this, 'display_settings_page' ) - ); - add_submenu_page( - 'accessibility-settings', - __( 'Accessibility Toolbar', 'pojo-accessibility' ), - __( 'Toolbar', 'pojo-accessibility' ), - 'manage_options', - 'accessibility-toolbar', - array( &$this, 'display_settings_page' ) - ); - add_submenu_page( - 'accessibility-settings', - __( 'Customize', 'pojo-accessibility' ), - __( 'Customize', 'pojo-accessibility' ), - 'manage_options', - '/customize.php?autofocus[section]=accessibility' - ); - } - - public function plugin_action_links( $links, $plugin_file ) { - if ( POJO_A11Y_BASE === $plugin_file ) { - $settings = '' . __( 'Settings', 'pojo-accessibility' ) . ''; - $toolbar = '' . __( 'Toolbar', 'pojo-accessibility' ) . ''; - $customizer = '' . __( 'Customize', 'pojo-accessibility' ) . ''; - array_unshift( $links, $customizer ); - array_unshift( $links, $toolbar ); - array_unshift( $links, $settings ); - } - return $links; - } - - private function get_admin_url( $type ) { - switch ( $type ) { - case 'customizer': - return admin_url( 'customize.php?autofocus[section]=accessibility' ); - break; - case 'general': - return admin_url( 'admin.php?page=accessibility-settings' ); - break; - case 'toolbar': - return admin_url( 'admin.php?page=accessibility-toolbar' ); - break; - } - } - - private function get_default_values() { - if ( empty( $this->_defaults ) ) { - if ( empty( $this->_sections ) ) { - $this->get_settings_sections(); - } - $defaults = array(); - foreach ( $this->_sections as $section ) { - foreach ( $section['fields'] as $field ) { - $defaults[ $field['id'] ] = isset( $field['std'] ) ? $field['std'] : ''; - } - } - $this->_defaults = $defaults; - } - } - - public function get_default_title_text( $option ) { - $this->get_default_values(); - $default = isset( $this->_defaults[ $option ] ) ? $this->_defaults[ $option ] : ''; - - return get_option( $option, $default ); - } - - public function __construct() { - $this->_page_title = __( 'One Click Accessibility', 'pojo-accessibility' ); - $this->_page_menu_title = __( 'One Click Accessibility', 'pojo-accessibility' ); - $this->_menu_parent = 'themes.php'; - - add_action( 'admin_menu', array( &$this, 'admin_menu' ), 20 ); - add_action( 'admin_init', array( &$this, 'admin_init' ), 20 ); - add_action( 'admin_footer', array( &$this, 'print_js' ) ); - add_filter( 'plugin_action_links_' . POJO_A11Y_BASE, [ $this, 'plugin_action_links' ], 10, 2 ); - } -} \ No newline at end of file diff --git a/includes/pojo-a11y-admin-ui.php b/modules/legacy/components/admin.php similarity index 93% rename from includes/pojo-a11y-admin-ui.php rename to modules/legacy/components/admin.php index 05c963a..ffc15fd 100644 --- a/includes/pojo-a11y-admin-ui.php +++ b/modules/legacy/components/admin.php @@ -1,23 +1,27 @@ _is_elementor_installed() ) { + if ( ! current_user_can( 'install_plugins' ) || Utils::is_elementor_installed() ) { return; } @@ -152,7 +156,7 @@ public function admin_footer_text( $footer_text ) { $current_screen = get_current_screen(); if ( in_array( $current_screen->id, array( self::SETTINGS_SLUG, self::TOOLBAR_SLUG ) ) ) { $footer_text = sprintf( - /* translators: 1: One Click Accessibility, 2: Link to plugin review */ + /* translators: 1: One Click Accessibility, 2: Link to plugin review */ __( 'Enjoyed %1$s? Please leave us a %2$s rating. We really appreciate your support!', 'pojo-accessibility' ), '' . __( 'One Click Accessibility', 'pojo-accessibility' ) . '', '★★★★★' diff --git a/modules/legacy/components/customizer.php b/modules/legacy/components/customizer.php new file mode 100644 index 0000000..03f76a6 --- /dev/null +++ b/modules/legacy/components/customizer.php @@ -0,0 +1,298 @@ + 'a11y_toolbar_icon', + 'title' => __( 'Toolbar Icon', 'pojo-accessibility' ), + 'type' => 'select', + 'choices' => [ + 'one-click' => __( 'One Click', 'pojo-accessibility' ), + 'wheelchair' => __( 'Wheelchair', 'pojo-accessibility' ), + 'accessibility' => __( 'Accessibility', 'pojo-accessibility' ), + ], + 'std' => 'one-click', + 'description' => __( 'Set Toolbar Icon', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'a11y_toolbar_position', + 'title' => __( 'Toolbar Position', 'pojo-accessibility' ), + 'type' => 'select', + 'choices' => [ + 'left' => __( 'Left', 'pojo-accessibility' ), + 'right' => __( 'Right', 'pojo-accessibility' ), + ], + 'std' => is_rtl() ? 'right' : 'left', + 'description' => __( 'Set Toolbar Position', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'a11y_toolbar_distance_top', + 'title' => __( 'Offset from Top (Desktop)', 'pojo-accessibility' ), + 'type' => 'text', + 'std' => '100px', + 'description' => __( 'Set Toolbar top offset (Desktop)', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'a11y_toolbar_distance_top_mobile', + 'title' => __( 'Offset from Top (Mobile)', 'pojo-accessibility' ), + 'type' => 'text', + 'std' => '50px', + 'description' => __( 'Set Toolbar top offset (Mobile)', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'a11y_bg_toolbar', + 'title' => __( 'Toolbar Background', 'pojo-accessibility' ), + 'type' => 'color', + 'std' => '#ffffff', + 'selector' => '#pojo-a11y-toolbar .pojo-a11y-toolbar-overlay', + 'change_type' => 'bg_color', + 'description' => __( 'Set Toolbar background color', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'a11y_color_toolbar', + 'title' => __( 'Toolbar Color', 'pojo-accessibility' ), + 'type' => 'color', + 'std' => '#333333', + 'selector' => '#pojo-a11y-toolbar .pojo-a11y-toolbar-overlay ul.pojo-a11y-toolbar-items li.pojo-a11y-toolbar-item a, #pojo-a11y-toolbar .pojo-a11y-toolbar-overlay p.pojo-a11y-toolbar-title', + 'change_type' => 'color', + 'description' => __( 'Set Toolbar text color', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'a11y_toggle_button_bg_color', + 'title' => __( 'Toggle Button Background', 'pojo-accessibility' ), + 'type' => 'color', + 'std' => '#4054b2', + 'description' => __( 'Set Toolbar toggle button background color', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'a11y_toggle_button_color', + 'title' => __( 'Toggle Button Color', 'pojo-accessibility' ), + 'type' => 'color', + 'std' => '#ffffff', + 'selector' => '#pojo-a11y-toolbar .pojo-a11y-toolbar-toggle a', + 'change_type' => 'color', + 'description' => __( 'Set Toolbar toggle button icon color', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'a11y_bg_active', + 'title' => __( 'Active Background', 'pojo-accessibility' ), + 'type' => 'color', + 'std' => '#4054b2', + 'selector' => '#pojo-a11y-toolbar .pojo-a11y-toolbar-overlay ul.pojo-a11y-toolbar-items li.pojo-a11y-toolbar-item a.active', + 'change_type' => 'bg_color', + 'description' => __( 'Set Toolbar active background color', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'a11y_color_active', + 'title' => __( 'Active Color', 'pojo-accessibility' ), + 'type' => 'color', + 'std' => '#ffffff', + 'selector' => '#pojo-a11y-toolbar .pojo-a11y-toolbar-overlay ul.pojo-a11y-toolbar-items li.pojo-a11y-toolbar-item a.active', + 'change_type' => 'color', + 'description' => __( 'Set Toolbar active text color', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'a11y_focus_outline_style', + 'title' => __( 'Focus Outline Style', 'pojo-accessibility' ), + 'type' => 'select', + 'choices' => [ + 'solid' => __( 'Solid', 'pojo-accessibility' ), + 'dotted' => __( 'Dotted', 'pojo-accessibility' ), + 'dashed' => __( 'Dashed', 'pojo-accessibility' ), + 'double' => __( 'Double', 'pojo-accessibility' ), + 'groove' => __( 'Groove', 'pojo-accessibility' ), + 'ridge' => __( 'Ridge', 'pojo-accessibility' ), + 'outset' => __( 'Outset', 'pojo-accessibility' ), + 'initial' => __( 'Initial', 'pojo-accessibility' ), + ], + 'std' => 'solid', + 'description' => __( 'Set Focus outline style', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'a11y_focus_outline_width', + 'title' => __( 'Focus Outline Width', 'pojo-accessibility' ), + 'type' => 'select', + 'choices' => [ + '1px' => '1px', + '2px' => '2px', + '3px' => '3px', + '4px' => '4px', + '5px' => '5px', + '6px' => '6px', + '7px' => '7px', + '8px' => '8px', + '9px' => '9px', + '10px' => '10px', + ], + 'std' => '1px', + 'description' => __( 'Set Focus outline width', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'a11y_focus_outline_color', + 'title' => __( 'Focus Outline Color', 'pojo-accessibility' ), + 'type' => 'color', + 'std' => '#FF0000', + 'description' => __( 'Set Focus outline color', 'pojo-accessibility' ), + ]; + + return $fields; + } + + public function customize_a11y( $wp_customize ) { + $fields = $this->get_customizer_fields(); + + $section_description = '

' . __( 'Use the control below to customize the appearance and layout of the Accessibility Toolbar', + 'pojo-accessibility' ) . '

' . + sprintf( __( 'Additional Toolbar settings can be configured at the %s page.', + 'pojo-accessibility' ), + '' . __( 'Accessibility Toolbar', + 'pojo-accessibility' ) . '' + ) . '

'; + + $wp_customize->add_section( 'accessibility', [ + 'title' => __( 'Accessibility', 'pojo-accessibility' ), + 'priority' => 30, + 'description' => $section_description, + ] ); + + foreach ( $fields as $field ) { + $customizer_id = POJO_A11Y_CUSTOMIZER_OPTIONS . '[' . $field['id'] . ']'; + $wp_customize->add_setting( $customizer_id, [ + 'default' => $field['std'] ? $field['std'] : null, + 'type' => 'option', + ] ); + switch ( $field['type'] ) { + case 'color': + $wp_customize->add_control( new \WP_Customize_Color_Control( $wp_customize, $field['id'], [ + 'label' => $field['title'], + 'section' => 'accessibility', + 'settings' => $customizer_id, + 'description' => isset( $field['description'] ) ? $field['description'] : null, + ] ) ); + break; + case 'select': + case 'text': + $wp_customize->add_control( $field['id'], [ + 'label' => $field['title'], + 'section' => 'accessibility', + 'settings' => $customizer_id, + 'type' => $field['type'], + 'choices' => isset( $field['choices'] ) ? $field['choices'] : null, + 'description' => isset( $field['description'] ) ? $field['description'] : null, + ] ); + break; + } + } + } + + public function get_custom_css_code() { + $options = $this->get_customizer_options(); + $bg_color = $options['a11y_toggle_button_bg_color']; // get_theme_mod( 'a11y_toggle_button_bg_color', '#4054b2' ); + if ( ! empty( $bg_color ) ) { + $this->add_css_rule( '#pojo-a11y-toolbar .pojo-a11y-toolbar-toggle a', 'background-color', $bg_color ); + $this->add_css_rule( '#pojo-a11y-toolbar .pojo-a11y-toolbar-overlay, #pojo-a11y-toolbar .pojo-a11y-toolbar-overlay ul.pojo-a11y-toolbar-items.pojo-a11y-links', + 'border-color', $bg_color ); + } + + $outline_style = $options['a11y_focus_outline_style']; //get_theme_mod( 'a11y_focus_outline_style', 'solid' ); + if ( ! empty( $outline_style ) ) { + $this->add_css_rule( 'body.pojo-a11y-focusable a:focus', 'outline-style', $outline_style . ' !important' ); + } + + $outline_width = $options['a11y_focus_outline_width']; // get_theme_mod( 'a11y_focus_outline_width', '1px' ); + if ( ! empty( $outline_width ) ) { + $this->add_css_rule( 'body.pojo-a11y-focusable a:focus', 'outline-width', $outline_width . ' !important' ); + } + + $outline_color = $options['a11y_focus_outline_color']; //get_theme_mod( 'a11y_focus_outline_color', '#FF0000' ); + if ( ! empty( $outline_color ) ) { + $this->add_css_rule( 'body.pojo-a11y-focusable a:focus', 'outline-color', $outline_color . ' !important' ); + } + + $distance_top = $options['a11y_toolbar_distance_top']; //get_theme_mod( 'a11y_toolbar_distance_top', '100px' ); + if ( ! empty( $distance_top ) ) { + $this->add_css_rule( '#pojo-a11y-toolbar', 'top', $distance_top . ' !important' ); + } + + $distance_top_mobile = $options['a11y_toolbar_distance_top_mobile']; // get_theme_mod( 'a11y_toolbar_distance_top_mobile', '50px' ); + if ( ! empty( $distance_top_mobile ) ) { + $this->add_css_code( "@media (max-width: 767px) { #pojo-a11y-toolbar { top: {$distance_top_mobile} !important; } }" ); + } + + $fields = $this->get_customizer_fields(); + foreach ( $fields as $field ) { + if ( empty( $field['selector'] ) || empty( $field['change_type'] ) ) { + continue; + } + + $option = $options[ $field['id'] ]; + + if ( 'color' === $field['change_type'] ) { + $this->add_css_rule( $field['selector'], 'color', $option ); + } elseif ( 'bg_color' === $field['change_type'] ) { + $this->add_css_rule( $field['selector'], 'background-color', $option ); + } + } + } + + private function get_customizer_options() { + static $options = false; + if ( false === $options ) { + $options = get_option( POJO_A11Y_CUSTOMIZER_OPTIONS ); + } + + return $options; + } + + private function add_css_rule( $selector, $rule, $value ) { + if ( ! isset( $this->css_rules[ $selector ] ) ) { + $this->css_rules[ $selector ] = []; + } + $this->css_rules[ $selector ][] = $rule . ': ' . $value . ';'; + } + + private function add_css_code( $code ) { + $this->css_code .= "\n" . $code; + } + + public function print_css_code() { + $this->get_custom_css_code(); + $css = ''; + foreach ( $this->css_rules as $selector => $css_rules ) { + $css .= "\n" . $selector . '{ ' . implode( "\t", $css_rules ) . '}'; + } + echo ''; + } + + public function __construct() { + add_filter( 'customize_register', [ $this, 'customize_a11y' ], 610 ); + add_filter( 'wp_head', [ $this, 'print_css_code' ] ); + } +} diff --git a/includes/pojo-a11y-elementor.php b/modules/legacy/components/elementor.php similarity index 66% rename from includes/pojo-a11y-elementor.php rename to modules/legacy/components/elementor.php index 819c1aa..a260e5c 100644 --- a/includes/pojo-a11y-elementor.php +++ b/modules/legacy/components/elementor.php @@ -1,8 +1,15 @@ preview->is_preview_mode() ) { $is_active = false; diff --git a/includes/pojo-a11y-frontend.php b/modules/legacy/components/frontend.php similarity index 89% rename from includes/pojo-a11y-frontend.php rename to modules/legacy/components/frontend.php index 05a5659..456f9e3 100644 --- a/includes/pojo-a11y-frontend.php +++ b/modules/legacy/components/frontend.php @@ -1,8 +1,18 @@ settings->get_default_title_text( "pojo_a11y_toolbar_button_{$button_type}_title" ); + /** + * @var Settings $settings + */ + $settings = Module::get_settings(); + $title = $settings->get_default_title_text( "pojo_a11y_toolbar_button_{$button_type}_title" ); return '' . $this->get_toolbar_svg( $button_type, $title ) . '' . $title . ''; } public function enqueue_scripts() { wp_register_script( 'pojo-a11y', - POJO_A11Y_ASSETS_URL . 'js/app.min.js', - array( - 'jquery', - ), + EA11Y_ASSETS_URL . 'js/app.min.js', + [ 'jquery' ], '1.0.0', true ); wp_register_style( 'pojo-a11y', - POJO_A11Y_ASSETS_URL . 'css/style.min.css', - array(), + EA11Y_ASSETS_URL . 'css/style.min.css', + [], '1.0.0' ); @@ -46,13 +58,13 @@ public function enqueue_scripts() { wp_localize_script( 'pojo-a11y', 'PojoA11yOptions', - array( + [ 'focusable' => ( 'enable' === get_option( 'pojo_a11y_focusable' ) ), 'remove_link_target' => ( 'enable' === get_option( 'pojo_a11y_remove_link_target' ) ), 'add_role_links' => ( 'enable' === get_option( 'pojo_a11y_add_role_links' ) ), 'enable_save' => ( 'enable' === get_option( 'pojo_a11y_save' ) ), 'save_expiration' => get_option( 'pojo_a11y_save_expiration' ), - ) + ] ); } @@ -77,16 +89,16 @@ public function print_toolbar() { $customizer_options = get_option( POJO_A11Y_CUSTOMIZER_OPTIONS ); $toolbar_position = $customizer_options['a11y_toolbar_position']; - if ( empty( $toolbar_position ) || ! in_array( $toolbar_position, array( 'right', 'left' ) ) ) { + if ( empty( $toolbar_position ) || ! in_array( $toolbar_position, [ 'right', 'left' ] ) ) { $toolbar_position = 'left'; } - - $toolbar_title = Pojo_Accessibility::$instance->settings->get_default_title_text( 'pojo_a11y_toolbar_title' ); + $settings = Module::get_settings(); + $toolbar_title = $settings->get_default_title_text( 'pojo_a11y_toolbar_title' ); $toolbar_visibility = get_option( 'pojo_a11y_toolbar' ); - $wrapper_classes = array( + $wrapper_classes = [ 'pojo-a11y-toolbar-' . $toolbar_position, - ); + ]; if ( 'enable' !== $toolbar_visibility ) { $wrapper_classes[] = 'pojo-a11y-' . $toolbar_visibility; @@ -114,7 +126,7 @@ public function print_toolbar() { @@ -219,7 +231,7 @@ public function print_toolbar() { } private function get_toolbar_svg( $icon, $icon_title = '' ) { - $icons = array( + $icons = [ 'resize_font_add' => '', 'resize_font_less' => '', 'grayscale' => '', @@ -232,7 +244,7 @@ private function get_toolbar_svg( $icon, $icon_title = '' ) { 'sitemap' => '', 'help' => '', 'feedback' => '', - ); + ]; if ( isset( $icons[ $icon ] ) ) { $icon_title_html = ''; @@ -248,11 +260,11 @@ private function get_toolbar_svg( $icon, $icon_title = '' ) { private function get_svg_icon( $icon ) { if ( null === $this->svg_icons ) { - $this->svg_icons = array( + $this->svg_icons = [ 'wheelchair' => '', 'one-click' => '', 'accessibility' => '', - ); + ]; } if ( isset( $this->svg_icons[ $icon ] ) ) { @@ -263,10 +275,8 @@ private function get_svg_icon( $icon ) { } public function __construct() { - add_action( 'wp_enqueue_scripts', array( &$this, 'enqueue_scripts' ) ); - - add_action( 'wp_footer', array( &$this, 'print_skip_to_content_link' ), 20 ); - add_action( 'wp_footer', array( &$this, 'print_toolbar' ), 30 ); + add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_scripts' ] ); + add_action( 'wp_footer', [ $this, 'print_skip_to_content_link' ], 20 ); + add_action( 'wp_footer', [ $this, 'print_toolbar' ], 30 ); } - } diff --git a/modules/legacy/components/settings.php b/modules/legacy/components/settings.php new file mode 100644 index 0000000..6e86fb4 --- /dev/null +++ b/modules/legacy/components/settings.php @@ -0,0 +1,671 @@ + 'pojo_a11y_toolbar', + 'title' => __( 'Display Toolbar', 'pojo-accessibility' ), + 'type' => self::FIELD_SELECT, + 'options' => [ + 'enable' => __( 'Show on all devices', 'pojo-accessibility' ), + 'visible-desktop' => __( 'Visible Desktop', 'pojo-accessibility' ), + 'visible-tablet' => __( 'Visible Tablet', 'pojo-accessibility' ), + 'visible-phone' => __( 'Visible Phone', 'pojo-accessibility' ), + 'hidden-desktop' => __( 'Hidden Desktop', 'pojo-accessibility' ), + 'hidden-tablet' => __( 'Hidden Tablet', 'pojo-accessibility' ), + 'hidden-phone' => __( 'Hidden Phone', 'pojo-accessibility' ), + 'disable' => __( 'Disable', 'pojo-accessibility' ), + ], + 'std' => 'enable', + ]; + + $toolbar_options_classes = 'pojo-a11y-toolbar-button'; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_title', + 'title' => __( 'Title', 'pojo-accessibility' ), + 'type' => self::FIELD_TEXT, + 'desc' => __( 'Title top of the toolbar (recommended).', 'pojo-accessibility' ), + 'class' => $toolbar_options_classes, + 'std' => __( 'Accessibility Tools', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_resize_font', + 'title' => __( 'Resize Font', 'pojo-accessibility' ), + 'type' => self::FIELD_SELECT, + 'class' => $toolbar_options_classes, + 'options' => [ + 'enable' => __( 'Enable', 'pojo-accessibility' ), + 'disable' => __( 'Disable', 'pojo-accessibility' ), + ], + 'std' => 'enable', + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_resize_font_add_title', + 'title' => __( 'Increase Text', 'pojo-accessibility' ), + 'type' => self::FIELD_TEXT, + 'class' => $toolbar_options_classes . ' pojo-settings-child-row no-border', + 'std' => __( 'Increase Text', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_resize_font_less_title', + 'title' => __( 'Decrease Text', 'pojo-accessibility' ), + 'type' => self::FIELD_TEXT, + 'class' => $toolbar_options_classes . ' pojo-settings-child-row', + 'std' => __( 'Decrease Text', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_grayscale', + 'title' => __( 'Grayscale', 'pojo-accessibility' ), + 'type' => self::FIELD_SELECT, + 'class' => $toolbar_options_classes, + 'options' => [ + 'enable' => __( 'Enable', 'pojo-accessibility' ), + 'disable' => __( 'Disable', 'pojo-accessibility' ), + ], + 'std' => 'enable', + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_grayscale_title', + 'title' => __( 'Grayscale Title', 'pojo-accessibility' ), + 'type' => self::FIELD_TEXT, + 'class' => $toolbar_options_classes . ' pojo-settings-child-row', + 'std' => __( 'Grayscale', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_high_contrast', + 'title' => __( 'High Contrast', 'pojo-accessibility' ), + 'type' => self::FIELD_SELECT, + 'class' => $toolbar_options_classes, + 'options' => [ + 'enable' => __( 'Enable', 'pojo-accessibility' ), + 'disable' => __( 'Disable', 'pojo-accessibility' ), + ], + 'std' => 'enable', + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_high_contrast_title', + 'title' => __( 'High Contrast Title', 'pojo-accessibility' ), + 'type' => self::FIELD_TEXT, + 'class' => $toolbar_options_classes . ' pojo-settings-child-row', + 'std' => __( 'High Contrast', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_negative_contrast', + 'title' => __( 'Negative Contrast', 'pojo-accessibility' ), + 'type' => self::FIELD_SELECT, + 'class' => $toolbar_options_classes, + 'options' => [ + 'enable' => __( 'Enable', 'pojo-accessibility' ), + 'disable' => __( 'Disable', 'pojo-accessibility' ), + ], + 'std' => 'enable', + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_negative_contrast_title', + 'title' => __( 'Negative Contrast Title', 'pojo-accessibility' ), + 'type' => self::FIELD_TEXT, + 'class' => $toolbar_options_classes . ' pojo-settings-child-row', + 'std' => __( 'Negative Contrast', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_light_bg', + 'title' => __( 'Light Background', 'pojo-accessibility' ), + 'type' => self::FIELD_SELECT, + 'class' => $toolbar_options_classes, + 'options' => [ + 'enable' => __( 'Enable', 'pojo-accessibility' ), + 'disable' => __( 'Disable', 'pojo-accessibility' ), + ], + 'std' => 'enable', + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_light_bg_title', + 'title' => __( 'Light Background Title', 'pojo-accessibility' ), + 'type' => self::FIELD_TEXT, + 'class' => $toolbar_options_classes . ' pojo-settings-child-row', + 'std' => __( 'Light Background', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_links_underline', + 'title' => __( 'Links Underline', 'pojo-accessibility' ), + 'type' => self::FIELD_SELECT, + 'class' => $toolbar_options_classes, + 'options' => [ + 'enable' => __( 'Enable', 'pojo-accessibility' ), + 'disable' => __( 'Disable', 'pojo-accessibility' ), + ], + 'std' => 'enable', + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_links_underline_title', + 'title' => __( 'Links Underline Title', 'pojo-accessibility' ), + 'type' => self::FIELD_TEXT, + 'class' => $toolbar_options_classes . ' pojo-settings-child-row', + 'std' => __( 'Links Underline', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_readable_font', + 'title' => __( 'Readable Font', 'pojo-accessibility' ), + 'type' => self::FIELD_SELECT, + 'class' => $toolbar_options_classes, + 'options' => [ + 'enable' => __( 'Enable', 'pojo-accessibility' ), + 'disable' => __( 'Disable', 'pojo-accessibility' ), + ], + 'std' => 'enable', + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_readable_font_title', + 'title' => __( 'Readable Font Title', 'pojo-accessibility' ), + 'type' => self::FIELD_TEXT, + 'class' => $toolbar_options_classes . ' pojo-settings-child-row', + 'std' => __( 'Readable Font', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_sitemap_title', + 'title' => __( 'Sitemap Title', 'pojo-accessibility' ), + 'type' => self::FIELD_TEXT, + 'class' => $toolbar_options_classes, + 'std' => __( 'Sitemap', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_sitemap_link', + 'title' => __( 'Sitemap Link', 'pojo-accessibility' ), + 'type' => self::FIELD_TEXT, + 'placeholder' => 'http://your-domain.com/sitemap', + 'desc' => __( 'Link for sitemap page in your website. Leave blank to disable.', + 'pojo-accessibility' ), + 'class' => $toolbar_options_classes . ' pojo-settings-child-row', + 'std' => '', + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_help_title', + 'title' => __( 'Help Title', 'pojo-accessibility' ), + 'type' => self::FIELD_TEXT, + 'class' => $toolbar_options_classes, + 'std' => __( 'Help', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_help_link', + 'title' => __( 'Help Link', 'pojo-accessibility' ), + 'type' => self::FIELD_TEXT, + 'placeholder' => 'http://your-domain.com/help', + 'desc' => __( 'Link for help page in your website. Leave blank to disable.', 'pojo-accessibility' ), + 'class' => $toolbar_options_classes . ' pojo-settings-child-row', + 'std' => '', + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_feedback_title', + 'title' => __( 'Feedback Title', 'pojo-accessibility' ), + 'type' => self::FIELD_TEXT, + 'class' => $toolbar_options_classes, + 'std' => __( 'Feedback', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_feedback_link', + 'title' => __( 'Feedback Link', 'pojo-accessibility' ), + 'type' => self::FIELD_TEXT, + 'placeholder' => 'http://your-domain.com/feedback', + 'desc' => __( 'Link for feedback page in your website. Leave blank to disable.', + 'pojo-accessibility' ), + 'class' => $toolbar_options_classes . ' pojo-settings-child-row', + 'std' => '', + ]; + + $sections[] = [ + 'id' => 'section-a11y-toolbar', + 'page' => self::TOOLBAR_PAGE, + 'title' => __( 'Toolbar Settings', 'pojo-accessibility' ), + 'intro' => '', + 'fields' => $fields, + ]; + + $sections[] = [ + 'id' => 'section-a11y-styles', + 'page' => self::TOOLBAR_PAGE, + 'title' => __( 'Style Settings', 'pojo-accessibility' ), + 'intro' => sprintf( __( 'For style settings of accessibility tools go to > Customize > Accessibility.', 'pojo-accessibility' ), + $this->get_admin_url( 'customizer' ) + ), + 'fields' => [], + ]; + + return $sections; + } + + public function section_a11y_settings( $sections ) { + $fields = []; + + $fields[] = [ + 'id' => 'pojo_a11y_focusable', + 'title' => __( 'Add Outline Focus', 'pojo-accessibility' ), + 'type' => self::FIELD_SELECT, + 'desc' => __( 'Add outline to elements on keyboard focus.', 'pojo-accessibility' ), + 'options' => [ + 'enable' => __( 'Enable', 'pojo-accessibility' ), + 'disable' => __( 'Disable', 'pojo-accessibility' ), + ], + 'std' => 'disable', + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_skip_to_content_link', + 'title' => __( 'Skip to Content link', 'pojo-accessibility' ), + 'type' => self::FIELD_SELECT, + 'desc' => __( 'Add skip to content link when using keyboard.', 'pojo-accessibility' ), + 'options' => [ + 'enable' => __( 'Enable', 'pojo-accessibility' ), + 'disable' => __( 'Disable', 'pojo-accessibility' ), + ], + 'std' => 'enable', + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_skip_to_content_link_element_id', + 'title' => __( 'Skip to Content Element ID', 'pojo-accessibility' ), + 'placeholder' => 'content', + 'type' => self::FIELD_TEXT, + 'std' => 'content', + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_remove_link_target', + 'title' => __( 'Remove target attribute from links', 'pojo-accessibility' ), + 'type' => self::FIELD_SELECT, + 'desc' => __( 'This option will reset all your target links to open in the same window or tab.', + 'pojo-accessibility' ), + 'options' => [ + 'enable' => __( 'Enable', 'pojo-accessibility' ), + 'disable' => __( 'Disable', 'pojo-accessibility' ), + ], + 'std' => 'disable', + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_add_role_links', + 'title' => __( 'Add landmark roles to all links', 'pojo-accessibility' ), + 'type' => self::FIELD_SELECT, + 'desc' => __( 'This option will add role="link" to all links on the page.', + 'pojo-accessibility' ), + 'options' => [ + 'enable' => __( 'Enable', 'pojo-accessibility' ), + 'disable' => __( 'Disable', 'pojo-accessibility' ), + ], + 'std' => 'enable', + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_save', + 'title' => __( 'Sitewide Accessibility', 'pojo-accessibility' ), + 'desc' => __( 'Consistent accessibility throughout your site visit. Site remembers you and stays accessible.', + 'pojo-accessibility' ), + 'type' => self::FIELD_SELECT, + 'options' => [ + 'enable' => __( 'Enable', 'pojo-accessibility' ), + 'disable' => __( 'Disable', 'pojo-accessibility' ), + ], + 'std' => 'enable', + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_save_expiration', + 'title' => __( 'Remember user for', 'pojo-accessibility' ), + 'type' => self::FIELD_SELECT, + 'desc' => __( 'Define how long your toolbar settings will be remembered', 'pojo-accessibility' ), + 'options' => [ + '1' => __( '1 Hour', 'pojo-accessibility' ), + '6' => __( '6 Hours', 'pojo-accessibility' ), + '12' => __( '12 Hours', 'pojo-accessibility' ), + '24' => __( '1 Day', 'pojo-accessibility' ), + '48' => __( '2 Days', 'pojo-accessibility' ), + '72' => __( '3 Days', 'pojo-accessibility' ), + '168' => __( '1 Week', 'pojo-accessibility' ), + '720' => __( '1 Month', 'pojo-accessibility' ), + ], + 'std' => '12', + ]; + + $sections[] = [ + 'id' => 'section-a11y-settings', + 'page' => self::SETTINGS_PAGE, + 'title' => __( 'General Settings', 'pojo-accessibility' ), + 'intro' => '', + 'fields' => $fields, + ]; + + return $sections; + } + + public function print_js() { + // TODO: Maybe need to move to other file + ?> + + section_a11y_toolbar( $sections ); + $sections = $this->section_a11y_settings( $sections ); + $this->_sections = $sections; + + return $this->_sections; + } + + public function add_settings_section( $args = [] ) { + $args = wp_parse_args( $args, [ + 'id' => '', + 'title' => '', + ] ); + + foreach ( $this->_sections as $section ) { + if ( $args['id'] !== $section['id'] ) { + continue; + } + if ( empty( $section['intro'] ) ) { + return; + } + printf( '

%s

', $section['intro'] ); + break; + } + } + + public function add_settings_field( $args = [] ) { + if ( empty( $args ) ) { + return; + } + + $args = wp_parse_args( $args, [ + 'id' => '', + 'std' => '', + 'type' => self::FIELD_TEXT, + ] ); + + if ( empty( $args['id'] ) || empty( $args['type'] ) ) { + return; + } + + $field_callback = 'render_' . $args['type'] . '_field'; + if ( method_exists( $this, $field_callback ) ) { + call_user_func( [ $this, $field_callback ], $args ); + } + } + + public function render_select_field( $field ) { + $options = []; + foreach ( $field['options'] as $option_key => $option_value ) { + $options[] = sprintf( + '', + esc_attr( $option_key ), + selected( get_option( $field['id'], $field['std'] ), $option_key, false ), + $option_value + ); + } + ?> + + + +

+ + + /> + + +

+ + get_settings_sections() as $section_key => $section ) { + add_settings_section( + $section['id'], + $section['title'], + [ &$this, 'add_settings_section' ], + $section['page'] + ); + + if ( empty( $section['fields'] ) ) { + continue; + } + + foreach ( $section['fields'] as $field ) { + add_settings_field( + $field['id'], + $field['title'], + [ &$this, 'add_settings_field' ], + $section['page'], + $section['id'], + $field + ); + + $sanitize_callback = [ $this, 'field_html' ]; + if ( ! empty( $field['type'] ) && self::FIELD_CHECKBOX_LIST === $field['type'] ) { + $sanitize_callback = [ $this, 'field_checkbox_list' ]; + } + if ( ! empty( $field['sanitize_callback'] ) ) { + $sanitize_callback = $field['sanitize_callback']; + } + + register_setting( $section['page'], $field['id'], $sanitize_callback ); + } + } + } + + public static function field_html( $input ) { + return stripslashes( wp_filter_post_kses( addslashes( $input ) ) ); + } + + public static function field_checkbox_list( $input ) { + if ( empty( $input ) ) { + $input = []; + } + + return $input; + } + + public function display_settings_page() { + $screen = get_current_screen(); + $screen_id = $screen->id; + if ( false !== strpos( $screen_id, 'toolbar' ) ) { + $screen_id = self::TOOLBAR_PAGE; + } + ?> +
+

_page_title; ?>

+ +
+ +
+ +
+ menu_slug = add_menu_page( + __( 'Accessibility', 'pojo-accessibility' ), + __( 'Accessibility', 'pojo-accessibility' ), + 'manage_options', + 'accessibility-settings', + [ &$this, 'display_settings_page' ], + 'dashicons-universal-access-alt' + ); + add_submenu_page( + 'accessibility-settings', + __( 'Accessibility Settings', 'pojo-accessibility' ), + __( 'Settings', 'pojo-accessibility' ), + 'manage_options', + 'accessibility-settings', + [ &$this, 'display_settings_page' ] + ); + add_submenu_page( + 'accessibility-settings', + __( 'Accessibility Toolbar', 'pojo-accessibility' ), + __( 'Toolbar', 'pojo-accessibility' ), + 'manage_options', + 'accessibility-toolbar', + [ &$this, 'display_settings_page' ] + ); + add_submenu_page( + 'accessibility-settings', + __( 'Customize', 'pojo-accessibility' ), + __( 'Customize', 'pojo-accessibility' ), + 'manage_options', + '/customize.php?autofocus[section]=accessibility' + ); + } + + public function plugin_action_links( $links, $plugin_file ) { + if ( EA11Y_BASE === $plugin_file ) { + $settings = '' . __( 'Settings', 'pojo-accessibility' ) . ''; + $toolbar = '' . __( 'Toolbar', 'pojo-accessibility' ) . ''; + $customizer = '' . __( 'Customize', 'pojo-accessibility' ) . ''; + array_unshift( $links, $customizer ); + array_unshift( $links, $toolbar ); + array_unshift( $links, $settings ); + } + + return $links; + } + + private function get_admin_url( $type ) { + switch ( $type ) { + case 'customizer': + return admin_url( 'customize.php?autofocus[section]=accessibility' ); + break; + case 'general': + return admin_url( 'admin.php?page=accessibility-settings' ); + break; + case 'toolbar': + return admin_url( 'admin.php?page=accessibility-toolbar' ); + break; + } + } + + private function get_default_values() { + if ( empty( $this->_defaults ) ) { + if ( empty( $this->_sections ) ) { + $this->get_settings_sections(); + } + $defaults = []; + foreach ( $this->_sections as $section ) { + foreach ( $section['fields'] as $field ) { + $defaults[ $field['id'] ] = isset( $field['std'] ) ? $field['std'] : ''; + } + } + $this->_defaults = $defaults; + } + } + + public function get_default_title_text( $option ) { + $this->get_default_values(); + $default = isset( $this->_defaults[ $option ] ) ? $this->_defaults[ $option ] : ''; + + return get_option( $option, $default ); + } + + public function __construct() { + $this->_page_title = __( 'One Click Accessibility', 'pojo-accessibility' ); + $this->_page_menu_title = __( 'One Click Accessibility', 'pojo-accessibility' ); + $this->_menu_parent = 'themes.php'; + + add_action( 'admin_menu', [ &$this, 'admin_menu' ], 20 ); + add_action( 'admin_init', [ &$this, 'admin_init' ], 20 ); + add_action( 'admin_footer', [ &$this, 'print_js' ] ); + add_filter( 'plugin_action_links_' . EA11Y_BASE, [ $this, 'plugin_action_links' ], 10, 2 ); + } +} diff --git a/modules/legacy/module.php b/modules/legacy/module.php new file mode 100644 index 0000000..3b04414 --- /dev/null +++ b/modules/legacy/module.php @@ -0,0 +1,67 @@ +get_components( 'Customizer' ); + $customizer_fields = $customizer->get_customizer_fields(); + $options = []; + $mods = get_theme_mods(); + foreach ( $customizer_fields as $field ) { + if ( isset( $mods[ $field['id'] ] ) ) { + $options[ $field['id'] ] = $mods[ $field['id'] ]; + } else { + $options[ $field['id'] ] = $field['std']; + } + } + update_option( POJO_A11Y_CUSTOMIZER_OPTIONS, $options ); + } + } + + public function add_elementor_support() { + $this->register_components( [ 'Elementor' ] ); + } + + public static function get_settings() { + /** + * @var Settings $settings + */ + return Plugin::instance()->modules_manager->get_modules( 'Legacy' )->get_component( 'Settings' ); + } + + /** + * Module constructor. + */ + public function __construct() { + $this->register_components(); + add_action( 'admin_init', [ $this, 'backwards_compatibility' ] ); + add_action( 'elementor/init', [ $this, 'add_elementor_support' ] ); + } +} diff --git a/plugin.php b/plugin.php new file mode 100644 index 0000000..d0f561b --- /dev/null +++ b/plugin.php @@ -0,0 +1,111 @@ +classes_aliases[ $class ] ); + + // Backward Compatibility: Save old class name for set an alias after the new class is loaded + if ( $has_class_alias ) { + $class_alias_name = $this->classes_aliases[ $class ]; + $class_to_load = $class_alias_name; + } else { + $class_to_load = $class; + } + + if ( ! class_exists( $class_to_load ) ) { + $filename = strtolower( + preg_replace( + [ '/^' . __NAMESPACE__ . '\\\/', '/([a-z])([A-Z])/', '/_/', '/\\\/' ], + [ '', '$1-$2', '-', DIRECTORY_SEPARATOR ], + $class_to_load + ) + ); + $filename = EA11Y_PATH . $filename . '.php'; + + if ( is_readable( $filename ) ) { + include $filename; + } + } + + if ( $has_class_alias ) { + class_alias( $class_alias_name, $class ); + } + } + + private function includes() { + require_once EA11Y_PATH . 'includes/manager.php'; + $this->modules_manager = new \EA11y\Manager(); + } + + /** + * Plugin class constructor + * + * Register plugin action hooks and filters + * + * @access public + */ + public function __construct() { + static $autoloader_registered = false; + if ( ! $autoloader_registered ) { + $autoloader_registered = spl_autoload_register( [ $this, 'autoload' ] ); + } + $this->includes(); + } +} +// Instantiate Plugin Class +Plugin::instance(); diff --git a/pojo-accessibility.php b/pojo-accessibility.php index 54f0af1..b41e9e9 100644 --- a/pojo-accessibility.php +++ b/pojo-accessibility.php @@ -9,15 +9,21 @@ Text Domain: pojo-accessibility Domain Path: /languages/ */ -if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly +if ( ! defined( 'ABSPATH' ) ) { + exit; +} // Exit if accessed directly -define( 'POJO_A11Y__FILE__', __FILE__ ); -define( 'POJO_A11Y_BASE', plugin_basename( POJO_A11Y__FILE__ ) ); -define( 'POJO_A11Y_URL', plugins_url( '/', POJO_A11Y__FILE__ ) ); -define( 'POJO_A11Y_ASSETS_PATH', plugin_dir_path( POJO_A11Y__FILE__ ) . 'assets/' ); -define( 'POJO_A11Y_ASSETS_URL', POJO_A11Y_URL . 'assets/' ); +// Legacy define( 'POJO_A11Y_CUSTOMIZER_OPTIONS', 'pojo_a11y_customizer_options' ); +define( 'EA11Y_VERSION', '2.1.0' ); +define( 'EA11Y_MAIN_FILE', __FILE__ ); +define( 'EA11Y_BASE', plugin_basename( EA11Y_MAIN_FILE ) ); +define( 'EA11Y_PATH', plugin_dir_path( __FILE__ ) ); +define( 'EA11Y_URL', plugins_url( '/', __FILE__ ) ); +define( 'EA11Y_ASSETS_PATH', EA11Y_PATH . 'assets/' ); +define( 'EA11Y_ASSETS_URL', EA11Y_URL . 'assets/' ); + final class Pojo_Accessibility { /** @@ -26,38 +32,16 @@ final class Pojo_Accessibility { */ public static $instance = null; - /** - * @var Pojo_A11y_Frontend - */ - public $frontend; - - /** - * @var Pojo_A11y_Customizer - */ - public $customizer; - - /** - * @var Pojo_A11y_Settings - */ - public $settings; - - /** - * @var Pojo_A11y_Admin_UI - */ - public $admin_ui; - - public function load_textdomain() { + public function i18n() { load_plugin_textdomain( 'pojo-accessibility' ); } /** * Throw error on object clone - * * The whole idea of the singleton design pattern is that there is a single * object therefore, we don't want the object to be cloned. - * - * @since 1.0.0 * @return void + * @since 1.0.0 */ public function __clone() { // Cloning instances of the class is forbidden @@ -66,9 +50,8 @@ public function __clone() { /** * Disable unserializing of the class - * - * @since 1.0.0 * @return void + * @since 1.0.0 */ public function __wakeup() { // Unserializing instances of the class is forbidden @@ -82,49 +65,30 @@ public static function instance() { if ( is_null( self::$instance ) ) { self::$instance = new self(); } - return self::$instance; - } - - public function bootstrap() { - require( 'includes/pojo-a11y-frontend.php' ); - require( 'includes/pojo-a11y-customizer.php' ); - require( 'includes/pojo-a11y-settings.php' ); - require( 'includes/pojo-a11y-admin-ui.php' ); - $this->frontend = new Pojo_A11y_Frontend(); - $this->customizer = new Pojo_A11y_Customizer(); - $this->settings = new Pojo_A11y_Settings(); - $this->admin_ui = new Pojo_A11y_Admin_UI(); - } - - public function backwards_compatibility() { - if ( false === get_option( POJO_A11Y_CUSTOMIZER_OPTIONS, false ) ) { - $customizer_fields = $this->customizer->get_customizer_fields(); - $options = array(); - $mods = get_theme_mods(); - foreach ( $customizer_fields as $field ) { - if ( isset( $mods[ $field['id'] ] ) ) { - $options[ $field['id'] ] = $mods[ $field['id'] ]; - } else { - $options[ $field['id'] ] = $field['std']; - } - } - update_option( POJO_A11Y_CUSTOMIZER_OPTIONS, $options ); - } + return self::$instance; } - public function add_elementor_support() { - require( 'includes/pojo-a11y-elementor.php' ); - - new Pojo_A11y_Elementor(); + /** + * Initialize the plugin + * Do your Validations here: + * for example checks for basic plugin requirements, if one check fail don't continue, + * if all check have passed include the plugin class. + * Fired by `plugins_loaded` action hook. + * @since 2.2.0 + * @access public + */ + public function init() { + // Once we get here, We have passed all validation checks, so we can safely include our plugin + require_once 'plugin.php'; } private function __construct() { - add_action( 'init', array( &$this, 'bootstrap' ) ); - add_action( 'admin_init', array( &$this, 'backwards_compatibility' ) ); - add_action( 'plugins_loaded', array( &$this, 'load_textdomain' ) ); + // Load translation + add_action( 'init', [ $this, 'i18n' ] ); - add_action( 'elementor/init', array( $this, 'add_elementor_support' ) ); + // Init Plugin + add_action( 'plugins_loaded', [ $this, 'init' ] ); } } From c64149555e1778e3e16fbfbb7377595e08468fd4 Mon Sep 17 00:00:00 2001 From: Ohad Date: Tue, 22 Oct 2024 16:28:17 +0300 Subject: [PATCH 02/65] =?UTF-8?q?=E2=9C=85=20Added=20build=20and=20tests?= =?UTF-8?q?=20CI/CD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .build-rsync-exclude | 33 ++ .eslintignore | 10 + .eslintrc | 52 +++ .github/checkstyle-problem-matcher.json | 23 ++ .github/scripts/publish-to-wordpress-org.sh | 65 ++++ .github/scripts/update-version-in-files.js | 63 ++++ .github/scripts/validate-build-files.sh | 49 +++ .github/workflows/build.yml | 92 ++++++ .github/workflows/js-ci.yml | 45 +++ .github/workflows/php-coding-standards.yml | 55 ++++ .github/workflows/phpunit.yml | 146 +++++++++ .gitignore | 8 + .jshintrc | 16 - .travis.yml | 35 --- Gruntfile.js | 297 ------------------ composer.json | 43 ++- .../legacy/assets}/css/style.css | 0 .../legacy/assets}/css/style.min.css | 0 .../legacy/assets}/js/app.dev.js | 0 .../legacy/assets}/js/app.min.js | 0 .../legacy/assets}/js/skip-link-focus-fix.js | 0 .../legacy/assets}/less/_background.less | 0 .../legacy/assets}/less/_high-contrast.less | 0 .../legacy/assets}/less/_mixing.less | 0 .../legacy/assets}/less/_toolbar.less | 0 .../legacy/assets}/less/_underline.less | 0 .../legacy/assets}/less/_visibility.less | 0 .../legacy/assets}/less/style.less | 0 modules/legacy/components/customizer.php | 11 +- modules/legacy/components/frontend.php | 4 +- package.json | 54 ++-- phpunit.xml.dist | 43 +++ ruleset.xml | 54 ++++ tests/bootstrap.php | 54 ++-- tests/phpunit/helpers/functions.php | 8 + tests/phpunit/helpers/module-test-base.php | 77 +++++ tests/phpunit/helpers/route-test.php | 31 ++ tests/phpunit/helpers/test-base.php | 40 +++ .../plugin/modules/legacy/module-test.php | 20 ++ tests/test-base.php | 13 - webpack.config.js | 20 ++ 41 files changed, 1047 insertions(+), 414 deletions(-) create mode 100644 .build-rsync-exclude create mode 100644 .eslintignore create mode 100644 .eslintrc create mode 100644 .github/checkstyle-problem-matcher.json create mode 100644 .github/scripts/publish-to-wordpress-org.sh create mode 100644 .github/scripts/update-version-in-files.js create mode 100644 .github/scripts/validate-build-files.sh create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/js-ci.yml create mode 100644 .github/workflows/php-coding-standards.yml create mode 100644 .github/workflows/phpunit.yml delete mode 100755 .jshintrc delete mode 100644 .travis.yml delete mode 100644 Gruntfile.js rename {assets => modules/legacy/assets}/css/style.css (100%) rename {assets => modules/legacy/assets}/css/style.min.css (100%) rename {assets => modules/legacy/assets}/js/app.dev.js (100%) rename {assets => modules/legacy/assets}/js/app.min.js (100%) rename {assets => modules/legacy/assets}/js/skip-link-focus-fix.js (100%) rename {assets => modules/legacy/assets}/less/_background.less (100%) rename {assets => modules/legacy/assets}/less/_high-contrast.less (100%) rename {assets => modules/legacy/assets}/less/_mixing.less (100%) rename {assets => modules/legacy/assets}/less/_toolbar.less (100%) rename {assets => modules/legacy/assets}/less/_underline.less (100%) rename {assets => modules/legacy/assets}/less/_visibility.less (100%) rename {assets => modules/legacy/assets}/less/style.less (100%) create mode 100644 phpunit.xml.dist create mode 100644 ruleset.xml mode change 100755 => 100644 tests/bootstrap.php create mode 100644 tests/phpunit/helpers/functions.php create mode 100644 tests/phpunit/helpers/module-test-base.php create mode 100644 tests/phpunit/helpers/route-test.php create mode 100644 tests/phpunit/helpers/test-base.php create mode 100644 tests/phpunit/plugin/modules/legacy/module-test.php delete mode 100755 tests/test-base.php create mode 100644 webpack.config.js diff --git a/.build-rsync-exclude b/.build-rsync-exclude new file mode 100644 index 0000000..5a08744 --- /dev/null +++ b/.build-rsync-exclude @@ -0,0 +1,33 @@ +.bpg.json +.babelrc +ruleset.xml +webpack.config.js +phpunit.xml.dist +.DS_Store +node_modules/ +.git +.gitignore +.gitmodules +.github/ +.editorconfig +.gitattributes +.eslintrc +.eslintignore +.phpcs.xml.dist +.phpunit.xml.d +coverage/ +assets/dev/ +.phpunit.result* +bin/ +tests/ +package.json +composer.json +composer.lock +package-lock.json +jest.config.js + +.build-rsync-exclude +one-click-accessibility/ +one-click-accessibility.zip +.svn-assets/ +.husky/ diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..9184afc --- /dev/null +++ b/.eslintignore @@ -0,0 +1,10 @@ +assets/lib/**/*.js +assets/js/*.js +**/*.min.js +**/node_modules/** +**/vendor/** +build/** +libraries/** +coverage/** +assets/build/** +tests/** diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..bd2111c --- /dev/null +++ b/.eslintrc @@ -0,0 +1,52 @@ +{ + "extends": [ + "plugin:react/recommended", + "plugin:@wordpress/eslint-plugin/recommended-with-formatting", + "plugin:import/recommended", + "plugin:jsx-a11y/strict" + ], + "plugins": [ + "babel", + "react" + ], + "env": { + "browser": true, + "node": true, + "es6": true, + "jquery": true + }, + "globals": { + "jQuery": "writable" + }, + "parserOptions": { + "ecmaVersion": 8, + "sourceType": "module", + "requireConfigFile": false + }, + "rules": { + "no-console": "off", + "react-hooks/exhaustive-deps": "off", + "strict": [ "error", "global" ], + "curly": "warn", + "import/order": [ + "error", + { + "groups": [ + "builtin", + "external", + "internal", + "parent", + "sibling", + "index", + "object", + "type" + ], + "alphabetize": { + "order": "asc", + "caseInsensitive": false + } + } + ] + }, + "parser": "@babel/eslint-parser" +} diff --git a/.github/checkstyle-problem-matcher.json b/.github/checkstyle-problem-matcher.json new file mode 100644 index 0000000..5c6ce66 --- /dev/null +++ b/.github/checkstyle-problem-matcher.json @@ -0,0 +1,23 @@ +{ + "problemMatcher": [ + { + "owner": "phpcs", + "severity": "error", + "pattern": [ + { + "regexp": "^$", + "file": 1 + }, + { + "regexp": "+)$", + "line": 1, + "column": 2, + "severity": 3, + "message": 4, + "code": 5, + "loop": true + } + ] + } + ] +} diff --git a/.github/scripts/publish-to-wordpress-org.sh b/.github/scripts/publish-to-wordpress-org.sh new file mode 100644 index 0000000..dcbed84 --- /dev/null +++ b/.github/scripts/publish-to-wordpress-org.sh @@ -0,0 +1,65 @@ +#!/bin/bash +set -eo pipefail + +if [[ -z "$SVN_USERNAME" ]]; then + echo "Set the SVN_USERNAME secret" + exit 1 +fi + +if [[ -z "$SVN_PASSWORD" ]]; then + echo "Set the SVN_PASSWORD secret" + exit 1 +fi + +if [[ -z "$PLUGIN_VERSION" ]]; then + echo "Set the PLUGIN_VERSION env var" + exit 1 +fi + +if [[ -z "$PLUGIN_SLUG" ]]; then + echo "Set the PLUGIN_SLUG env var" + exit 1 +fi + +echo "Publish version: ${PLUGIN_VERSION}" + +PLUGIN_PATH="$GITHUB_WORKSPACE/${PLUGIN_SLUG}" +SVN_PATH="$GITHUB_WORKSPACE/svn" + +cd $PLUGIN_PATH +mkdir -p $SVN_PATH +cd $SVN_PATH + +echo "Checkout from SVN" +svn co https://plugins.svn.wordpress.org/${PLUGIN_SLUG}/trunk + +echo "Clean trunk folder" +cd $SVN_PATH/trunk +find . -maxdepth 1 -not -name ".svn" -not -name "." -not -name ".." -exec rm -rf {} + + +echo "Copy files" +rsync -ah --progress $PLUGIN_PATH/* $SVN_PATH/trunk + +echo "Preparing files" +cd $SVN_PATH/trunk + +echo "svn delete" +svn status | grep -v '^.[ \t]*\\..*' | { grep '^!' || true; } | awk '{print $2}' | xargs -r svn delete; + +echo "svn add" +svn status | grep -v '^.[ \t]*\\..*' | { grep '^?' || true; } | awk '{print $2}' | xargs -r svn add; + +svn status + +echo "Commit files to trunk" +svn ci -m "Upload v${PLUGIN_VERSION}" --no-auth-cache --non-interactive --username "$SVN_USERNAME" --password "$SVN_PASSWORD" + +echo "Copy files from trunk to tag ${PLUGIN_VERSION}" +svn cp https://plugins.svn.wordpress.org/${PLUGIN_SLUG}/trunk https://plugins.svn.wordpress.org/${PLUGIN_SLUG}/tags/${PLUGIN_VERSION} --message "Tagged ${PLUGIN_VERSION}" --no-auth-cache --non-interactive --username "$SVN_USERNAME" --password "$SVN_PASSWORD" +svn update + +echo "Remove the SVN folder from the workspace (for multiple releases in the same Action)" +#rm -rf $SVN_PATH + +echo "Back to the workspace root" +cd $GITHUB_WORKSPACE diff --git a/.github/scripts/update-version-in-files.js b/.github/scripts/update-version-in-files.js new file mode 100644 index 0000000..03ede1b --- /dev/null +++ b/.github/scripts/update-version-in-files.js @@ -0,0 +1,63 @@ +'use strict'; + +const fs = require( "fs" ); + +const { VERSION } = process.env; + if ( ! VERSION ) { + throw new Error( 'VERSION is not defined in the environment variables.' ); +} + +async function replaceInFileWithArray( filePath, replacementArray ) { + try { + let content = fs.readFileSync( filePath, { encoding: 'utf8' } ); + + for ( const replacement of replacementArray ) { + console.log( `Replacing ${ replacement.from } with ${ replacement.to } in ${ filePath }` ); + content = content.replace( replacement.from, replacement.to ); + } + console.log( content ); + //fs.writeFileSync( filePath, content, { encoding: 'utf8' } ); + console.log( `All replacements in ${ filePath } were successful.`); + } catch (error) { + console.error( `An error occurred in ${ filePath }:`, error ); + } +} + +const run = async () => { + try { + // update stable tag in readme.txt + await replaceInFileWithArray( './readme.txt', + [ + { + from: /Stable tag: (.*)/m, + to: `Stable tag: ${ VERSION }`, + }, + { + from: /= NEXT_VERSION =/m, + to: `= ${ VERSION } =`, + }, + ] + ); + + // update version in image-optimization.php + await replaceInFileWithArray( './image-optimization.php', + [ + { + from: /\* Version: (.*)/m, + to: `* Version: ${ VERSION }`, + }, + { + from: /define\( \'IMAGE_OPTIMIZER_VERSION\', \'(.*)\' \)\;/m, + to: `define( 'IMAGE_OPTIMIZER_VERSION', '${ VERSION }' );`, + }, + ] + ); + + } catch ( err ) { + // eslint-disable-next-line no-console + console.error( 'Error occurred:', err ); + process.exit( 1 ); + } +}; + +run(); diff --git a/.github/scripts/validate-build-files.sh b/.github/scripts/validate-build-files.sh new file mode 100644 index 0000000..31fb621 --- /dev/null +++ b/.github/scripts/validate-build-files.sh @@ -0,0 +1,49 @@ +#!/bin/bash +set -eo pipefail + +if [[ -z "$PLUGIN_SLUG" ]]; then + echo "Set the PLUGIN_SLUG env var" + exit 1 +fi + +if [[ -z "$PLUGIN_VERSION" ]]; then + echo "Set the PLUGIN_VERSION env var" + exit 1 +fi + +PLUGIN_PATH="$GITHUB_WORKSPACE/${PLUGIN_SLUG}" + +cd $PLUGIN_PATH + +PLUGIN_MAIN_FILE="${PLUGIN_SLUG}.php" + +if [ ! -f "${PLUGIN_MAIN_FILE}" ]; then + echo "${PLUGIN_MAIN_FILE} file does not exist" + exit 1 +fi + +if [ ! -f "readme.txt" ]; then + echo "readme.txt file does not exist" + exit 1 +fi + +if [[ $(grep -c "Version: $PLUGIN_VERSION" "$PLUGIN_MAIN_FILE") -eq 0 ]]; then + echo "${PLUGIN_MAIN_FILE} file does not contain the correct build version : $PLUGIN_VERSION" + EXISTING_VERSION=$(sed -n 's/.*Version: \(.*\)/\1/p' "$PLUGIN_MAIN_FILE") + echo "Existing version: $EXISTING_VERSION" + exit 1 +fi + +if [[ $(grep -c "Stable tag: $PLUGIN_VERSION" "readme.txt") -eq 0 ]]; then + echo "readme.txt file does not contain the correct stable tag version : $PLUGIN_VERSION" + EXISTING_VERSION=$(sed -n 's/.*Stable tag: \(.*\)/\1/p' "readme.txt") + echo "Existing version: $EXISTING_VERSION" + exit 1 +fi + +echo "validate-build-files Details:" +echo "---" +echo "SVN Tag name: $PLUGIN_VERSION" +echo "Package Version: $PACKAGE_VERSION" +echo "Trunk Files:" +ls -la diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..bfe9bf8 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,92 @@ +name: Build +on: + pull_request: + workflow_call: + workflow_dispatch: + inputs: + push_to_slack: + description: 'Push to Slack' + type: boolean + required: true + default: 'false' + +concurrency: + cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.ref }} + +permissions: + contents: read + +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up PHP + uses: shivammathur/setup-php@a36e1e52ff4a1c9e9c9be31551ee4712a6cb6bd0 # 2.27.1 + with: + php-version: '7.4' + env: + fail-fast: 'true' + + - name: OAuth Composer Authentication + run: + composer config -g github-oauth.github.com ${{ secrets.DEVOPS_TOKEN }} + + - name: Install Composer dependencies cache + uses: ramsey/composer-install@v2 + + - name: Install Composer dependencies + run: + composer install --no-dev + + - name: Get current date + id: date + run: echo "::set-output name=date::$(date +'%Y-%m-%d')" + + - name: "Debug info: show tooling versions" + continue-on-error: true + run: | + set +e + echo "Start debug Info" + echo "Date: ${{ steps.date.outputs.date }}" + echo "PHP Version: $(php -v)" + echo "Composer Version: $(composer --version)" + echo "Node Version: $(node --version)" + echo "NPM Version: $(npm --version)" + echo "GIT Version: $(git --version)" + echo "The github context is:" + echo "${{ toJson(github) }}" + echo "End debug Info" + echo "exitcode=$?" >> $GITHUB_OUTPUT + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build + + - name: Build Plugin + run: | + rsync -av --exclude-from=.build-rsync-exclude . one-click-accessibility + zip -r one-click-accessibility.zip one-click-accessibility + + - name: Archive + uses: actions/upload-artifact@v4 + with: + name: one-click-accessibility + path: one-click-accessibility.zip + retention-days: 14 + + - name: Push to Slack on PR merge + if: ${{ github.event.pull_request.merged == true }} + run: | + curl -F file=@one-click-accessibility.zip -F "initial_comment=One Click Accessibility - A new PR has been pushed to the master branch by ${{ github.actor }}." title="${{ github.event.pull_request.title }}" -F channels=C07LFCFNGDB -H "Authorization: Bearer ${{ secrets.CLOUD_SLACK_BOT_TOKEN }}" https://slack.com/api/files.upload + + - name: Push to Slack on Manual Trigger + if: ${{ github.event.inputs.push_to_slack == 'true' }} + run: | + curl -F file=@one-click-accessibility.zip -F "initial_comment=One Click Accessibility - A new build was triggered by ${{ github.actor }}." title="Manuall ${{ steps.date.outputs.date }}" -F channels=C07LFCFNGDB -H "Authorization: Bearer ${{ secrets.CLOUD_SLACK_BOT_TOKEN }}" https://slack.com/api/files.upload diff --git a/.github/workflows/js-ci.yml b/.github/workflows/js-ci.yml new file mode 100644 index 0000000..efcf141 --- /dev/null +++ b/.github/workflows/js-ci.yml @@ -0,0 +1,45 @@ +name: NPM CI + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +concurrency: + cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.ref }} + +env: + token: ${{ secrets.CLOUD_DEVOPS_TOKEN }} + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [18.x] + + steps: + - uses: actions/checkout@v2 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + cache-dependency-path: package-lock.json + + - name: Install Dependencies + run: npm ci + + - name: Build + run: npm run build + + - name: Lint + run: npm run lint:js + +# - name: Test +# run: npm run test diff --git a/.github/workflows/php-coding-standards.yml b/.github/workflows/php-coding-standards.yml new file mode 100644 index 0000000..26a91dc --- /dev/null +++ b/.github/workflows/php-coding-standards.yml @@ -0,0 +1,55 @@ +name: PHP Lint + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +concurrency: + cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.ref }} + +permissions: + contents: read + +jobs: + PHP-Code-Standards: + name: Lint PHP files + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, '[skip PHPCS]') || !contains(github.event.head_commit.message, '[skip CI]')" + steps: + - name: Check out source code + uses: actions/checkout@v4 + + - name: Set up PHP + uses: shivammathur/setup-php@a36e1e52ff4a1c9e9c9be31551ee4712a6cb6bd0 # 2.27.1 + with: + php-version: '7.4' + coverage: none + tools: composer, cs2pr, phpcs + env: + fail-fast: 'true' + + - name: Log debug information + run: | + export PATH=$HOME/.composer/vendor/bin:$PATH + php --version + phpcs -i + composer --version + + - name: Oauth Composer authentication + run: composer config -g github-oauth.github.com ${{ secrets.DEVOPS_TOKEN }} + + - name: Install dependencies + uses: ramsey/composer-install@83af392bf5f031813d25e6fe4cd626cdba9a2df6 # 2.2.0 + + - name: Add error matcher + run: echo "::add-matcher::$(pwd)/.github/checkstyle-problem-matcher.json" + + - name: Run style check + run: | + export PATH=$HOME/.composer/vendor/bin:$PATH + composer run lint -- --report=checkstyle + diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml new file mode 100644 index 0000000..2f9665c --- /dev/null +++ b/.github/workflows/phpunit.yml @@ -0,0 +1,146 @@ +name: PHPUnit + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +concurrency: + cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.ref }} + +permissions: + contents: read + +jobs: + unit-tests: + name: "WP ${{ matrix.wp }}, multisite: ${{ matrix.ms }}, PHP: ${{ matrix.php }}" + if: "!contains(github.event.head_commit.message, '[ci skip]')" + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: [7.4, 8.0, 8.1, 8.2] + wp: [6.4.x, latest, nightly] + ms: [no, yes] + coverage: [no] + phpunit: [9] + include: + # PHP 7.4 is not supported by PHPUnit 9 + - phpunit: 7 + php: 7.4 + # add coverage job + - coverage: yes + php: 8.0 + wp: latest + ms: no + phpunit: 9 + + services: + mysql: + image: mariadb:latest + ports: + - "3306:3306" + env: + MYSQL_ROOT_PASSWORD: wordpress + MARIADB_INITDB_SKIP_TZINFO: 1 + MYSQL_USER: wordpress + MYSQL_PASSWORD: wordpress + MYSQL_DATABASE: wordpress_test + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Matrix variables + id: matrix + run: | + echo "::group::Matrix variables" + echo "PHP version = ${{ matrix.php }}" + echo "WordPress = ${{ matrix.wp }}" + echo "Multisite = ${{ matrix.ms }}" + echo "Coverage = ${{ matrix.coverage }}" + echo "PHPUnit = ${{ matrix.phpunit }}" + echo "::endgroup::" + + - name: Decide whether to enable coverage + id: coverage + run: | + if [ "${{ matrix.coverage }}" = "yes" ]; then + echo "coverage=pcov" >> $GITHUB_OUTPUT + echo 'ini=pcov.directory=inc, pcov.exclude="~/(vendor|tests|node_modules)/~"' + else + echo "coverage=none" >> $GITHUB_OUTPUT + echo "ini=opcache.enable_cli=1, opcache.jit=tracing, opcache.jit_buffer_size=64M" >> $GITHUB_OUTPUT + fi + + - name: Set up PHP + uses: shivammathur/setup-php@a36e1e52ff4a1c9e9c9be31551ee4712a6cb6bd0 # 2.27.1 + with: + coverage: ${{ steps.coverage.outputs.coverage }} + ini-values: ${{ steps.coverage.outputs.ini }} + php-version: ${{ matrix.php }} + env: + fail-fast: 'true' + + - name: Install PHPUnit + run: | + wget -q -O /usr/local/bin/phpunit "https://phar.phpunit.de/phpunit-${{ matrix.phpunit }}.phar" + chmod +x /usr/local/bin/phpunit + + - name: Oauth Composer authentication + run: + composer config -g github-oauth.github.com ${{ secrets.DEVOPS_TOKEN }} + + - name: Install dependencies + uses: ramsey/composer-install@83af392bf5f031813d25e6fe4cd626cdba9a2df6 # 2.2.0 + + #- name: Install Node.js + # uses: actions/setup-node@v2 + # with: + # node-version: 18.x + + #- name: Install dependencies + # run: npm ci + + #- name: Build assets + # run: npm run build + + - name: Set up WordPress and WordPress Test Library + uses: sjinks/setup-wordpress-test-library@dd7f8144e892b042e034d713b734f7a19446abe1 # 2.0.1 + with: + version: ${{ matrix.wp }} + + - name: Set up multisite mode + run: echo "WP_MULTISITE=1" >> $GITHUB_ENV + if: matrix.ms == 'yes' + + - name: Update wp-test-config.php + run: | + if php -r 'exit(PHP_VERSION_ID < 80100);'; then + echo "Disabling WP_DEBUG in wp-test-config.php" + sed -i "s@define( 'WP_DEBUG', true );@// define( 'WP_DEBUG', true );@" /tmp/wordpress-tests-lib/wp-tests-config.php + fi + + - name: Verify MariaDB connection + run: | + while ! mysqladmin ping -h 127.0.0.1 -P ${{ job.services.mysql.ports[3306] }} --silent; do + sleep 1 + done + timeout-minutes: 1 + + - name: Run tests + run: | + OPTIONS= + if [ "${{ steps.coverage.outputs.coverage }}" != 'none' ]; then + OPTIONS="$OPTIONS --coverage-clover=clover.xml" + fi + phpunit --order-by=random ${OPTIONS} + + - name: Upload coverage report + uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # v3.1.4 + with: + files: clover.xml + flags: unittests + if: ${{ steps.coverage.outputs.coverage != 'none' }} diff --git a/.gitignore b/.gitignore index 26b1f11..4d1728c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,11 @@ composer.lock npm-debug.log .DS_Store package-lock.json +bin/.env +*/node_modules +!assets/dev/* +assets/js/ +coverage/ +.phpunit.result.cache +.editorconfig +.vscode diff --git a/.jshintrc b/.jshintrc deleted file mode 100755 index 1164285..0000000 --- a/.jshintrc +++ /dev/null @@ -1,16 +0,0 @@ -{ - "bitwise": true, - "browser": true, - "curly": true, - "eqeqeq": true, - "eqnull": true, - "esnext": true, - "immed": true, - "jquery": true, - "latedef": true, - "newcap": true, - "noarg": true, - "node": true, - "strict": false, - "trailing": true -} \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 5ab28c5..0000000 --- a/.travis.yml +++ /dev/null @@ -1,35 +0,0 @@ -# Travis CI Configuration File - -sudo: false - -# Tell Travis CI we're using PHP -language: php - -# PHP version used in first build configuration. -php: - - 5.3 - - 5.4 - - 5.5 - - 5.6 - - 7.0 - -# WordPress version used in first build configuration. -env: - - WP_VERSION=latest WP_MULTISITE=0 - - WP_VERSION=latest WP_MULTISITE=1 - - WP_VERSION=4.0 WP_MULTISITE=0 - - WP_VERSION=4.0 WP_MULTISITE=1 - - WP_VERSION=4.1 WP_MULTISITE=0 - - WP_VERSION=4.1 WP_MULTISITE=1 - - WP_VERSION=4.2 WP_MULTISITE=0 - - WP_VERSION=4.2 WP_MULTISITE=1 - - WP_VERSION=4.3 WP_MULTISITE=0 - - WP_VERSION=4.3 WP_MULTISITE=1 - -before_script: - - bash bin/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION - -after_success: - - bash <(curl -s https://codecov.io/bash) - -script: phpunit --coverage-clover=coverage.xml \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index 6f76dca..0000000 --- a/Gruntfile.js +++ /dev/null @@ -1,297 +0,0 @@ -/** - * Pojo Makefile - */ -'use strict'; - -module.exports = function( grunt ) { - - require( 'matchdep' ).filterDev( 'grunt-*' ).forEach( grunt.loadNpmTasks ); - - // Project configuration. - grunt.initConfig( { - pkg: grunt.file.readJSON( 'package.json' ), - - banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' + - '<%= grunt.template.today("dd-mm-yyyy") %> */', - - checktextdomain: { - standard: { - options:{ - text_domain: 'pojo-accessibility', - correct_domain: true, - keywords: [ - // WordPress keywords - '__:1,2d', - '_e:1,2d', - '_x:1,2c,3d', - 'esc_html__:1,2d', - 'esc_html_e:1,2d', - 'esc_html_x:1,2c,3d', - 'esc_attr__:1,2d', - 'esc_attr_e:1,2d', - 'esc_attr_x:1,2c,3d', - '_ex:1,2c,3d', - '_n:1,2,4d', - '_nx:1,2,4c,5d', - '_n_noop:1,2,3d', - '_nx_noop:1,2,3c,4d' - ] - }, - files: [ { - src: [ - '**/*.php', - '!node_modules/**', - '!build/**', - '!tests/**', - '!vendor/**', - '!*~' - ], - expand: true - } ] - } - }, - - usebanner: { - dist: { - options: { - banner: '<%= banner %>' - }, - files: { - src: [ - 'assets/js/app.min.js', - 'assets/css/style.css', - 'assets/css/style.min.css' - ] - } - } - }, - - jshint: { - options: { - jshintrc: '.jshintrc' - }, - all: [ - 'Gruntfile.js', - 'assets/js/skip-link-focus-fix.js', - 'assets/js/app.dev.js' - ] - }, - - uglify: { - options: {}, - dist: { - files: { - 'assets/js/app.min.js': [ - 'assets/js/skip-link-focus-fix.js', - 'assets/js/app.dev.js' - ] - } - } - }, - - less: { - dist: { - options: { - //cleancss: true - }, - files: { - 'assets/css/style.css': 'assets/less/style.less' - } - }, - - mincss: { - options: { - compress: true - }, - files: { - 'assets/css/style.min.css': 'assets/less/style.less' - } - } - }, - - watch: { - js: { - files: [ - '**/*.js', - '!**/*.min.js' - ], - tasks: [ - 'scripts' - ], - options: {} - }, - - less: { - - files: [ - '**/*.less' - ], - tasks: [ - 'styles' - ], - options: {} - } - }, - - bumpup: { - options: { - updateProps: { - pkg: 'package.json' - } - }, - file: 'package.json' - }, - - replace: { - plugin_main: { - src: [ 'pojo-accessibility.php' ], - overwrite: true, - replacements: [ - { - from: /Version: \d{1,1}\.\d{1,2}\.\d{1,2}/g, - to: 'Version: <%= pkg.version %>' - } - ] - }, - - readme: { - src: [ 'readme.txt' ], - overwrite: true, - replacements: [ - { - from: /Stable tag: \d{1,1}\.\d{1,2}\.\d{1,2}/g, - to: 'Stable tag: <%= pkg.version %>' - } - ] - } - }, - - shell: { - git_add_all : { - command: [ - 'git add --all', - 'git commit -m "Bump to <%= pkg.version %>"' - ].join( '&&' ) - } - }, - - release: { - options: { - bump: false, - npm: false, - commit: false, - tagName: 'v<%= version %>', - commitMessage: 'released v<%= version %>', - tagMessage: 'Tagged as v<%= version %>' - } - }, - - wp_readme_to_markdown: { - github: { - options: { - screenshot_url: 'assets/{screenshot}.png', - wordpressPluginSlug: 'pojo-accessibility', - travisUrlRepo: 'https://travis-ci.org/pojome/one-click-accessibility', - gruntDependencyStatusUrl: 'https://david-dm.org/pojome/pojo-accessibility' - }, - files: { - 'README.md': 'readme.txt' - } - } - }, - - copy: { - main: { - src: [ - '**', - '!node_modules/**', - '!build/**', - '!bin/**', - '!.git/**', - '!tests/**', - '!.travis.yml', - '!.jshintrc', - '!README.md', - '!phpunit.xml', - '!vendor/**', - '!Gruntfile.js', - '!package.json', - '!package-lock.json', - '!npm-debug.log', - '!composer.json', - '!composer.lock', - '!assets/less/**', - '!wp-assets/**', - '!.gitignore', - '!.gitmodules', - '!*~' - ], - expand: true, - dest: 'build/' - } - }, - - clean: { - //Clean up build folder - main: [ - 'build' - ] - }, - - wp_deploy: { - deploy:{ - options: { - plugin_slug: '<%= pkg.slug %>', - svn_user: 'KingYes', - build_dir: 'build/', - assets_dir: 'wp-assets/' - } - } - }, - - phpunit: { - classes: { - dir: '' - }, - options: { - bin: 'phpunit', - bootstrap: 'tests/bootstrap.php', - colors: true - } - } - - } ); - - // Default task(s). - grunt.registerTask( 'default', [ - 'checktextdomain', - 'scripts', - 'styles', - 'usebanner', - 'wp_readme_to_markdown', - //'phpunit' - ] ); - - grunt.registerTask( 'build', [ - 'default', - 'clean', - 'copy' - ] ); - - grunt.registerTask( 'scripts', [ - 'jshint', - 'uglify' - ] ); - - grunt.registerTask( 'styles', [ - 'less' - ] ); - - grunt.registerTask( 'publish', [ - 'default', - 'bumpup', - 'replace', - 'shell:git_add_all', - 'release' - ] ); -}; diff --git a/composer.json b/composer.json index d91f30d..97f699d 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,44 @@ "email": "yakir@pojo.me" } ], - "require": { - "phpunit/phpunit": "4.8.8" - } + "require-dev": { + "johnpbloch/wordpress-core": "^6.0", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.1", + "squizlabs/php_codesniffer": "^3.6", + "wp-coding-standards/wpcs": "2.3", + "php-stubs/wordpress-stubs": "^6.0", + "elementor/eunit": "^0.0.10", + "phpcompatibility/phpcompatibility-wp": "^2.1", + "phpunit/php-code-coverage": "^9.2", + "wildwolf/wordpress-test-library-stubs": "^6.0", + "wp-phpunit/wp-phpunit": "^6.0", + "yoast/phpunit-polyfills": "^2.0" + }, + "scripts": { + "lint": "phpcs --standard=./ruleset.xml ./**/*.php" + }, + "require": { + "firebase/php-jwt": "^6.10" + }, + "config": { + "allow-plugins": { + "composer/installers": true, + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "extra": { + "imposter": { + "namespace": "...", + "excludes": [ "firebase/php-jwt"] + }, + "installer-paths": { + "vendor/{$vendor}/{$name}/": [ "firebase/php-jwt"] + } + }, + "repositories":[ + { + "type": "vcs", + "url": "git@github.com:elementor/eunit.git" + } + ] } diff --git a/assets/css/style.css b/modules/legacy/assets/css/style.css similarity index 100% rename from assets/css/style.css rename to modules/legacy/assets/css/style.css diff --git a/assets/css/style.min.css b/modules/legacy/assets/css/style.min.css similarity index 100% rename from assets/css/style.min.css rename to modules/legacy/assets/css/style.min.css diff --git a/assets/js/app.dev.js b/modules/legacy/assets/js/app.dev.js similarity index 100% rename from assets/js/app.dev.js rename to modules/legacy/assets/js/app.dev.js diff --git a/assets/js/app.min.js b/modules/legacy/assets/js/app.min.js similarity index 100% rename from assets/js/app.min.js rename to modules/legacy/assets/js/app.min.js diff --git a/assets/js/skip-link-focus-fix.js b/modules/legacy/assets/js/skip-link-focus-fix.js similarity index 100% rename from assets/js/skip-link-focus-fix.js rename to modules/legacy/assets/js/skip-link-focus-fix.js diff --git a/assets/less/_background.less b/modules/legacy/assets/less/_background.less similarity index 100% rename from assets/less/_background.less rename to modules/legacy/assets/less/_background.less diff --git a/assets/less/_high-contrast.less b/modules/legacy/assets/less/_high-contrast.less similarity index 100% rename from assets/less/_high-contrast.less rename to modules/legacy/assets/less/_high-contrast.less diff --git a/assets/less/_mixing.less b/modules/legacy/assets/less/_mixing.less similarity index 100% rename from assets/less/_mixing.less rename to modules/legacy/assets/less/_mixing.less diff --git a/assets/less/_toolbar.less b/modules/legacy/assets/less/_toolbar.less similarity index 100% rename from assets/less/_toolbar.less rename to modules/legacy/assets/less/_toolbar.less diff --git a/assets/less/_underline.less b/modules/legacy/assets/less/_underline.less similarity index 100% rename from assets/less/_underline.less rename to modules/legacy/assets/less/_underline.less diff --git a/assets/less/_visibility.less b/modules/legacy/assets/less/_visibility.less similarity index 100% rename from assets/less/_visibility.less rename to modules/legacy/assets/less/_visibility.less diff --git a/assets/less/style.less b/modules/legacy/assets/less/style.less similarity index 100% rename from assets/less/style.less rename to modules/legacy/assets/less/style.less diff --git a/modules/legacy/components/customizer.php b/modules/legacy/components/customizer.php index 03f76a6..464c831 100644 --- a/modules/legacy/components/customizer.php +++ b/modules/legacy/components/customizer.php @@ -168,13 +168,10 @@ public function get_customizer_fields() { public function customize_a11y( $wp_customize ) { $fields = $this->get_customizer_fields(); - $section_description = '

' . __( 'Use the control below to customize the appearance and layout of the Accessibility Toolbar', - 'pojo-accessibility' ) . '

' . - sprintf( __( 'Additional Toolbar settings can be configured at the %s page.', - 'pojo-accessibility' ), - '' . __( 'Accessibility Toolbar', - 'pojo-accessibility' ) . '' - ) . '

'; + $section_description = '

' . __( 'Use the control below to customize the appearance and layout of the Accessibility Toolbar', 'pojo-accessibility' ) . '

' . + sprintf( __( 'Additional Toolbar settings can be configured at the %s page.', 'pojo-accessibility' ), + '' . __( 'Accessibility Toolbar', 'pojo-accessibility' ) . '' + ) . '

'; $wp_customize->add_section( 'accessibility', [ 'title' => __( 'Accessibility', 'pojo-accessibility' ), diff --git a/modules/legacy/components/frontend.php b/modules/legacy/components/frontend.php index 456f9e3..1e957e1 100644 --- a/modules/legacy/components/frontend.php +++ b/modules/legacy/components/frontend.php @@ -39,7 +39,7 @@ public function get_toolbar_button_title( $button_type ) { public function enqueue_scripts() { wp_register_script( 'pojo-a11y', - EA11Y_ASSETS_URL . 'js/app.min.js', + EA11Y_URL . 'modules/legacy/js/app.min.js', [ 'jquery' ], '1.0.0', true @@ -47,7 +47,7 @@ public function enqueue_scripts() { wp_register_style( 'pojo-a11y', - EA11Y_ASSETS_URL . 'css/style.min.css', + EA11Y_URL . 'modules/legacy/assets/css/style.min.css', [], '1.0.0' ); diff --git a/package.json b/package.json index 8caef29..2ed5569 100644 --- a/package.json +++ b/package.json @@ -4,23 +4,41 @@ "homepage": "http://pojo.me/", "description": "", "version": "2.1.0", + "scripts": { + "build": "wp-scripts build", + "start": "wp-scripts start", + "format": "wp-scripts format", + "lint:js": "wp-scripts lint-js", + "prepare": "husky install", + "local:start": "wp-env start", + "local:stop": "wp-env stop", + "local:composer:dev": "wp-env run cli --env-cwd=wp-content/plugins/one-click-accessibility composer install", + "local:composer:build": "wp-env run cli --env-cwd=wp-content/plugins/one-click-accessibility composer install --no-dev", + "local:wp-cli": "wp-env run cli --env-cwd=wp-content/plugins/one-click-accessibility wp", + "local:phpunit": "wp-env run tests-cli --env-cwd=wp-content/plugins/one-click-accessibility ./vendor/bin/phpunit", + "local:phpunit:cov": "wp-env start --xdebug=coverage && wp-env run tests-cli --env-cwd=wp-content/plugins/one-click-accessibility ./vendor/bin/phpunit --coverage-html /var/www/html/wp-content/plugins/one-click-accessibility/coverage" + }, "devDependencies": { - "grunt": "~1.0.2", - "grunt-checktextdomain": "~1.0.1", - "grunt-phpunit": "~0.3.6", - "grunt-wp-readme-to-markdown-with-extra": "~2.2.0", - "grunt-contrib-jshint": "~1.1.0", - "grunt-contrib-watch": "~1.0.1", - "grunt-contrib-uglify": "~3.3.0", - "grunt-contrib-less": "~1.4.1", - "grunt-banner": "~0.6.0", - "grunt-bumpup": "~0.6.3", - "grunt-shell": "~2.1.0", - "grunt-text-replace": "~0.4.0", - "grunt-release": "~0.14.0", - "grunt-contrib-copy": "~1.0.0", - "grunt-contrib-clean": "~1.1.0", - "grunt-wp-deploy": "~1.2.1", - "matchdep": "~2.0.0" + "@eslint/js": "^9.13.0", + "@wordpress/dom-ready": "^4.10.0", + "@wordpress/env": "^10.10.0", + "@wordpress/eslint-plugin": "^21.3.0", + "@wordpress/scripts": "^30.3.0", + "eslint": "^8.57.1", + "eslint-plugin-babel": "^5.3.1", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jsx-a11y": "^6.10.1", + "eslint-plugin-react": "^7.37.1" + }, + "dependencies": { + "@elementor/icons": "^1.17.0", + "@elementor/ui": "^1.21.2", + "@wordpress/api-fetch": "^7.10.0", + "@wordpress/core-data": "^7.10.0", + "@wordpress/data": "^10.10.0", + "@wordpress/date": "^5.10.0", + "@wordpress/element": "^6.10.0", + "@wordpress/i18n": "^5.10.0", + "@wordpress/url": "^4.10.0" } -} \ No newline at end of file +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..24f5e89 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,43 @@ + + + + + + + + + + + + ./tests/phpunit/plugin + + + + + + ./ + + .github + assets + bin + build + docs + node_modules + tests + includes/libraries + libraries + libraries/action-scheduler + vendor + coverage + + + + diff --git a/ruleset.xml b/ruleset.xml new file mode 100644 index 0000000..2f53acf --- /dev/null +++ b/ruleset.xml @@ -0,0 +1,54 @@ + + + Elementor Coding Standard + + + + + + vendor/ + tmp/ + build/ + node_modules/ + includes/libraries/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/bootstrap.php b/tests/bootstrap.php old mode 100755 new mode 100644 index 2b9ce1b..b239294 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,32 +1,40 @@ array( PLUGIN_PATH ), -); +if ( ! file_exists( $_tests_dir . '/includes/functions.php' ) ) { + // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- CLI + throw new Exception( "Could not find $_tests_dir/includes/functions.php, have you run bin/install-wp-tests.sh?" ); // NOSONAR +} +// Give access to tests_add_filter() function. +/** @psalm-suppress UnresolvableInclude */ require_once $_tests_dir . '/includes/functions.php'; -tests_add_filter( - 'muplugins_loaded', - function() { - // Manually load plugin - require dirname( __DIR__ ) . '/' . PLUGIN_FILE; - } -); +function _manually_load_plugin(): void { + require dirname( __DIR__ ) . '/pojo-accessibility.php'; +} + +tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' ); + +// Start up the WP testing environment. +/** @psalm-suppress UnresolvableInclude */ +require $_tests_dir . '/includes/bootstrap.php'; -// Removes all sql tables on shutdown -// Do this action last -tests_add_filter( 'shutdown', 'drop_tables', 999999 ); +// @phpcs:disable -require $_tests_dir . '/includes/bootstrap.php'; \ No newline at end of file +//module tests +include_once dirname( __DIR__ ) . '/tests/phpunit/helpers/module-test-base.php'; +//rest tests +include_once dirname( __DIR__ ) . '/tests/phpunit/helpers/route-test.php'; +//test base with factories +include_once dirname( __DIR__ ) . '/tests/phpunit/helpers/test-base.php'; +// functions +include_once dirname( __DIR__ ) . '/tests/phpunit/helpers/functions.php'; +// phpcs:enable diff --git a/tests/phpunit/helpers/functions.php b/tests/phpunit/helpers/functions.php new file mode 100644 index 0000000..979173d --- /dev/null +++ b/tests/phpunit/helpers/functions.php @@ -0,0 +1,8 @@ +module; + + + if ( ! method_exists( $module, 'routes_list' ) || empty( $module::routes_list() ) ) { + $this->assertTrue( true, 'No routes to test' ); + return; + } + $routes = $module::routes_list(); + + $this->assertTrue( is_array( $routes ), + 'Test that the routes are returned as an array' + ); + + $this->assertCount( count( $this->routes ), $routes, + 'Test that the number of routes are as expected' + ); + + foreach ( $this->routes as $route ) { + $this->assertContains( $route, $routes, + 'Test that Route "' . $route . '" is registered' + ); + } + } + + public function test_components() { + /** + * @var Module_Base $module + */ + $module = $this->module; + if ( method_exists( $module, 'component_list' ) === false || empty( $module::component_list() ) ) { + $this->assertTrue( true, 'No components to test' ); + return; + } + $components = $module::component_list(); + + $this->assertTrue( is_array( $components ), + 'Test that the components are returned as an array' + ); + + $this->assertCount( count( $this->components ), $components, + 'Test that the number of components are as expected' + ); + + foreach ( $this->components as $component ) { + $this->assertContains( $component, $components, + 'Test that component "' . $component . '" is registered' + ); + } + } + + /** + * test_constructor + */ + public function test_constructor() { + $this->test_components(); + $this->test_routes(); + } +} diff --git a/tests/phpunit/helpers/route-test.php b/tests/phpunit/helpers/route-test.php new file mode 100644 index 0000000..446ce0f --- /dev/null +++ b/tests/phpunit/helpers/route-test.php @@ -0,0 +1,31 @@ +route with an actual instance of the route class we are testing + $this->route = $this->get_plugin_module( $this->module_name )->routes[ $this->class_name ]; + + /** + * this includes automated unit tests for: + * - Test that the route is registered + * - Test route name is correct + * - Test allowed methods are correct + * - Test Endpoint is configured correctly + */ + } +} diff --git a/tests/phpunit/helpers/test-base.php b/tests/phpunit/helpers/test-base.php new file mode 100644 index 0000000..fefffe4 --- /dev/null +++ b/tests/phpunit/helpers/test-base.php @@ -0,0 +1,40 @@ +subscriber = $this->factory->user->create( [ 'role' => 'subscriber' ] ); + $this->editor = $this->factory->user->create( [ 'role' => 'editor' ] ); + $this->administrator = $this->factory->user->create( [ 'role' => 'administrator' ] ); + + // Factories + $this->factory->log = new \WP_UnitTest_Factory_For_Log( $this->factory ); + } + + + /** + * tearDown + */ + public function tearDown() : void { + parent::tearDown(); + } + + public function get_plugin() { + return \EA11y\Plugin::instance(); + } + + public function get_plugin_module( $name ) { + /** + * @var Module_Base $module + */ + $module = '\EA11y\\Modules\\' . $name . '\Module'; + return $module::instance(); + } +} diff --git a/tests/phpunit/plugin/modules/legacy/module-test.php b/tests/phpunit/plugin/modules/legacy/module-test.php new file mode 100644 index 0000000..4f5af7a --- /dev/null +++ b/tests/phpunit/plugin/modules/legacy/module-test.php @@ -0,0 +1,20 @@ +assertTrue( is_plugin_active( PLUGIN_PATH ) ); - } - - public function test_getInstance() { - $this->assertInstanceOf( 'Pojo_Accessibility', Pojo_Accessibility::instance() ); - } - -} \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..7216c1c --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,20 @@ +const path = require( 'path' ); +const defaultConfig = require( '@wordpress/scripts/config/webpack.config' ); + +// add your entry points here +const entryPoints = { + // admin: path.resolve( + // process.cwd(), + // 'assets/dev/js', + // 'admin.js', + // ), +}; + +module.exports = { + ...defaultConfig, + entry: entryPoints, + output: { + ...defaultConfig.output, + path: path.resolve( process.cwd(), 'assets/build' ), + }, +}; From c003cf74b1618cf3b3a7c6233981e815ca727363 Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Fri, 25 Oct 2024 18:20:19 +0530 Subject: [PATCH 03/65] update: add src for admin settings --- webpack.config.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/webpack.config.js b/webpack.config.js index 7216c1c..3c31c7d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -3,11 +3,11 @@ const defaultConfig = require( '@wordpress/scripts/config/webpack.config' ); // add your entry points here const entryPoints = { - // admin: path.resolve( - // process.cwd(), - // 'assets/dev/js', - // 'admin.js', - // ), + admin: path.resolve( + process.cwd(), + 'assets/src/settings/js', + 'admin.js', + ), }; module.exports = { From 21987b44e1d7b0b55b4d5cd6530e05b7e4c94b81 Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Fri, 25 Oct 2024 18:20:58 +0530 Subject: [PATCH 04/65] update: incorrect constant names --- classes/utils/assets.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/classes/utils/assets.php b/classes/utils/assets.php index 37aeec7..d4ed171 100644 --- a/classes/utils/assets.php +++ b/classes/utils/assets.php @@ -56,7 +56,7 @@ public static function enqueue_styles( string $handle, string $style_name, array * @return string */ private static function get_assets_version( string $version = '' ) : string { - return empty( $version ) ? \SITE_MAILER_VERSION : $version; + return empty( $version ) ? \EA11Y_VERSION : $version; } /** @@ -91,7 +91,7 @@ private static function get_asset_version_and_suffix( string $version = '' ) : a * @return string */ private static function get_assets_path( string $asset_name, string $asset_type, string $suffix = '' ) : string { - return \SITE_MAILER_ASSETS_URL . '/build/' . $asset_name . $suffix . '.' . $asset_type; + return \EA11Y_ASSETS_URL . '/build/' . $asset_name . $suffix . '.' . $asset_type; //TODO: Check if we need this //return \SITE_MAILER_ASSETS_URL . $asset_type . '/' . ( '' === $suffix ? 'dev/' : '' ) . $asset_name . $suffix . '.' . $asset_type; } @@ -103,8 +103,8 @@ private static function get_assets_path( string $asset_name, string $asset_type, * @param bool $with_css */ public static function enqueue_app_assets( string $handle = '', bool $with_css = true ) : void { - $dir = \SITE_MAILER_ASSETS_PATH . 'build/'; - $url = \SITE_MAILER_ASSETS_URL . 'build/'; + $dir = \EA11Y_ASSETS_PATH . 'build/'; + $url = \EA11Y_ASSETS_URL . 'build/'; $script_asset_path = $dir . $handle . '.asset.php'; if ( ! file_exists( $script_asset_path ) ) { @@ -131,7 +131,7 @@ public static function enqueue_app_assets( string $handle = '', bool $with_css = } // enqueue css $css_file_name = 'style-' . $handle . '.css'; - $css_version = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? filemtime( $dir . $css_file_name ) : \SITE_MAILER_VERSION; + $css_version = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? filemtime( $dir . $css_file_name ) : \EA11Y_VERSION; wp_enqueue_style( $handle, $url . $css_file_name, From 6333248d45303cdfc9b071202159da43f08f7138 Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Fri, 25 Oct 2024 18:21:11 +0530 Subject: [PATCH 05/65] update: namespace --- tests/phpunit/helpers/test-base.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/helpers/test-base.php b/tests/phpunit/helpers/test-base.php index fefffe4..a9d165c 100644 --- a/tests/phpunit/helpers/test-base.php +++ b/tests/phpunit/helpers/test-base.php @@ -1,5 +1,5 @@ Date: Fri, 25 Oct 2024 18:23:52 +0530 Subject: [PATCH 06/65] add: accessibility settings --- assets/src/settings/css/style.css | 39 ++++++++ assets/src/settings/js/admin.js | 26 +++++ assets/src/settings/js/app.js | 14 +++ .../js/components/admin-top-bar/index.js | 35 +++++++ assets/src/settings/js/constants/index.js | 1 + .../src/settings/js/icons/elementor-logo.js | 21 ++++ includes/manager.php | 1 + modules/settings/module.php | 97 +++++++++++++++++++ 8 files changed, 234 insertions(+) create mode 100644 assets/src/settings/css/style.css create mode 100644 assets/src/settings/js/admin.js create mode 100644 assets/src/settings/js/app.js create mode 100644 assets/src/settings/js/components/admin-top-bar/index.js create mode 100644 assets/src/settings/js/constants/index.js create mode 100644 assets/src/settings/js/icons/elementor-logo.js create mode 100644 modules/settings/module.php diff --git a/assets/src/settings/css/style.css b/assets/src/settings/css/style.css new file mode 100644 index 0000000..ab54ef7 --- /dev/null +++ b/assets/src/settings/css/style.css @@ -0,0 +1,39 @@ +@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap'); + +body { + background: #fff; +} +.wrap { + margin-top: 0; +} + +html:not([dir='rtl']) #ea11y-app { + margin-left: -20px; + background: white; +} + +html[dir='rtl'] #ea11y-app { + margin-right: -20px; + background: white; +} + +html:not([dir='rtl']) #ea11y-app-top-bar { + margin-left: -20px; + margin-bottom: 20px; +} + +html[dir='rtl'] #ea11y-app-top-bar { + margin-right: -20px; + margin-bottom: 20px; +} + +#ea11y-app * { + box-sizing: border-box; +} + +.ea11y-textfield .MuiInputBase-input { + height: 40px; + padding: 8px 12px; + box-shadow: none; + border-color: rgba(12, 13, 14, 0.23); +} \ No newline at end of file diff --git a/assets/src/settings/js/admin.js b/assets/src/settings/js/admin.js new file mode 100644 index 0000000..4e51e3a --- /dev/null +++ b/assets/src/settings/js/admin.js @@ -0,0 +1,26 @@ +import { ThemeProvider } from '@elementor/ui/styles'; +import { StrictMode, Fragment, createRoot } from '@wordpress/element'; +import App from './app'; +import AdminTopBar from './components/admin-top-bar'; + +const rootNode = document.getElementById( 'ea11y-app' ); +const topBarNode = document.getElementById( 'ea11y-app-top-bar' ); + +// Can't use the settings hook in the global scope so accessing directly +const isDevelopment = window?.ea11ySettingsData?.isDevelopment; +const AppWrapper = Boolean( isDevelopment ) ? StrictMode : Fragment; + +const root = createRoot( rootNode ); +const topBar = createRoot( topBarNode ); + +topBar.render( + + + , +); + +root.render( + + + , +); diff --git a/assets/src/settings/js/app.js b/assets/src/settings/js/app.js new file mode 100644 index 0000000..35d5bfb --- /dev/null +++ b/assets/src/settings/js/app.js @@ -0,0 +1,14 @@ +import '../css/style.css'; +import DirectionProvider from '@elementor/ui/DirectionProvider'; +import { ThemeProvider } from '@elementor/ui/styles'; + +const App = () => { + return ( + + + + + ); +}; + +export default App; diff --git a/assets/src/settings/js/components/admin-top-bar/index.js b/assets/src/settings/js/components/admin-top-bar/index.js new file mode 100644 index 0000000..b789079 --- /dev/null +++ b/assets/src/settings/js/components/admin-top-bar/index.js @@ -0,0 +1,35 @@ +import HelpIcon from '@elementor/icons/HelpIcon'; +import AppBar from '@elementor/ui/AppBar'; +import Grid from '@elementor/ui/Grid'; +import Link from '@elementor/ui/Link'; +import Toolbar from '@elementor/ui/Toolbar'; +import Typography from '@elementor/ui/Typography'; +import { __ } from '@wordpress/i18n'; +import { HELP_LINK } from '../../constants'; +import ElementorLogo from '../../icons/elementor-logo'; + +const AdminTopBar = () => { + return ( + + + + + + { __( 'Accessibility', 'pojo-accessibility' ) } + + + + + + + + + ); +}; + +export default AdminTopBar; diff --git a/assets/src/settings/js/constants/index.js b/assets/src/settings/js/constants/index.js new file mode 100644 index 0000000..edeb853 --- /dev/null +++ b/assets/src/settings/js/constants/index.js @@ -0,0 +1 @@ +export const HELP_LINK = 'https://go.elementor.com/'; diff --git a/assets/src/settings/js/icons/elementor-logo.js b/assets/src/settings/js/icons/elementor-logo.js new file mode 100644 index 0000000..cc62ff4 --- /dev/null +++ b/assets/src/settings/js/icons/elementor-logo.js @@ -0,0 +1,21 @@ +import SvgIcon from '@elementor/ui/SvgIcon'; + +const ElementorLogo = ( { size } ) => { + return ( + + + + + + + + + + + + ); +}; + +export default ElementorLogo; diff --git a/includes/manager.php b/includes/manager.php index a8b29d6..b851757 100644 --- a/includes/manager.php +++ b/includes/manager.php @@ -17,6 +17,7 @@ final class Manager { public static function get_module_list(): array { return [ 'Legacy', + 'Settings' ]; } diff --git a/modules/settings/module.php b/modules/settings/module.php new file mode 100644 index 0000000..2cd3555 --- /dev/null +++ b/modules/settings/module.php @@ -0,0 +1,97 @@ +id ) { + return; + } + + ?> +
+ + +
+

+
+ +
+ wp_create_nonce( 'wp_rest' ) ] + ); + } + + /** + * Module constructor. + */ + public function __construct() { + $this->register_components(); + add_action( 'admin_menu', [ $this, 'register_page' ] ); + add_action( 'in_admin_header', [ $this, 'render_top_bar' ] ); + add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] ); + } +} From 9e3bc10631e7536160adcb50aa1149e5d467dfd9 Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Mon, 28 Oct 2024 18:31:38 +0530 Subject: [PATCH 07/65] update: webpack to output files inside a folder --- webpack.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/webpack.config.js b/webpack.config.js index 3c31c7d..367ae35 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -15,6 +15,7 @@ module.exports = { entry: entryPoints, output: { ...defaultConfig.output, + filename: '[name]/[name].js', path: path.resolve( process.cwd(), 'assets/build' ), }, }; From 9757cda111ea4c86dd492076c711a747cd363f96 Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Tue, 29 Oct 2024 12:07:56 +0530 Subject: [PATCH 08/65] update: build output folders --- classes/utils/assets.php | 4 ++-- package.json | 6 ++++-- webpack.config.js | 21 --------------------- 3 files changed, 6 insertions(+), 25 deletions(-) delete mode 100644 webpack.config.js diff --git a/classes/utils/assets.php b/classes/utils/assets.php index d4ed171..4064a48 100644 --- a/classes/utils/assets.php +++ b/classes/utils/assets.php @@ -103,8 +103,8 @@ private static function get_assets_path( string $asset_name, string $asset_type, * @param bool $with_css */ public static function enqueue_app_assets( string $handle = '', bool $with_css = true ) : void { - $dir = \EA11Y_ASSETS_PATH . 'build/'; - $url = \EA11Y_ASSETS_URL . 'build/'; + $dir = \EA11Y_ASSETS_PATH . 'build/' . $handle . '/'; + $url = \EA11Y_ASSETS_URL . 'build/' . $handle . '/'; $script_asset_path = $dir . $handle . '.asset.php'; if ( ! file_exists( $script_asset_path ) ) { diff --git a/package.json b/package.json index 2ed5569..5684ef7 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,10 @@ "description": "", "version": "2.1.0", "scripts": { - "build": "wp-scripts build", - "start": "wp-scripts start", + "build": "npm run build:admin", + "build:admin": "wp-scripts build assets/src/settings/js/admin.js --output-path=assets/build/admin", + "start": "npm run start:admin", + "start:admin": "wp-scripts start assets/src/settings/js/admin.js --output-path=assets/build/admin", "format": "wp-scripts format", "lint:js": "wp-scripts lint-js", "prepare": "husky install", diff --git a/webpack.config.js b/webpack.config.js deleted file mode 100644 index 367ae35..0000000 --- a/webpack.config.js +++ /dev/null @@ -1,21 +0,0 @@ -const path = require( 'path' ); -const defaultConfig = require( '@wordpress/scripts/config/webpack.config' ); - -// add your entry points here -const entryPoints = { - admin: path.resolve( - process.cwd(), - 'assets/src/settings/js', - 'admin.js', - ), -}; - -module.exports = { - ...defaultConfig, - entry: entryPoints, - output: { - ...defaultConfig.output, - filename: '[name]/[name].js', - path: path.resolve( process.cwd(), 'assets/build' ), - }, -}; From b0dafef8177408d79642ea9a1a1a4bceded8b7a2 Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Tue, 29 Oct 2024 12:13:10 +0530 Subject: [PATCH 09/65] update: removed commented code --- classes/utils/assets.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/classes/utils/assets.php b/classes/utils/assets.php index 4064a48..e2c7978 100644 --- a/classes/utils/assets.php +++ b/classes/utils/assets.php @@ -92,8 +92,6 @@ private static function get_asset_version_and_suffix( string $version = '' ) : a */ private static function get_assets_path( string $asset_name, string $asset_type, string $suffix = '' ) : string { return \EA11Y_ASSETS_URL . '/build/' . $asset_name . $suffix . '.' . $asset_type; - //TODO: Check if we need this - //return \SITE_MAILER_ASSETS_URL . $asset_type . '/' . ( '' === $suffix ? 'dev/' : '' ) . $asset_name . $suffix . '.' . $asset_type; } /** From 2ceabd381231a6402c5b003cb7cd448f5785a3db Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Tue, 29 Oct 2024 14:53:03 +0530 Subject: [PATCH 10/65] update: npm scripts --- package.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 5684ef7..2ed5569 100644 --- a/package.json +++ b/package.json @@ -5,10 +5,8 @@ "description": "", "version": "2.1.0", "scripts": { - "build": "npm run build:admin", - "build:admin": "wp-scripts build assets/src/settings/js/admin.js --output-path=assets/build/admin", - "start": "npm run start:admin", - "start:admin": "wp-scripts start assets/src/settings/js/admin.js --output-path=assets/build/admin", + "build": "wp-scripts build", + "start": "wp-scripts start", "format": "wp-scripts format", "lint:js": "wp-scripts lint-js", "prepare": "husky install", From 1a8756044e19ad09cd67dcd99a4924079854a9bf Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Tue, 29 Oct 2024 14:53:13 +0530 Subject: [PATCH 11/65] add: webpack config --- webpack.config.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 webpack.config.js diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..b40d4aa --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,20 @@ +const path = require( 'path' ); +const defaultConfig = require( '@wordpress/scripts/config/webpack.config' ); + +// add your entry points here +const entryPoints = { + admin: path.resolve( + process.cwd(), + 'modules/settings/assets/js', + 'admin.js', + ), +}; + +module.exports = { + ...defaultConfig, + entry: entryPoints, + output: { + ...defaultConfig.output, + path: path.resolve( process.cwd(), 'assets/build' ), + }, +}; From d0a1b8fd105ce24b6b88ba2bb34980486995b8d0 Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Tue, 29 Oct 2024 14:54:22 +0530 Subject: [PATCH 12/65] add: hooks --- .../assets/js/api/exceptions/APIError.js | 9 ++ modules/settings/assets/js/api/index.js | 132 ++++++++++++++++++ modules/settings/assets/js/hooks/index.js | 0 modules/settings/assets/js/hooks/use-auth.js | 33 +++++ modules/settings/assets/js/hooks/use-modal.js | 22 +++ 5 files changed, 196 insertions(+) create mode 100644 modules/settings/assets/js/api/exceptions/APIError.js create mode 100644 modules/settings/assets/js/api/index.js create mode 100644 modules/settings/assets/js/hooks/index.js create mode 100644 modules/settings/assets/js/hooks/use-auth.js create mode 100644 modules/settings/assets/js/hooks/use-modal.js diff --git a/modules/settings/assets/js/api/exceptions/APIError.js b/modules/settings/assets/js/api/exceptions/APIError.js new file mode 100644 index 0000000..f13a1f1 --- /dev/null +++ b/modules/settings/assets/js/api/exceptions/APIError.js @@ -0,0 +1,9 @@ +class APIError extends Error { + constructor( message ) { + super( message ); + + this.name = 'APIError'; + } +} + +export default APIError; diff --git a/modules/settings/assets/js/api/index.js b/modules/settings/assets/js/api/index.js new file mode 100644 index 0000000..f66dd2c --- /dev/null +++ b/modules/settings/assets/js/api/index.js @@ -0,0 +1,132 @@ +import apiFetch from '@wordpress/api-fetch'; +import { addQueryArgs } from '@wordpress/url'; +import APIError from './exceptions/APIError'; + +const wpV2Prefix = '/wp/v2'; +const v1Prefix = '/site-mailer/v1'; + +class API { + static async request( { path, data, method = 'POST' } ) { + try { + if ( 'GET' === method && ! path.startsWith( wpV2Prefix ) ) { + path = addQueryArgs( path, { sb_time: new Date().getTime() } ); + } + + const response = await apiFetch( { + path, + method, + data, + } ); + + if ( path.startsWith( wpV2Prefix ) ) { + return response; + } + + if ( ! response.success ) { + throw new APIError( response.data.message ); + } + + return response.data; + } catch ( e ) { + if ( e instanceof APIError ) { + throw e; + } else { + throw new APIError( e.message ); + } + } + } + + static async initConnect( context = 'new' ) { + const data = { + wp_rest: window?.siteMailerSettingsData?.wpRestNonce, + }; + + if ( 'update' === context ) { + data.update_redirect_uri = true; + } + + return API.request( { + method: 'POST', + path: `${ v1Prefix }/connect/authorize`, + data, + } ); + } + + static async clearSession() { + return API.request( { + method: 'POST', + path: `${ v1Prefix }/connect/deactivate_and_disconnect`, + data: { + wp_rest: window?.siteMailerSettingsData?.wpRestNonce, + clear_session: true, + }, + } ); + } + + static async deactivateAndDisconnect() { + return API.request( { + method: 'POST', + path: `${ v1Prefix }/connect/deactivate_and_disconnect`, + data: { + wp_rest: window?.siteMailerSettingsData?.wpRestNonce, + }, + } ); + } + + static async deactivate() { + return API.request( { + method: 'POST', + path: `${ v1Prefix }/connect/deactivate`, + data: { + wp_rest: window?.siteMailerSettingsData?.wpRestNonce, + }, + } ); + } + + static async disconnect() { + return API.request( { + method: 'POST', + path: `${ v1Prefix }/connect/disconnect`, + data: { + wp_rest: window?.siteMailerSettingsData?.wpRestNonce, + }, + } ); + } + + static async reconnect() { + return API.request( { + method: 'POST', + path: `${ v1Prefix }/connect/reconnect`, + data: { + wp_rest: window?.siteMailerSettingsData?.wpRestNonce, + }, + } ); + } + + static async getSettings() { + return API.request( { + method: 'GET', + path: `${ wpV2Prefix }/settings`, + } ); + } + + static async updateSettings( data ) { + return API.request( { + method: 'PUT', + path: `${ wpV2Prefix }/settings`, + data, + } ); + } + + /** + * @return {Promise} {} + */ + static async getPluginSettings() { + return API.request( { + method: 'GET', + path: `${ v1Prefix }/settings/get-settings`, + } ); + } +} + +export default API; diff --git a/modules/settings/assets/js/hooks/index.js b/modules/settings/assets/js/hooks/index.js new file mode 100644 index 0000000..e69de29 diff --git a/modules/settings/assets/js/hooks/use-auth.js b/modules/settings/assets/js/hooks/use-auth.js new file mode 100644 index 0000000..22ae065 --- /dev/null +++ b/modules/settings/assets/js/hooks/use-auth.js @@ -0,0 +1,33 @@ +import API from '../api'; +import { UPGRADE_LINK } from '../constants'; +import { usePluginSettingsContext } from '../context/plugin-settings-context'; + +const useAuth = () => { + const { subscriptionId } = 123; + + const redirectToConnect = async () => { + const link = await getConnectLink(); + + window.open( link, '_self' ).focus(); + }; + + const getConnectLink = async () => { + return API.initConnect(); + }; + + const getUpgradeLink = () => { + const url = new URL( UPGRADE_LINK ); + + url.searchParams.append( 'subscription_id', subscriptionId ); + + return url.toString(); + }; + + return { + redirectToConnect, + getConnectLink, + getUpgradeLink, + }; +}; + +export default useAuth; diff --git a/modules/settings/assets/js/hooks/use-modal.js b/modules/settings/assets/js/hooks/use-modal.js new file mode 100644 index 0000000..6ae362b --- /dev/null +++ b/modules/settings/assets/js/hooks/use-modal.js @@ -0,0 +1,22 @@ +import { useState } from '@wordpress/element'; + +const useModal = ( defaultIsOpen = true ) => { + const [ isOpen, setIsOpen ] = useState( defaultIsOpen ); + + const open = () => { + setIsOpen( true ); + }; + + const close = () => { + setIsOpen( false ); + }; + + return { + isOpen, + setIsOpen, + open, + close, + }; +}; + +export default useModal; From 2566690defdf29218633ac7ba98dd04c42954590 Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Tue, 29 Oct 2024 14:55:05 +0530 Subject: [PATCH 13/65] update: move admin setting to the module folder --- assets/src/settings/js/constants/index.js | 1 - {assets/src/settings => modules/settings/assets}/css/style.css | 0 {assets/src/settings => modules/settings/assets}/js/admin.js | 0 {assets/src/settings => modules/settings/assets}/js/app.js | 2 ++ .../settings/assets}/js/components/admin-top-bar/index.js | 0 modules/settings/assets/js/constants/index.js | 2 ++ .../settings/assets}/js/icons/elementor-logo.js | 0 7 files changed, 4 insertions(+), 1 deletion(-) delete mode 100644 assets/src/settings/js/constants/index.js rename {assets/src/settings => modules/settings/assets}/css/style.css (100%) rename {assets/src/settings => modules/settings/assets}/js/admin.js (100%) rename {assets/src/settings => modules/settings/assets}/js/app.js (82%) rename {assets/src/settings => modules/settings/assets}/js/components/admin-top-bar/index.js (100%) create mode 100644 modules/settings/assets/js/constants/index.js rename {assets/src/settings => modules/settings/assets}/js/icons/elementor-logo.js (100%) diff --git a/assets/src/settings/js/constants/index.js b/assets/src/settings/js/constants/index.js deleted file mode 100644 index edeb853..0000000 --- a/assets/src/settings/js/constants/index.js +++ /dev/null @@ -1 +0,0 @@ -export const HELP_LINK = 'https://go.elementor.com/'; diff --git a/assets/src/settings/css/style.css b/modules/settings/assets/css/style.css similarity index 100% rename from assets/src/settings/css/style.css rename to modules/settings/assets/css/style.css diff --git a/assets/src/settings/js/admin.js b/modules/settings/assets/js/admin.js similarity index 100% rename from assets/src/settings/js/admin.js rename to modules/settings/assets/js/admin.js diff --git a/assets/src/settings/js/app.js b/modules/settings/assets/js/app.js similarity index 82% rename from assets/src/settings/js/app.js rename to modules/settings/assets/js/app.js index 35d5bfb..047a209 100644 --- a/assets/src/settings/js/app.js +++ b/modules/settings/assets/js/app.js @@ -1,11 +1,13 @@ import '../css/style.css'; import DirectionProvider from '@elementor/ui/DirectionProvider'; import { ThemeProvider } from '@elementor/ui/styles'; +import ConnectModal from './components/connect-modal'; const App = () => { return ( + ); diff --git a/assets/src/settings/js/components/admin-top-bar/index.js b/modules/settings/assets/js/components/admin-top-bar/index.js similarity index 100% rename from assets/src/settings/js/components/admin-top-bar/index.js rename to modules/settings/assets/js/components/admin-top-bar/index.js diff --git a/modules/settings/assets/js/constants/index.js b/modules/settings/assets/js/constants/index.js new file mode 100644 index 0000000..c4169b4 --- /dev/null +++ b/modules/settings/assets/js/constants/index.js @@ -0,0 +1,2 @@ +export const HELP_LINK = 'https://go.elementor.com/'; +export const UPGRADE_LINK = 'https://go.elementor.com/'; diff --git a/assets/src/settings/js/icons/elementor-logo.js b/modules/settings/assets/js/icons/elementor-logo.js similarity index 100% rename from assets/src/settings/js/icons/elementor-logo.js rename to modules/settings/assets/js/icons/elementor-logo.js From 96fec7c44c5067dad3fcc6bc93aabeb56f5d2a5f Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Tue, 29 Oct 2024 14:57:08 +0530 Subject: [PATCH 14/65] update: assets loading logic --- classes/utils/assets.php | 4 ++-- modules/settings/assets/js/app.js | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/classes/utils/assets.php b/classes/utils/assets.php index e2c7978..ef9107c 100644 --- a/classes/utils/assets.php +++ b/classes/utils/assets.php @@ -101,8 +101,8 @@ private static function get_assets_path( string $asset_name, string $asset_type, * @param bool $with_css */ public static function enqueue_app_assets( string $handle = '', bool $with_css = true ) : void { - $dir = \EA11Y_ASSETS_PATH . 'build/' . $handle . '/'; - $url = \EA11Y_ASSETS_URL . 'build/' . $handle . '/'; + $dir = \EA11Y_ASSETS_PATH . 'build/'; + $url = \EA11Y_ASSETS_URL . 'build/'; $script_asset_path = $dir . $handle . '.asset.php'; if ( ! file_exists( $script_asset_path ) ) { diff --git a/modules/settings/assets/js/app.js b/modules/settings/assets/js/app.js index 047a209..35d5bfb 100644 --- a/modules/settings/assets/js/app.js +++ b/modules/settings/assets/js/app.js @@ -1,13 +1,11 @@ import '../css/style.css'; import DirectionProvider from '@elementor/ui/DirectionProvider'; import { ThemeProvider } from '@elementor/ui/styles'; -import ConnectModal from './components/connect-modal'; const App = () => { return ( - ); From 32a2057c6d203029aa8c82ee9b89b07a5ef2956f Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Tue, 29 Oct 2024 16:11:32 +0530 Subject: [PATCH 15/65] update: add rule to move jsx props to multiline imporving readability --- .eslintrc | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc b/.eslintrc index bd2111c..0ad9518 100644 --- a/.eslintrc +++ b/.eslintrc @@ -26,6 +26,7 @@ "rules": { "no-console": "off", "react-hooks/exhaustive-deps": "off", + "react/jsx-max-props-per-line": [1, { "maximum": { "single": 2, "multi": 1 } }], "strict": [ "error", "global" ], "curly": "warn", "import/order": [ From dcb1245b02d8a89c5dd3cbeb21b533b6d51d8cda Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Tue, 29 Oct 2024 16:11:57 +0530 Subject: [PATCH 16/65] add: connect modal --- modules/settings/assets/js/app.js | 2 + .../connect-modal/connect-modal-icon.js | 68 +++++++++++++++++++ .../js/components/connect-modal/index.js | 57 ++++++++++++++++ .../settings/assets/js/components/index.js | 1 + 4 files changed, 128 insertions(+) create mode 100644 modules/settings/assets/js/components/connect-modal/connect-modal-icon.js create mode 100644 modules/settings/assets/js/components/connect-modal/index.js create mode 100644 modules/settings/assets/js/components/index.js diff --git a/modules/settings/assets/js/app.js b/modules/settings/assets/js/app.js index 35d5bfb..2ca43d1 100644 --- a/modules/settings/assets/js/app.js +++ b/modules/settings/assets/js/app.js @@ -1,11 +1,13 @@ import '../css/style.css'; import DirectionProvider from '@elementor/ui/DirectionProvider'; import { ThemeProvider } from '@elementor/ui/styles'; +import { ConnectModal } from './components'; const App = () => { return ( + ); diff --git a/modules/settings/assets/js/components/connect-modal/connect-modal-icon.js b/modules/settings/assets/js/components/connect-modal/connect-modal-icon.js new file mode 100644 index 0000000..98915a2 --- /dev/null +++ b/modules/settings/assets/js/components/connect-modal/connect-modal-icon.js @@ -0,0 +1,68 @@ +import SvgIcon from '@elementor/ui/SvgIcon'; + +const ConnectModalIcon = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default ConnectModalIcon; diff --git a/modules/settings/assets/js/components/connect-modal/index.js b/modules/settings/assets/js/components/connect-modal/index.js new file mode 100644 index 0000000..3d6019d --- /dev/null +++ b/modules/settings/assets/js/components/connect-modal/index.js @@ -0,0 +1,57 @@ +import Box from '@elementor/ui/Box'; +import Button from '@elementor/ui/Button'; +import Grid from '@elementor/ui/Grid'; +import Modal from '@elementor/ui/Modal'; +import Typography from '@elementor/ui/Typography'; +import { __ } from '@wordpress/i18n'; +import API from '../../api'; +import useModal from '../../hooks/use-modal'; + +function ConnectModal() { + const { isOpen } = useModal(); + + const redirectToConnect = async () => { + const link = await getConnectLink(); + window.open( link, '_self' ).focus(); + }; + + const getConnectLink = async () => { + return API.initConnect(); + }; + + return ( + + + + + { __( 'Connect plugin on your site!', 'site-mailer' ) } + + + + + ); +} + +export default ConnectModal; diff --git a/modules/settings/assets/js/components/index.js b/modules/settings/assets/js/components/index.js new file mode 100644 index 0000000..a07d5ce --- /dev/null +++ b/modules/settings/assets/js/components/index.js @@ -0,0 +1 @@ +export { default as ConnectModal } from './connect-modal'; From a1f7b68fdcdae58a75393239ef9202d74660ad6b Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Tue, 29 Oct 2024 17:31:54 +0530 Subject: [PATCH 17/65] update: hooks import for better readability --- modules/settings/assets/js/hooks/index.js | 2 ++ modules/settings/assets/js/hooks/use-auth.js | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/settings/assets/js/hooks/index.js b/modules/settings/assets/js/hooks/index.js index e69de29..2bbadea 100644 --- a/modules/settings/assets/js/hooks/index.js +++ b/modules/settings/assets/js/hooks/index.js @@ -0,0 +1,2 @@ +export { default as useAuth } from './use-auth'; +export { default as useModal } from './use-modal'; diff --git a/modules/settings/assets/js/hooks/use-auth.js b/modules/settings/assets/js/hooks/use-auth.js index 22ae065..1eb666d 100644 --- a/modules/settings/assets/js/hooks/use-auth.js +++ b/modules/settings/assets/js/hooks/use-auth.js @@ -1,13 +1,12 @@ import API from '../api'; import { UPGRADE_LINK } from '../constants'; -import { usePluginSettingsContext } from '../context/plugin-settings-context'; const useAuth = () => { const { subscriptionId } = 123; const redirectToConnect = async () => { const link = await getConnectLink(); - + window.open( link, '_self' ).focus(); }; From d6d1ab68a0d24ee746fd3f2de464221bc94699d5 Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Tue, 29 Oct 2024 17:34:16 +0530 Subject: [PATCH 18/65] update: replace functions with hooks --- .../assets/js/components/connect-modal/index.js | 13 ++----------- modules/settings/assets/js/hooks/use-auth.js | 2 +- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/modules/settings/assets/js/components/connect-modal/index.js b/modules/settings/assets/js/components/connect-modal/index.js index 3d6019d..d0fe566 100644 --- a/modules/settings/assets/js/components/connect-modal/index.js +++ b/modules/settings/assets/js/components/connect-modal/index.js @@ -4,20 +4,11 @@ import Grid from '@elementor/ui/Grid'; import Modal from '@elementor/ui/Modal'; import Typography from '@elementor/ui/Typography'; import { __ } from '@wordpress/i18n'; -import API from '../../api'; -import useModal from '../../hooks/use-modal'; +import { useAuth, useModal } from '../../hooks'; function ConnectModal() { const { isOpen } = useModal(); - - const redirectToConnect = async () => { - const link = await getConnectLink(); - window.open( link, '_self' ).focus(); - }; - - const getConnectLink = async () => { - return API.initConnect(); - }; + const { redirectToConnect } = useAuth(); return ( diff --git a/modules/settings/assets/js/hooks/use-auth.js b/modules/settings/assets/js/hooks/use-auth.js index 1eb666d..4ce5914 100644 --- a/modules/settings/assets/js/hooks/use-auth.js +++ b/modules/settings/assets/js/hooks/use-auth.js @@ -6,7 +6,7 @@ const useAuth = () => { const redirectToConnect = async () => { const link = await getConnectLink(); - + window.open( link, '_self' ).focus(); }; From 8810af3f5e991f0725d9beda56d4cd84444e90c5 Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Fri, 1 Nov 2024 12:44:04 +0530 Subject: [PATCH 19/65] add: connect module --- includes/manager.php | 3 +- modules/connect/classes/basic-enum.php | 35 ++ modules/connect/classes/config.php | 22 ++ modules/connect/classes/data.php | 357 ++++++++++++++++++ .../classes/exceptions/service-exception.php | 13 + modules/connect/classes/grant-types.php | 16 + modules/connect/classes/route-base.php | 43 +++ modules/connect/classes/service.php | 320 ++++++++++++++++ modules/connect/classes/utils.php | 125 ++++++ modules/connect/components/handler.php | 91 +++++ modules/connect/module.php | 84 +++++ modules/connect/rest/authorize.php | 78 ++++ .../rest/deactivate-and-disconnect.php | 50 +++ modules/connect/rest/deactivate.php | 42 +++ modules/connect/rest/disconnect.php | 42 +++ modules/connect/rest/reconnect.php | 41 ++ modules/connect/rest/switch-domain.php | 54 +++ 17 files changed, 1415 insertions(+), 1 deletion(-) create mode 100644 modules/connect/classes/basic-enum.php create mode 100644 modules/connect/classes/config.php create mode 100644 modules/connect/classes/data.php create mode 100644 modules/connect/classes/exceptions/service-exception.php create mode 100644 modules/connect/classes/grant-types.php create mode 100644 modules/connect/classes/route-base.php create mode 100644 modules/connect/classes/service.php create mode 100644 modules/connect/classes/utils.php create mode 100644 modules/connect/components/handler.php create mode 100644 modules/connect/module.php create mode 100644 modules/connect/rest/authorize.php create mode 100644 modules/connect/rest/deactivate-and-disconnect.php create mode 100644 modules/connect/rest/deactivate.php create mode 100644 modules/connect/rest/disconnect.php create mode 100644 modules/connect/rest/reconnect.php create mode 100644 modules/connect/rest/switch-domain.php diff --git a/includes/manager.php b/includes/manager.php index b851757..247b9b2 100644 --- a/includes/manager.php +++ b/includes/manager.php @@ -17,7 +17,8 @@ final class Manager { public static function get_module_list(): array { return [ 'Legacy', - 'Settings' + 'Connect', + 'Settings', ]; } diff --git a/modules/connect/classes/basic-enum.php b/modules/connect/classes/basic-enum.php new file mode 100644 index 0000000..bf5c1aa --- /dev/null +++ b/modules/connect/classes/basic-enum.php @@ -0,0 +1,35 @@ +getConstants(); + } + + return self::$entries[ $caller ]; + } +} diff --git a/modules/connect/classes/config.php b/modules/connect/classes/config.php new file mode 100644 index 0000000..1c4daa9 --- /dev/null +++ b/modules/connect/classes/config.php @@ -0,0 +1,22 @@ +get_col( + $wpdb->prepare( + "SELECT option_value + FROM $wpdb->options + WHERE option_name = %s + AND %s = %s + LIMIT 1", + $option_name, + $cache_buster, + $cache_buster + ) + ); + if ( ! empty( $option ) ) { + return $option[0]; + } + return $default; + } + + /** + * insert_option_uniquely + * + * used to insert option if not there already + * direct query to avoid cache and race condition issues + * + * @param $option_name + * @param $option_value + * + * @return bool + */ + public static function insert_option_uniquely( $option_name, $option_value ): bool { + global $wpdb; + if ( ! self::is_option_whitelisted_for_direct_access( $option_name ) ) { + return false; + } + $cache_buster = wp_generate_uuid4(); + $result = $wpdb->query( + $wpdb->prepare( + "INSERT INTO $wpdb->options (option_name, option_value, autoload) + SELECT * FROM (SELECT %s, %s, 'no') AS tmp + WHERE NOT EXISTS ( + SELECT option_name + FROM $wpdb->options + WHERE option_name = %s + AND option_value = %s + AND %s = %s + ) LIMIT 1", + $option_name, + $option_value, + $option_name, + $option_value, + $cache_buster, + $cache_buster + ) + ); + + if ( false === $result || 0 === $result ) { + // false means query failed, 0 means no row inserted because it exists + return false; + } + + return true; + } + + /** + * is_option_whitelisted_for_direct_access + * allowed only list of option names + * + * @param string $option_name + * + * @return boolean + */ + public static function is_option_whitelisted_for_direct_access( string $option_name ): bool { + $options_whitelist = [ + Config::APP_NAME . Service::REFRESH_TOKEN_LOCK, + ]; + return in_array( $option_name, $options_whitelist, true ); + } + + /** + * User is subscription owner. + * + * Check if current user is subscription owner. + * + * @return boolean + */ + public static function user_is_subscription_owner(): bool { + $owner_id = (int) self::get_connect_mode_data( self::OPTION_OWNER_USER_ID, false ); + + return get_current_user_id() === $owner_id; + } + + /** + * clear_session + */ + public static function clear_session( $with_client = false ) { + if ( Config::CONNECT_MODE === 'site' ) { + if ( $with_client ) { + self::delete_option( self::CLIENT_ID ); + self::delete_option( self::CLIENT_SECRET ); + } + self::delete_option( self::ACCESS_TOKEN ); + self::delete_option( self::REFRESH_TOKEN ); + self::delete_option( self::TOKEN_ID ); + self::delete_option( self::SUBSCRIPTION_ID ); + self::delete_option( self::OPTION_OWNER_USER_ID ); + self::delete_option( self::HOME_URL ); + } else { + $user_id = get_current_user_id(); + if ( $with_client ) { + self::delete_user_data( $user_id, self::CLIENT_ID ); + self::delete_user_data( $user_id, self::CLIENT_SECRET ); + } + self::delete_user_data( $user_id, self::ACCESS_TOKEN ); + self::delete_user_data( $user_id, self::REFRESH_TOKEN ); + self::delete_user_data( $user_id, self::TOKEN_ID ); + self::delete_user_data( $user_id, self::SUBSCRIPTION_ID ); + self::delete_user_data( $user_id, self::OPTION_OWNER_USER_ID ); + self::delete_user_data( $user_id, self::HOME_URL ); + } + } +} diff --git a/modules/connect/classes/exceptions/service-exception.php b/modules/connect/classes/exceptions/service-exception.php new file mode 100644 index 0000000..ad0af38 --- /dev/null +++ b/modules/connect/classes/exceptions/service-exception.php @@ -0,0 +1,13 @@ +get_path(); + } + + public function get_path(): string { + return $this->path; + } + + public function get_name(): string { + return ''; + } + + public function post_permission_callback( \WP_REST_Request $request ): bool { + return $this->get_permission_callback( $request ); + } + + public function get_permission_callback( \WP_REST_Request $request ): bool { + $valid = $this->permission_callback( $request ); + + return $valid && user_can( $this->current_user_id, 'manage_options' ); + } +} diff --git a/modules/connect/classes/service.php b/modules/connect/classes/service.php new file mode 100644 index 0000000..b38eca9 --- /dev/null +++ b/modules/connect/classes/service.php @@ -0,0 +1,320 @@ + 'POST', + 'headers' => [ + 'Content-Type' => 'application/json', + ], + 'body' => wp_json_encode([ + 'redirect_uri' => Utils::get_redirect_uri(), + 'app_type' => Config::APP_TYPE, + ]), + ], 201 ); + + $client_id = $client_data['client_id'] ?? null; + $client_secret = $client_data['client_secret'] ?? null; + + Data::set_client_id( $client_id ); + Data::set_client_secret( $client_secret ); + Data::set_home_url(); + + return $client_id; + } + + /** + * Deactivate license + * + * @return void + * @throws Service_Exception + */ + public static function deactivate_license(): void { + $client_id = Data::get_client_id(); + + if ( ! $client_id ) { + throw new Service_Exception( 'Missing client ID' ); + } + + $deactivation_url = Utils::get_deactivation_url( $client_id ); + + if ( ! $deactivation_url ) { + throw new Service_Exception( 'Missing deactivation URL' ); + } + + $access_token = Data::get_access_token(); + + if ( ! $access_token ) { + throw new Service_Exception( 'Missing access token' ); + } + + $refresh_token = Data::get_refresh_token(); + + if ( ! $refresh_token ) { + throw new Service_Exception( 'Missing refresh token' ); + } + + self::request($deactivation_url, [ + 'method' => 'DELETE', + 'headers' => [ + 'Authorization' => "Bearer {$access_token}", + ], + ], 204); + + self::get_token( 'refresh_token', $refresh_token ); + } + + /** + * disconnect + * + * @return void + * @throws Service_Exception + */ + public static function disconnect(): void { + $sessions_url = Utils::get_sessions_url(); + + if ( ! $sessions_url ) { + throw new Service_Exception( 'Missing sessions URL' ); + } + + $access_token = Data::get_access_token(); + + if ( ! $access_token ) { + throw new Service_Exception( 'Missing access token' ); + } + + self::request( $sessions_url, [ + 'method' => 'DELETE', + 'headers' => [ + 'Content-Type' => 'application/json', + 'Authorization' => "Bearer {$access_token}", + ], + ], 204 ); + + Data::clear_session(); + } + + /** + * disconnect + * + * @return void + * @throws Service_Exception + */ + public static function reconnect(): void { + $sessions_url = Utils::get_sessions_url(); + + if ( ! $sessions_url ) { + throw new Service_Exception( 'Missing sessions URL' ); + } + + Data::clear_session(); + } + + /** + * Get token & optionally save to user + * + * @param string $grant_type + * + * @param string|null $credential + * @param bool|null $update + * + * @return array + * @throws Service_Exception + */ + public static function get_token( string $grant_type, ?string $credential = null, ?bool $update = true ): array { + $token_url = Utils::get_token_url(); + + if ( ! $token_url ) { + throw new Service_Exception( 'Missing token URL' ); + } + + $client_id = Data::get_client_id(); + $client_secret = Data::get_client_secret(); + + if ( empty( $client_id ) || empty( $client_secret ) ) { + throw new Service_Exception( 'Missing client ID or secret' ); + } + + $body = [ + 'grant_type' => $grant_type, + 'redirect_uri' => Utils::get_redirect_uri(), + ]; + + switch ( $grant_type ) { + case GrantTypes::AUTHORIZATION_CODE: + $body['code'] = $credential; + break; + case GrantTypes::REFRESH_TOKEN: + $body[ GrantTypes::REFRESH_TOKEN ] = $credential; + break; + case GrantTypes::CLIENT_CREDENTIALS: + $body['redirect_uri'] = Utils::get_redirect_uri( Data::get_home_url() ); + + break; + default: + throw new Service_Exception( 'Invalid grant type' ); + } + + $data = self::request( $token_url, [ + 'method' => 'POST', + 'headers' => [ + 'x-elementor-apps' => Config::APP_NAME, + 'Authorization' => 'Basic ' . base64_encode( "{$client_id}:{$client_secret}" ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode + ], + 'body' => $body, + ] ); + + if ( $update ) { + Data::set_connect_mode_data( Data::TOKEN_ID, $data['id_token'] ?? null ); + Data::set_connect_mode_data( Data::ACCESS_TOKEN, $data['access_token'] ?? null ); + Data::set_connect_mode_data( Data::REFRESH_TOKEN, $data['refresh_token'] ?? null ); + Data::set_connect_mode_data( Data::OPTION_OWNER_USER_ID, get_current_user_id() ?? null ); + } + + return $data; + } + + public static function jwt_decode( $payload ): string { + static $jwks = null; + + $jwks_url = Utils::get_jwks_url(); + if ( ! $jwks_url ) { + return __( 'Missing JWKS URL', 'site-mailer' ); + } + + if ( ! $jwks ) { + $jwks = self::request($jwks_url, [ + 'method' => 'GET', + ]); + } + if ( ! class_exists( 'JWT' ) ) { + require_once SITE_MAILER_PATH . 'vendor/autoload.php'; + if ( ! class_exists( 'JWT' ) ) { + return __( 'JWT class not found', 'site-mailer' ); + } + } + + try { + $decoded = \Firebase\JWT\JWT::decode( $payload, \Firebase\JWT\JWK::parseKeySet( $jwks ) ); + return wp_json_encode( $decoded, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ); + } catch ( \Throwable $th ) { + if ( $th instanceof \Firebase\JWT\ExpiredException ) { + self::get_token( GrantTypes::REFRESH_TOKEN, Data::get_refresh_token() ); + return self::jwt_decode( $payload ); + } + return $th->getMessage(); + } + } + + /** + * @param string $url + * @param array $args + * @param int $valid_response_code + * + * @return array|null + * @throws Service_Exception + */ + public static function request( string $url, array $args, int $valid_response_code = 200 ): ?array { + $args['timeout'] = 30; + + $response = wp_remote_request( $url, $args ); + + if ( is_wp_error( $response ) ) { + Logger::error( $response->get_error_message() ); + + throw new Service_Exception( esc_html( $response->get_error_message() ) ); + } + + if ( wp_remote_retrieve_response_code( $response ) !== $valid_response_code ) { + Logger::error( 'Invalid status code ' . wp_remote_retrieve_response_code( $response ) ); + + throw new Service_Exception( esc_html( wp_remote_retrieve_body( $response ) ) ); + } + + return json_decode( wp_remote_retrieve_body( $response ), true ); + } + + /** + * @throws Service_Exception + */ + public static function refresh_token() { + $lock_key = Config::APP_NAME . self::REFRESH_TOKEN_LOCK; + $last_token = Data::fetch_option( $lock_key, '' ); + + $current_refresh_token = Data::get_refresh_token(); + + if ( ! empty( $last_token ) && $last_token === $current_refresh_token ) { + sleep( 1 ); + return; + } + + delete_option( $lock_key ); + $locked = Data::insert_option_uniquely( $lock_key, $current_refresh_token ); + if ( ! $locked ) { + sleep( 1 ); + return; + } + + self::get_token( GrantTypes::REFRESH_TOKEN, $current_refresh_token ); + } + + /** + * @throws Service_Exception + */ + public static function update_redirect_uri(): void { + $client_id = Data::get_client_id(); + + if ( ! $client_id ) { + throw new Service_Exception( 'Missing client ID' ); + } + + $client_patch_url = Utils::get_clients_patch_url( $client_id ); + + [ 'access_token' => $access_token ] = self::get_token( + GrantTypes::CLIENT_CREDENTIALS, + null, + false + ); + + self::request( $client_patch_url, [ + 'method' => 'PATCH', + 'headers' => [ + 'Content-Type' => 'application/json', + 'Authorization' => "Bearer {$access_token}", + ], + 'body' => wp_json_encode( [ + 'redirect_uri' => Utils::get_redirect_uri(), + ] ), + ] ); + + self::refresh_token(); + + Data::set_home_url(); + } +} diff --git a/modules/connect/classes/utils.php b/modules/connect/classes/utils.php new file mode 100644 index 0000000..9eb9db1 --- /dev/null +++ b/modules/connect/classes/utils.php @@ -0,0 +1,125 @@ + $client_id, + 'redirect_uri' => rawurlencode( self::get_redirect_uri() ), + 'response_type' => 'code', + 'scope' => Config::SCOPES, + 'state' => wp_create_nonce( Config::STATE_NONCE ), + ], self::get_auth_url() ); + } + + /** + * get_deactivation_url + * @param string $client_id + * + * @return string + */ + public static function get_deactivation_url( string $client_id ): string { + return self::get_base_url() . "/api/v1/clients/{$client_id}/activation"; + } + + public static function get_jwks_url(): string { + return self::get_base_url() . '/v1/.well-known/jwks.json'; + } + + /** + * get_sessions_url + * @return string + */ + public static function get_sessions_url(): string { + return self::get_base_url() . '/api/v1/session'; + } + + public static function get_token_url(): string { + return self::get_base_url() . '/api/v1/oauth2/token'; + } + + /** + * Get clients URL + * + * @param string $client_id + * + * @return string + */ + public static function get_clients_patch_url( string $client_id ): string { + return Utils::get_base_url() . "/api/v1/clients/{$client_id}"; + } + + /** + * get_base_url + * @return string + */ + public static function get_base_url(): string { + return apply_filters( 'site_mailer_connect_get_base_url', Config::BASE_URL ); + } + + /** + * is_valid_home_url + * @return bool + */ + public static function is_valid_home_url(): bool { + static $valid = null; + + if ( null === $valid ) { + if ( empty( Data::get_home_url() ) ) { + $valid = true; + } else { + $valid = Data::get_home_url() === home_url(); + } + } + + return $valid; + } +} diff --git a/modules/connect/components/handler.php b/modules/connect/components/handler.php new file mode 100644 index 0000000..5eee71d --- /dev/null +++ b/modules/connect/components/handler.php @@ -0,0 +1,91 @@ +should_handle_auth_code() ) { + return; + } + + $code = sanitize_text_field( $_GET['code'] ); + $state = sanitize_text_field( $_GET['state'] ); + + // Check if the state is valid + $this->validate_nonce( $state ); + + try { + // Exchange the code for an access token and store it + Service::get_token( GrantTypes::AUTHORIZATION_CODE, $code ); // Makes sure we won't stick in the mismatch limbo + + Data::set_home_url(); + + do_action( 'on_connect_' . Config::APP_PREFIX . '_connected' ); // Redirect to the redirect URI + } catch ( Throwable $t ) { + Logger::error( 'Unable to handle auth code: ' . $t->getMessage() ); + } + + wp_redirect( Utils::get_redirect_uri() ); + + exit; + } + + /** + * Handler constructor. + */ + public function __construct() { + add_action( 'admin_init', [ $this, 'handle_auth_code' ] ); + } +} diff --git a/modules/connect/module.php b/modules/connect/module.php new file mode 100644 index 0000000..c226985 --- /dev/null +++ b/modules/connect/module.php @@ -0,0 +1,84 @@ +register_components(); + $this->register_routes(); + add_filter( 'site_mailer_connect_authorize_url', [ $this, 'authorize_url' ] ); + } +} + diff --git a/modules/connect/rest/authorize.php b/modules/connect/rest/authorize.php new file mode 100644 index 0000000..8437861 --- /dev/null +++ b/modules/connect/rest/authorize.php @@ -0,0 +1,78 @@ +verify_nonce_and_capability( + $request->get_param( self::NONCE_NAME ), + self::NONCE_NAME + ); + + if ( Connect::is_connected() && Utils::is_valid_home_url() ) { + return $this->respond_error_json( [ + 'message' => esc_html__( 'You are already connected', 'site-mailer' ), + 'code' => 'forbidden', + ] ); + } + + try { + $client_id = Data::get_client_id(); + + if ( ! $client_id ) { + $client_id = Service::register_client(); + } + + if ( ! Utils::is_valid_home_url() ) { + if ( $request->get_param( 'update_redirect_uri' ) ) { + Service::update_redirect_uri(); + } else { + return $this->respond_error_json( [ + 'message' => esc_html__( 'Connected domain mismatch', 'site-mailer' ), + 'code' => 'forbidden', + ] ); + } + } + + $authorize_url = Utils::get_authorize_url( $client_id ); + + $authorize_url = apply_filters( 'site_mailer_connect_authorize_url', $authorize_url ); + + return $this->respond_success_json( $authorize_url ); + } catch ( Throwable $t ) { + return $this->respond_error_json( [ + 'message' => $t->getMessage(), + 'code' => 'internal_server_error', + ] ); + } + } +} diff --git a/modules/connect/rest/deactivate-and-disconnect.php b/modules/connect/rest/deactivate-and-disconnect.php new file mode 100644 index 0000000..4b7dc87 --- /dev/null +++ b/modules/connect/rest/deactivate-and-disconnect.php @@ -0,0 +1,50 @@ +get_param( 'clear_session' ) ) { + Data::clear_session( true ); + return $this->respond_success_json(); + } + + Service::deactivate_license(); + Service::disconnect(); + + return $this->respond_success_json(); + } catch ( Throwable $t ) { + return $this->respond_error_json( [ + 'message' => $t->getMessage(), + 'code' => 'internal_server_error', + ] ); + } + } +} diff --git a/modules/connect/rest/deactivate.php b/modules/connect/rest/deactivate.php new file mode 100644 index 0000000..01c9aec --- /dev/null +++ b/modules/connect/rest/deactivate.php @@ -0,0 +1,42 @@ +respond_success_json(); + } catch ( Throwable $t ) { + return $this->respond_error_json( [ + 'message' => $t->getMessage(), + 'code' => 'internal_server_error', + ] ); + } + } +} diff --git a/modules/connect/rest/disconnect.php b/modules/connect/rest/disconnect.php new file mode 100644 index 0000000..303c477 --- /dev/null +++ b/modules/connect/rest/disconnect.php @@ -0,0 +1,42 @@ +respond_success_json(); + } catch ( Throwable $t ) { + return $this->respond_error_json( [ + 'message' => $t->getMessage(), + 'code' => 'internal_server_error', + ] ); + } + } +} diff --git a/modules/connect/rest/reconnect.php b/modules/connect/rest/reconnect.php new file mode 100644 index 0000000..0c43431 --- /dev/null +++ b/modules/connect/rest/reconnect.php @@ -0,0 +1,41 @@ +respond_success_json(); + } catch ( Throwable $t ) { + return $this->respond_error_json( [ + 'message' => $t->getMessage(), + 'code' => 'internal_server_error', + ] ); + } + } +} diff --git a/modules/connect/rest/switch-domain.php b/modules/connect/rest/switch-domain.php new file mode 100644 index 0000000..34f2cc2 --- /dev/null +++ b/modules/connect/rest/switch-domain.php @@ -0,0 +1,54 @@ +verify_nonce_and_capability( + $request->get_param( self::NONCE_NAME ), + self::NONCE_NAME + ); + + try { + $client_id = Data::get_client_id(); + + if ( ! $client_id ) { + return $this->respond_error_json( [ + 'message' => __( 'Client ID not found', 'site-mailer' ), + 'code' => 'ignore_error', + ] ); + } + + Service::update_redirect_uri(); + + return $this->respond_success_json( [ 'message' => __( 'Domain updated!', 'site-mailer' ) ] ); + } catch ( Throwable $t ) { + return $this->respond_error_json( [ + 'message' => $t->getMessage(), + 'code' => 'internal_server_error', + ] ); + } + } +} From d6c4efcc9c1bf894e7c616b8f681d8ec7a893ff6 Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Fri, 1 Nov 2024 13:18:01 +0530 Subject: [PATCH 20/65] add: settings and get settings route --- modules/settings/classes/route-base.php | 39 +++++++++++++++++++ modules/settings/classes/settings.php | 25 +++++++++++++ modules/settings/module.php | 22 ++++++++++- modules/settings/rest/get-settings.php | 50 +++++++++++++++++++++++++ 4 files changed, 134 insertions(+), 2 deletions(-) create mode 100644 modules/settings/classes/route-base.php create mode 100644 modules/settings/classes/settings.php create mode 100644 modules/settings/rest/get-settings.php diff --git a/modules/settings/classes/route-base.php b/modules/settings/classes/route-base.php new file mode 100644 index 0000000..907a340 --- /dev/null +++ b/modules/settings/classes/route-base.php @@ -0,0 +1,39 @@ +get_path(); + } + + public function get_path(): string { + return $this->path; + } + + public function get_name(): string { + return ''; + } + + public function get_permission_callback( \WP_REST_Request $request ): bool { + $valid = $this->permission_callback( $request ); + + return $valid && user_can( $this->current_user_id, 'manage_options' ); + } +} diff --git a/modules/settings/classes/settings.php b/modules/settings/classes/settings.php new file mode 100644 index 0000000..44f08b2 --- /dev/null +++ b/modules/settings/classes/settings.php @@ -0,0 +1,25 @@ + Connect::is_connected(), + ]; + } + /** * Module constructor. */ public function __construct() { + $this->register_routes(); $this->register_components(); add_action( 'admin_menu', [ $this, 'register_page' ] ); add_action( 'in_admin_header', [ $this, 'render_top_bar' ] ); diff --git a/modules/settings/rest/get-settings.php b/modules/settings/rest/get-settings.php new file mode 100644 index 0000000..4cdd581 --- /dev/null +++ b/modules/settings/rest/get-settings.php @@ -0,0 +1,50 @@ +verify_capability(); + + if ( $error ) { + return $error; + } + + $data = Settings::get_plugin_settings(); + + return $this->respond_success_json( $data ); + + } catch ( Throwable $t ) { + return $this->respond_error_json( [ + 'message' => $t->getMessage(), + 'code' => 'internal_server_error', + ] ); + } + } +} From c87250b35a5164efc93ff52fe61df0d8d7f4f521 Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Fri, 1 Nov 2024 13:18:31 +0530 Subject: [PATCH 21/65] add: hooks and contexts to get settings --- .../js/contexts/plugin-settings-context.js | 40 ++++++++++++++ .../settings/assets/js/hooks/use-settings.js | 55 +++++++++++++++++++ .../settings/assets/js/hooks/use-storage.js | 14 +++++ 3 files changed, 109 insertions(+) create mode 100644 modules/settings/assets/js/contexts/plugin-settings-context.js create mode 100644 modules/settings/assets/js/hooks/use-settings.js create mode 100644 modules/settings/assets/js/hooks/use-storage.js diff --git a/modules/settings/assets/js/contexts/plugin-settings-context.js b/modules/settings/assets/js/contexts/plugin-settings-context.js new file mode 100644 index 0000000..3897052 --- /dev/null +++ b/modules/settings/assets/js/contexts/plugin-settings-context.js @@ -0,0 +1,40 @@ +import { createContext, useCallback, useContext, useEffect, useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import API from '../api'; +import { useToastNotification } from '../hooks/use-settings'; + +const PluginSettingsContext = createContext( {} ); + +export const PluginSettingsProvider = ( { children } ) => { + const { error } = useToastNotification(); + const [ pluginSettings, setPluginSettings ] = useState(); + const [ loaded, setLoaded ] = useState( false ); + + const refreshPluginSettings = useCallback( () => { + API.getPluginSettings().then( ( settings ) => { + if ( 'isConnected' in settings ) { + settings.isConnected = Boolean( settings.isConnected ); + } + + setPluginSettings( settings ); + setLoaded( true ); + } ).catch( () => { + error( __( 'An error occurred.', 'site-mailer' ) ); + setLoaded( true ); + } ); + }, [] ); + + useEffect( () => { + refreshPluginSettings(); + }, [ refreshPluginSettings ] ); + + return ( + + { children } + + ); +}; + +export const usePluginSettingsContext = () => { + return useContext( PluginSettingsContext ); +}; diff --git a/modules/settings/assets/js/hooks/use-settings.js b/modules/settings/assets/js/hooks/use-settings.js new file mode 100644 index 0000000..1578ec8 --- /dev/null +++ b/modules/settings/assets/js/hooks/use-settings.js @@ -0,0 +1,55 @@ +import { useState, createContext, useContext } from '@wordpress/element'; + +/** + * Context Component. + */ +const SettingsContext = createContext( null ); + +export function useSettings() { + return useContext( SettingsContext ); +} + +const SettingsProvider = ( { children } ) => { + // Notification + const [ showNotification, setShowNotification ] = useState( false ); + const [ notificationMessage, setNotificationMessage ] = useState( '' ); + const [ notificationType, setNotificationType ] = useState( '' ); + + return ( + + { children } + + ); +}; + +export const useToastNotification = () => { + const { setNotificationMessage, setNotificationType, setShowNotification } = useContext( SettingsContext ); + + const error = ( message ) => { + setNotificationMessage( message ); + setNotificationType( 'error' ); + setShowNotification( true ); + }; + + const success = ( message ) => { + setNotificationMessage( message ); + setNotificationType( 'success' ); + setShowNotification( true ); + }; + + return { + success, + error, + }; +}; + +export default SettingsProvider; diff --git a/modules/settings/assets/js/hooks/use-storage.js b/modules/settings/assets/js/hooks/use-storage.js new file mode 100644 index 0000000..717ebd3 --- /dev/null +++ b/modules/settings/assets/js/hooks/use-storage.js @@ -0,0 +1,14 @@ +import { store as coreDataStore } from '@wordpress/core-data'; +import { dispatch } from '@wordpress/data'; + +const useStorage = () => { + const save = async ( data ) => { + return await dispatch( coreDataStore ).saveEntityRecord( 'root', 'site', data ); + }; + + return { + save, + }; +}; + +export default useStorage; From 7938003b623f8a97f862ab96adee032872e687f3 Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Fri, 1 Nov 2024 13:18:45 +0530 Subject: [PATCH 22/65] add: hooks --- modules/settings/assets/js/hooks/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/settings/assets/js/hooks/index.js b/modules/settings/assets/js/hooks/index.js index 2bbadea..6bbb8ca 100644 --- a/modules/settings/assets/js/hooks/index.js +++ b/modules/settings/assets/js/hooks/index.js @@ -1,2 +1,6 @@ export { default as useAuth } from './use-auth'; export { default as useModal } from './use-modal'; +export { useSettings } from './use-settings'; +export { useToastNotification } from './use-settings'; +export { default as SettingsProvider } from './use-settings'; +export { default as useStorage } from './use-storage'; From 06e8323f91b83b6e2c8acdaa2274e083aebdb7f3 Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Fri, 1 Nov 2024 13:19:03 +0530 Subject: [PATCH 23/65] add: notification component --- .../settings/assets/js/components/index.js | 1 + .../js/components/notifications/index.js | 34 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 modules/settings/assets/js/components/notifications/index.js diff --git a/modules/settings/assets/js/components/index.js b/modules/settings/assets/js/components/index.js index a07d5ce..ef0c049 100644 --- a/modules/settings/assets/js/components/index.js +++ b/modules/settings/assets/js/components/index.js @@ -1 +1,2 @@ export { default as ConnectModal } from './connect-modal'; +export { default as Notifications } from './notifications'; diff --git a/modules/settings/assets/js/components/notifications/index.js b/modules/settings/assets/js/components/notifications/index.js new file mode 100644 index 0000000..96c0b0a --- /dev/null +++ b/modules/settings/assets/js/components/notifications/index.js @@ -0,0 +1,34 @@ +import Alert from '@elementor/ui/Alert'; +import Snackbar from '@elementor/ui/Snackbar'; +import { useSettings } from '../../hooks'; + +const Notifications = ( { type, message } ) => { + const { + showNotification, + setShowNotification, + setNotificationMessage, + setNotificationType, + } = useSettings(); + + const closeNotification = () => { + setShowNotification( ! showNotification ); + setNotificationMessage( '' ); + setNotificationType( '' ); + }; + + return ( + + setShowNotification( ! showNotification ) } severity={ type } variant="filled" > + { message } + + + ); +}; + +export default Notifications; From 3c8845e31d1fa104c0f9ba0b3583afadb14f2803 Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Fri, 1 Nov 2024 13:19:16 +0530 Subject: [PATCH 24/65] add: data api --- modules/settings/assets/js/api/index.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/settings/assets/js/api/index.js b/modules/settings/assets/js/api/index.js index f66dd2c..2648f10 100644 --- a/modules/settings/assets/js/api/index.js +++ b/modules/settings/assets/js/api/index.js @@ -3,7 +3,7 @@ import { addQueryArgs } from '@wordpress/url'; import APIError from './exceptions/APIError'; const wpV2Prefix = '/wp/v2'; -const v1Prefix = '/site-mailer/v1'; +const v1Prefix = '/ea11y/v1'; class API { static async request( { path, data, method = 'POST' } ) { @@ -38,7 +38,7 @@ class API { static async initConnect( context = 'new' ) { const data = { - wp_rest: window?.siteMailerSettingsData?.wpRestNonce, + wp_rest: window?.ea11ySettingsData?.wpRestNonce, }; if ( 'update' === context ) { @@ -57,7 +57,7 @@ class API { method: 'POST', path: `${ v1Prefix }/connect/deactivate_and_disconnect`, data: { - wp_rest: window?.siteMailerSettingsData?.wpRestNonce, + wp_rest: window?.ea11ySettingsData?.wpRestNonce, clear_session: true, }, } ); @@ -68,7 +68,7 @@ class API { method: 'POST', path: `${ v1Prefix }/connect/deactivate_and_disconnect`, data: { - wp_rest: window?.siteMailerSettingsData?.wpRestNonce, + wp_rest: window?.ea11ySettingsData?.wpRestNonce, }, } ); } @@ -78,7 +78,7 @@ class API { method: 'POST', path: `${ v1Prefix }/connect/deactivate`, data: { - wp_rest: window?.siteMailerSettingsData?.wpRestNonce, + wp_rest: window?.ea11ySettingsData?.wpRestNonce, }, } ); } @@ -88,7 +88,7 @@ class API { method: 'POST', path: `${ v1Prefix }/connect/disconnect`, data: { - wp_rest: window?.siteMailerSettingsData?.wpRestNonce, + wp_rest: window?.ea11ySettingsData?.wpRestNonce, }, } ); } @@ -98,7 +98,7 @@ class API { method: 'POST', path: `${ v1Prefix }/connect/reconnect`, data: { - wp_rest: window?.siteMailerSettingsData?.wpRestNonce, + wp_rest: window?.ea11ySettingsData?.wpRestNonce, }, } ); } From b451dc57165e238d14e148db31048300dc3c148b Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Fri, 1 Nov 2024 13:19:57 +0530 Subject: [PATCH 25/65] add: settings provider and connect settings --- modules/settings/assets/js/admin.js | 8 +++++++- modules/settings/assets/js/app.js | 14 ++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/modules/settings/assets/js/admin.js b/modules/settings/assets/js/admin.js index 4e51e3a..b950bc1 100644 --- a/modules/settings/assets/js/admin.js +++ b/modules/settings/assets/js/admin.js @@ -2,6 +2,8 @@ import { ThemeProvider } from '@elementor/ui/styles'; import { StrictMode, Fragment, createRoot } from '@wordpress/element'; import App from './app'; import AdminTopBar from './components/admin-top-bar'; +import { PluginSettingsProvider } from './contexts/plugin-settings-context'; +import { SettingsProvider } from './hooks'; const rootNode = document.getElementById( 'ea11y-app' ); const topBarNode = document.getElementById( 'ea11y-app-top-bar' ); @@ -21,6 +23,10 @@ topBar.render( root.render( - + + + + + , ); diff --git a/modules/settings/assets/js/app.js b/modules/settings/assets/js/app.js index 2ca43d1..d9c8caa 100644 --- a/modules/settings/assets/js/app.js +++ b/modules/settings/assets/js/app.js @@ -1,13 +1,23 @@ import '../css/style.css'; import DirectionProvider from '@elementor/ui/DirectionProvider'; import { ThemeProvider } from '@elementor/ui/styles'; -import { ConnectModal } from './components'; +import { ConnectModal, Notifications } from './components'; +import { usePluginSettingsContext } from './contexts/plugin-settings-context'; +import { useSettings } from './hooks'; const App = () => { + const { + isConnected, + } = usePluginSettingsContext(); + const { + notificationMessage, + notificationType, + } = useSettings(); return ( - + { ! isConnected && } + ); From d89b521599c263ef57ea930f1ad7f8c9f99eb6ed Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Fri, 1 Nov 2024 13:20:14 +0530 Subject: [PATCH 26/65] add: husky --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 2ed5569..59821d2 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "@wordpress/date": "^5.10.0", "@wordpress/element": "^6.10.0", "@wordpress/i18n": "^5.10.0", - "@wordpress/url": "^4.10.0" + "@wordpress/url": "^4.10.0", + "husky": "^9.1.6" } } From 879b72e4bb9d7b5836f9ceea665f041ef89f197f Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Fri, 8 Nov 2024 13:26:54 +0530 Subject: [PATCH 27/65] fix: formatting and text-domain --- modules/connect/classes/service.php | 6 +++--- modules/connect/rest/authorize.php | 6 +++--- modules/connect/rest/switch-domain.php | 15 ++++++++++----- modules/settings/assets/js/app.js | 4 +--- .../assets/js/components/connect-modal/index.js | 2 +- .../assets/js/contexts/plugin-settings-context.js | 2 +- 6 files changed, 19 insertions(+), 16 deletions(-) diff --git a/modules/connect/classes/service.php b/modules/connect/classes/service.php index b38eca9..88180c4 100644 --- a/modules/connect/classes/service.php +++ b/modules/connect/classes/service.php @@ -205,7 +205,7 @@ public static function jwt_decode( $payload ): string { $jwks_url = Utils::get_jwks_url(); if ( ! $jwks_url ) { - return __( 'Missing JWKS URL', 'site-mailer' ); + return __( 'Missing JWKS URL', 'pojo-accessibility' ); } if ( ! $jwks ) { @@ -214,9 +214,9 @@ public static function jwt_decode( $payload ): string { ]); } if ( ! class_exists( 'JWT' ) ) { - require_once SITE_MAILER_PATH . 'vendor/autoload.php'; + require_once EA11Y_PATH . 'vendor/autoload.php'; if ( ! class_exists( 'JWT' ) ) { - return __( 'JWT class not found', 'site-mailer' ); + return __( 'JWT class not found', 'pojo-accessibility' ); } } diff --git a/modules/connect/rest/authorize.php b/modules/connect/rest/authorize.php index 8437861..32b6899 100644 --- a/modules/connect/rest/authorize.php +++ b/modules/connect/rest/authorize.php @@ -40,7 +40,7 @@ public function POST( WP_REST_Request $request ) { if ( Connect::is_connected() && Utils::is_valid_home_url() ) { return $this->respond_error_json( [ - 'message' => esc_html__( 'You are already connected', 'site-mailer' ), + 'message' => esc_html__( 'You are already connected', 'pojo-accessibility' ), 'code' => 'forbidden', ] ); } @@ -57,7 +57,7 @@ public function POST( WP_REST_Request $request ) { Service::update_redirect_uri(); } else { return $this->respond_error_json( [ - 'message' => esc_html__( 'Connected domain mismatch', 'site-mailer' ), + 'message' => esc_html__( 'Connected domain mismatch', 'pojo-accessibility' ), 'code' => 'forbidden', ] ); } @@ -65,7 +65,7 @@ public function POST( WP_REST_Request $request ) { $authorize_url = Utils::get_authorize_url( $client_id ); - $authorize_url = apply_filters( 'site_mailer_connect_authorize_url', $authorize_url ); + $authorize_url = apply_filters( 'ea11y_connect_authorize_url', $authorize_url ); return $this->respond_success_json( $authorize_url ); } catch ( Throwable $t ) { diff --git a/modules/connect/rest/switch-domain.php b/modules/connect/rest/switch-domain.php index 34f2cc2..d4b54c0 100644 --- a/modules/connect/rest/switch-domain.php +++ b/modules/connect/rest/switch-domain.php @@ -1,8 +1,13 @@ respond_error_json( [ - 'message' => __( 'Client ID not found', 'site-mailer' ), + 'message' => __( 'Client ID not found', 'pojo-accessibility' ), 'code' => 'ignore_error', ] ); } Service::update_redirect_uri(); - return $this->respond_success_json( [ 'message' => __( 'Domain updated!', 'site-mailer' ) ] ); + return $this->respond_success_json( [ 'message' => __( 'Domain updated!', 'pojo-accessibility' ) ] ); } catch ( Throwable $t ) { return $this->respond_error_json( [ 'message' => $t->getMessage(), diff --git a/modules/settings/assets/js/app.js b/modules/settings/assets/js/app.js index d9c8caa..b765bb8 100644 --- a/modules/settings/assets/js/app.js +++ b/modules/settings/assets/js/app.js @@ -6,9 +6,7 @@ import { usePluginSettingsContext } from './contexts/plugin-settings-context'; import { useSettings } from './hooks'; const App = () => { - const { - isConnected, - } = usePluginSettingsContext(); + const { isConnected } = usePluginSettingsContext(); const { notificationMessage, notificationType, diff --git a/modules/settings/assets/js/components/connect-modal/index.js b/modules/settings/assets/js/components/connect-modal/index.js index d0fe566..3f85e71 100644 --- a/modules/settings/assets/js/components/connect-modal/index.js +++ b/modules/settings/assets/js/components/connect-modal/index.js @@ -34,7 +34,7 @@ function ConnectModal() { - { __( 'Connect plugin on your site!', 'site-mailer' ) } + { __( 'Connect plugin on your site!', 'pojo-accessibility' ) } + + ); +}; From 92e7de27098e9391dcfc503c2342a0856c6c6d78 Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Fri, 15 Nov 2024 16:24:41 +0530 Subject: [PATCH 50/65] add: bottom bar and top bar --- modules/settings/assets/js/components/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/settings/assets/js/components/index.js b/modules/settings/assets/js/components/index.js index 6a99e3a..b5be9a7 100644 --- a/modules/settings/assets/js/components/index.js +++ b/modules/settings/assets/js/components/index.js @@ -4,3 +4,5 @@ export { default as MyAccountMenu } from './my-account-menu'; export { SidebarAppBar } from './sidebar-app-bar'; export { SidebarMenu } from './sidebar-menu'; export { MenuItems } from '../components/sidebar-menu/menu'; +export { AdminTopBar } from './admin-top-bar'; +export { BottomBar } from './bottom-bar'; From 042568d759bd439a48effec8040cce6f3e505aa6 Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Fri, 15 Nov 2024 16:24:48 +0530 Subject: [PATCH 51/65] add: bottom bar and top bar --- modules/settings/assets/js/app.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/settings/assets/js/app.js b/modules/settings/assets/js/app.js index 756388f..f629cb6 100644 --- a/modules/settings/assets/js/app.js +++ b/modules/settings/assets/js/app.js @@ -3,7 +3,7 @@ import Box from '@elementor/ui/Box'; import DirectionProvider from '@elementor/ui/DirectionProvider'; import Grid from '@elementor/ui/Grid'; import { ThemeProvider } from '@elementor/ui/styles'; -import { ConnectModal, Notifications, MenuItems } from './components'; +import { ConnectModal, Notifications, MenuItems, AdminTopBar, BottomBar } from './components'; import { usePluginSettingsContext } from './contexts/plugin-settings'; import { useNotificationSettings, useSettings } from './hooks'; import { Sidebar } from './layouts/sidebar'; @@ -27,8 +27,10 @@ const App = () => { flexDirection="row" height="100%"> - + + { selectedChild ? selectedChild.page : selectedParent?.page } + From 399e6e0e0b1a59f0de6540a6b23b14a938987c75 Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Fri, 15 Nov 2024 16:44:27 +0530 Subject: [PATCH 52/65] update: page content styling --- modules/settings/assets/js/app.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/settings/assets/js/app.js b/modules/settings/assets/js/app.js index f629cb6..54d4648 100644 --- a/modules/settings/assets/js/app.js +++ b/modules/settings/assets/js/app.js @@ -27,9 +27,15 @@ const App = () => { flexDirection="row" height="100%"> - + - { selectedChild ? selectedChild.page : selectedParent?.page } + + { selectedChild ? selectedChild.page : selectedParent?.page } + From 28cff2d69ff6113503673af319a7111dabbc067e Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Fri, 15 Nov 2024 16:44:44 +0530 Subject: [PATCH 53/65] fix: styling --- modules/settings/assets/js/components/admin-top-bar/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/settings/assets/js/components/admin-top-bar/index.js b/modules/settings/assets/js/components/admin-top-bar/index.js index 201f41d..eeeec94 100644 --- a/modules/settings/assets/js/components/admin-top-bar/index.js +++ b/modules/settings/assets/js/components/admin-top-bar/index.js @@ -14,7 +14,7 @@ export const AdminTopBar = () => { alignItems: 'center', backgroundColor: 'background.default', gap: '10px', - borderBottom: '1px solid rgba(0, 0, 0, 0.12)\n', + borderBottom: '1px solid rgba(0, 0, 0, 0.12)', } } padding={ 2 }> Date: Fri, 15 Nov 2024 16:44:52 +0530 Subject: [PATCH 54/65] fix: styling --- modules/settings/assets/js/components/bottom-bar/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/settings/assets/js/components/bottom-bar/index.js b/modules/settings/assets/js/components/bottom-bar/index.js index 23eb969..e749b1e 100644 --- a/modules/settings/assets/js/components/bottom-bar/index.js +++ b/modules/settings/assets/js/components/bottom-bar/index.js @@ -6,7 +6,8 @@ export const BottomBar = () => { + width="100%" + borderTop="1px solid rgba(0, 0, 0, 0.12)"> ); From 4714c6239d675f9bd565fd45498c69874f5eeda3 Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Fri, 15 Nov 2024 16:51:05 +0530 Subject: [PATCH 55/65] update: text domain --- .../settings/assets/js/components/bottom-bar/index.js | 5 ++++- .../settings/assets/js/components/sidebar-menu/menu.js | 9 +++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/modules/settings/assets/js/components/bottom-bar/index.js b/modules/settings/assets/js/components/bottom-bar/index.js index e749b1e..1e27212 100644 --- a/modules/settings/assets/js/components/bottom-bar/index.js +++ b/modules/settings/assets/js/components/bottom-bar/index.js @@ -1,5 +1,6 @@ import Box from '@elementor/ui/Box'; import Button from '@elementor/ui/Button'; +import { __ } from '@wordpress/i18n'; export const BottomBar = () => { return ( @@ -8,7 +9,9 @@ export const BottomBar = () => { p={ 2 } width="100%" borderTop="1px solid rgba(0, 0, 0, 0.12)"> - + ); }; diff --git a/modules/settings/assets/js/components/sidebar-menu/menu.js b/modules/settings/assets/js/components/sidebar-menu/menu.js index 7c50d3b..81a8f07 100644 --- a/modules/settings/assets/js/components/sidebar-menu/menu.js +++ b/modules/settings/assets/js/components/sidebar-menu/menu.js @@ -1,27 +1,28 @@ import { PagesIcon } from '@elementor/icons'; +import { __ } from '@wordpress/i18n'; import { WidgetIcon } from '../../icons'; import { AccessibilityStatement, Menu, IconSettings } from '../../pages'; export const MenuItems = { widget: { - name: 'Widget', + name: __( 'Widget', 'pojo-accessibility' ), key: 'widget', icon: , children: { iconSettings: { - name: 'Icon Settings', + name: __( 'Icon Settings', 'pojo-accessibility' ), key: 'icon-settings', page: , }, menu: { - name: 'Menu', + name: __( 'Menu', 'pojo-accessibility' ), key: 'menu', page: , }, }, }, accessibilityStatement: { - name: 'Accessibility Statement', + name: __( 'Accessibility Statement', 'pojo-accessibility' ), key: 'accessibility-statement', page: , icon: , From cf060e1366ec3b8a1941d0a86cda7df0a0afa9ee Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Tue, 19 Nov 2024 19:43:54 +0530 Subject: [PATCH 56/65] update: added translations --- .../settings/assets/js/components/my-account-menu/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/settings/assets/js/components/my-account-menu/index.js b/modules/settings/assets/js/components/my-account-menu/index.js index 24207b9..2ec1de2 100644 --- a/modules/settings/assets/js/components/my-account-menu/index.js +++ b/modules/settings/assets/js/components/my-account-menu/index.js @@ -18,7 +18,7 @@ import { const MyAccountMenu = () => { const { openSidebar } = useSettings(); - const accountMenuState = usePopupState( { variant: 'popover', popupId: 'demoMenu' } ); + const accountMenuState = usePopupState( { variant: 'popover', popupId: 'myAccountMenu' } ); return ( <> @@ -26,7 +26,7 @@ const MyAccountMenu = () => { - ); }; + +export default SidebarMenu; From 9e388c20ce6659d11f43b3c659a6b59eb21ef583 Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Mon, 25 Nov 2024 20:08:05 +0530 Subject: [PATCH 60/65] add: aliases for imports and fix exports --- .eslintrc | 15 ++- modules/settings/assets/js/admin.js | 2 +- modules/settings/assets/js/app.js | 4 +- .../js/components/connect-modal/index.js | 2 +- .../assets/js/components/icon-select/index.js | 27 +++-- .../assets/js/components/icon-size/index.js | 2 +- .../js/components/my-account-menu/index.js | 6 +- .../js/components/notifications/index.js | 2 +- .../js/components/sidebar-app-bar/index.js | 7 +- .../js/components/sidebar-menu/index.js | 2 +- .../assets/js/components/sidebar-menu/menu.js | 2 +- .../assets/js/layouts/icon-design-settings.js | 2 +- .../js/layouts/position-settings-desktop.js | 2 +- .../assets/js/layouts/position-settings.js | 2 +- modules/settings/assets/js/layouts/sidebar.js | 4 +- .../js/pages/accessibility-statement.js | 4 +- .../settings/assets/js/pages/icon-settings.js | 4 +- modules/settings/assets/js/pages/index.js | 6 +- modules/settings/assets/js/pages/menu.js | 4 +- package-lock.json | 111 +++++++++++++++++- package.json | 5 +- webpack.config.js | 10 ++ 22 files changed, 187 insertions(+), 38 deletions(-) diff --git a/.eslintrc b/.eslintrc index 0ad9518..a71f548 100644 --- a/.eslintrc +++ b/.eslintrc @@ -9,6 +9,16 @@ "babel", "react" ], + "settings": { + "import/resolver": { + "node": { + "paths": ["node_modules"] + }, + "webpack": { + "config": "webpack.config.js" + } + } + }, "env": { "browser": true, "node": true, @@ -47,7 +57,10 @@ "caseInsensitive": false } } - ] + ], + "import/newline-after-import": ["error", { + "count": 1 + }] }, "parser": "@babel/eslint-parser" } diff --git a/modules/settings/assets/js/admin.js b/modules/settings/assets/js/admin.js index 811e977..c3b2510 100644 --- a/modules/settings/assets/js/admin.js +++ b/modules/settings/assets/js/admin.js @@ -1,7 +1,7 @@ +import { SettingsProvider, NotificationsProvider } from '@ea11y/hooks'; import { StrictMode, Fragment, createRoot } from '@wordpress/element'; import App from './app'; import { PluginSettingsProvider } from './contexts/plugin-settings'; -import { SettingsProvider, NotificationsProvider } from './hooks'; const rootNode = document.getElementById( 'ea11y-app' ); diff --git a/modules/settings/assets/js/app.js b/modules/settings/assets/js/app.js index 54d4648..b794297 100644 --- a/modules/settings/assets/js/app.js +++ b/modules/settings/assets/js/app.js @@ -3,9 +3,9 @@ import Box from '@elementor/ui/Box'; import DirectionProvider from '@elementor/ui/DirectionProvider'; import Grid from '@elementor/ui/Grid'; import { ThemeProvider } from '@elementor/ui/styles'; -import { ConnectModal, Notifications, MenuItems, AdminTopBar, BottomBar } from './components'; +import { ConnectModal, Notifications, MenuItems, AdminTopBar, BottomBar } from '@ea11y/components'; +import { useNotificationSettings, useSettings } from '@ea11y/hooks'; import { usePluginSettingsContext } from './contexts/plugin-settings'; -import { useNotificationSettings, useSettings } from './hooks'; import { Sidebar } from './layouts/sidebar'; const App = () => { diff --git a/modules/settings/assets/js/components/connect-modal/index.js b/modules/settings/assets/js/components/connect-modal/index.js index 8ea7f9c..1179e4d 100644 --- a/modules/settings/assets/js/components/connect-modal/index.js +++ b/modules/settings/assets/js/components/connect-modal/index.js @@ -3,8 +3,8 @@ import Button from '@elementor/ui/Button'; import Grid from '@elementor/ui/Grid'; import Modal from '@elementor/ui/Modal'; import Typography from '@elementor/ui/Typography'; +import { useAuth, useModal } from '@ea11y/hooks'; import { __ } from '@wordpress/i18n'; -import { useAuth, useModal } from '../../hooks'; function ConnectModal() { const { isOpen } = useModal(); diff --git a/modules/settings/assets/js/components/icon-select/index.js b/modules/settings/assets/js/components/icon-select/index.js index e5d1efd..cd6bd41 100644 --- a/modules/settings/assets/js/components/icon-select/index.js +++ b/modules/settings/assets/js/components/icon-select/index.js @@ -4,19 +4,29 @@ import Paper from '@elementor/ui/Paper'; import Radio from '@elementor/ui/Radio'; import RadioGroup from '@elementor/ui/RadioGroup'; import Typography from '@elementor/ui/Typography'; +import { AccessibilityControlsIcon, AccessibilityEyeIcon, AccessibilityPersonIcon, AccessibilityTextIcon } from '@ea11y/icons'; import { useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { AccessibilityControlsIcon, AccessibilityEyeIcon, AccessibilityPersonIcon, AccessibilityTextIcon } from '../../icons'; const IconSelect = ( props ) => { const [ selectedValue, setSelectedValue ] = useState( 'person' ); - const optionStyle = { color: 'info.main', fontSize: 44 }; + const optionStyle = { + color: 'info.main', fontSize: 44, + }; const options = [ - { value: 'person', icon: , label: __( 'Accessibility Person Icon', 'pojo-accessibility' ) }, - { value: 'eye', icon: , label: __( 'Accessibility Eye Icon', 'pojo-accessibility' ) }, - { value: 'text', icon: , label: __( 'Accessibility Text Badge Icon', 'pojo-accessibility' ) }, - { value: 'controls', icon: , label: __( 'Accessibility Controls Slider Icon', 'pojo-accessibility' ) }, + { + value: 'person', icon: , label: __( 'Accessibility Person Icon', 'pojo-accessibility' ), + }, + { + value: 'eye', icon: , label: __( 'Accessibility Eye Icon', 'pojo-accessibility' ), + }, + { + value: 'text', icon: , label: __( 'Accessibility Text Badge Icon', 'pojo-accessibility' ), + }, + { + value: 'controls', icon: , label: __( 'Accessibility Controls Slider Icon', 'pojo-accessibility' ), + }, ]; return ( @@ -59,7 +69,10 @@ const IconSelect = ( props ) => { cursor: 'pointer', } } >{ option.icon } - + ) ) } diff --git a/modules/settings/assets/js/components/icon-size/index.js b/modules/settings/assets/js/components/icon-size/index.js index f8114d5..7f041a8 100644 --- a/modules/settings/assets/js/components/icon-size/index.js +++ b/modules/settings/assets/js/components/icon-size/index.js @@ -4,9 +4,9 @@ import Paper from '@elementor/ui/Paper'; import Radio from '@elementor/ui/Radio'; import RadioGroup from '@elementor/ui/RadioGroup'; import Typography from '@elementor/ui/Typography'; +import { AccessibilityEyeIcon, AccessibilityPersonIcon, AccessibilityTextIcon } from '@ea11y/icons'; import { useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { AccessibilityEyeIcon, AccessibilityPersonIcon, AccessibilityTextIcon } from '../../icons'; const IconSize = ( props ) => { const [ selectedValue, setSelectedValue ] = useState( 'medium' ); diff --git a/modules/settings/assets/js/components/my-account-menu/index.js b/modules/settings/assets/js/components/my-account-menu/index.js index 2ec1de2..8e39d5d 100644 --- a/modules/settings/assets/js/components/my-account-menu/index.js +++ b/modules/settings/assets/js/components/my-account-menu/index.js @@ -9,12 +9,12 @@ import Menu from '@elementor/ui/Menu'; import MenuItem from '@elementor/ui/MenuItem'; import Typography from '@elementor/ui/Typography'; import { bindMenu, bindTrigger, usePopupState } from '@elementor/ui/usePopupState'; -import { __ } from '@wordpress/i18n'; -import { useSettings } from '../../hooks'; +import { useSettings } from '@ea11y/hooks'; import { CreditCardIcon, UserArrowIcon, -} from '../../icons'; +} from '@ea11y/icons'; +import { __ } from '@wordpress/i18n'; const MyAccountMenu = () => { const { openSidebar } = useSettings(); diff --git a/modules/settings/assets/js/components/notifications/index.js b/modules/settings/assets/js/components/notifications/index.js index 367ee78..6939475 100644 --- a/modules/settings/assets/js/components/notifications/index.js +++ b/modules/settings/assets/js/components/notifications/index.js @@ -1,6 +1,6 @@ import Alert from '@elementor/ui/Alert'; import Snackbar from '@elementor/ui/Snackbar'; -import { useNotificationSettings } from '../../hooks'; +import { useNotificationSettings } from '@ea11y/hooks'; const Notifications = ( { type, message } ) => { const { diff --git a/modules/settings/assets/js/components/sidebar-app-bar/index.js b/modules/settings/assets/js/components/sidebar-app-bar/index.js index b7ceced..516ebb2 100644 --- a/modules/settings/assets/js/components/sidebar-app-bar/index.js +++ b/modules/settings/assets/js/components/sidebar-app-bar/index.js @@ -3,12 +3,9 @@ import Box from '@elementor/ui/Box'; import IconButton from '@elementor/ui/IconButton'; import Toolbar from '@elementor/ui/Toolbar'; import Typography from '@elementor/ui/Typography'; +import { useSettings } from '@ea11y/hooks'; +import { ElementorLogo, SquareRoundedChevronsLeft } from '@ea11y/icons'; import { __ } from '@wordpress/i18n'; -import { useSettings } from '../../hooks'; -import { - ElementorLogo, - SquareRoundedChevronsLeft, -} from '../../icons'; const SidebarAppBar = () => { const { openSidebar, setOpenSidebar } = useSettings(); diff --git a/modules/settings/assets/js/components/sidebar-menu/index.js b/modules/settings/assets/js/components/sidebar-menu/index.js index 52d2442..5c9eb01 100644 --- a/modules/settings/assets/js/components/sidebar-menu/index.js +++ b/modules/settings/assets/js/components/sidebar-menu/index.js @@ -4,8 +4,8 @@ import ListItem from '@elementor/ui/ListItem'; import ListItemButton from '@elementor/ui/ListItemButton'; import ListItemIcon from '@elementor/ui/ListItemIcon'; import ListItemText from '@elementor/ui/ListItemText'; +import { useSettings } from '@ea11y/hooks'; import { useState } from '@wordpress/element'; -import { useSettings } from '../../hooks'; import { MenuItems } from './menu'; const SidebarMenu = () => { diff --git a/modules/settings/assets/js/components/sidebar-menu/menu.js b/modules/settings/assets/js/components/sidebar-menu/menu.js index 81a8f07..9f82b18 100644 --- a/modules/settings/assets/js/components/sidebar-menu/menu.js +++ b/modules/settings/assets/js/components/sidebar-menu/menu.js @@ -1,6 +1,6 @@ import { PagesIcon } from '@elementor/icons'; +import { WidgetIcon } from '@ea11y/icons'; import { __ } from '@wordpress/i18n'; -import { WidgetIcon } from '../../icons'; import { AccessibilityStatement, Menu, IconSettings } from '../../pages'; export const MenuItems = { diff --git a/modules/settings/assets/js/layouts/icon-design-settings.js b/modules/settings/assets/js/layouts/icon-design-settings.js index d0be542..3390d1d 100644 --- a/modules/settings/assets/js/layouts/icon-design-settings.js +++ b/modules/settings/assets/js/layouts/icon-design-settings.js @@ -1,8 +1,8 @@ import Box from '@elementor/ui/Box'; import Grid from '@elementor/ui/Grid'; import Typography from '@elementor/ui/Typography'; +import { ColorPicker, IconSelect, IconSize } from '@ea11y/components'; import { __ } from '@wordpress/i18n'; -import { ColorPicker, IconSelect, IconSize } from '../components'; const IconDesignSettings = () => { return ( diff --git a/modules/settings/assets/js/layouts/position-settings-desktop.js b/modules/settings/assets/js/layouts/position-settings-desktop.js index ab129ba..52ef005 100644 --- a/modules/settings/assets/js/layouts/position-settings-desktop.js +++ b/modules/settings/assets/js/layouts/position-settings-desktop.js @@ -2,9 +2,9 @@ import Box from '@elementor/ui/Box'; import FormControlLabel from '@elementor/ui/FormControlLabel'; import Switch from '@elementor/ui/Switch'; import Typography from '@elementor/ui/Typography'; +import { AlignmentMatrixControl, PositionControl } from '@ea11y/components'; import { useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { AlignmentMatrixControl, PositionControl } from '../components'; const PositionSettingsDesktop = () => { const [ hiddenOnDesktop, setHiddenOnDesktop ] = useState( false ); diff --git a/modules/settings/assets/js/layouts/position-settings.js b/modules/settings/assets/js/layouts/position-settings.js index 8574cbe..56f5292 100644 --- a/modules/settings/assets/js/layouts/position-settings.js +++ b/modules/settings/assets/js/layouts/position-settings.js @@ -6,9 +6,9 @@ import TabPanel from '@elementor/ui/TabPanel'; import Tabs from '@elementor/ui/Tabs'; import Typography from '@elementor/ui/Typography'; import useTabs from '@elementor/ui/useTabs'; +import { PositionSettingsDesktop } from '@ea11y/layouts'; import { useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { PositionSettingsDesktop } from '../layouts'; const TABS = { one: 'one', diff --git a/modules/settings/assets/js/layouts/sidebar.js b/modules/settings/assets/js/layouts/sidebar.js index aaf462a..1f81610 100644 --- a/modules/settings/assets/js/layouts/sidebar.js +++ b/modules/settings/assets/js/layouts/sidebar.js @@ -1,7 +1,7 @@ import Box from '@elementor/ui/Box'; import Drawer from '@elementor/ui/Drawer'; -import { MyAccountMenu, SidebarAppBar, SidebarMenu } from '../components'; -import { useSettings } from '../hooks'; +import { MyAccountMenu, SidebarAppBar, SidebarMenu } from '@ea11y/components'; +import { useSettings } from '@ea11y/hooks'; export const Sidebar = () => { const { openSidebar } = useSettings(); diff --git a/modules/settings/assets/js/pages/accessibility-statement.js b/modules/settings/assets/js/pages/accessibility-statement.js index 1146e3c..c4a0c85 100644 --- a/modules/settings/assets/js/pages/accessibility-statement.js +++ b/modules/settings/assets/js/pages/accessibility-statement.js @@ -1,3 +1,5 @@ -export const AccessibilityStatement = () => { +const AccessibilityStatement = () => { return (

AccessibilityStatement

); }; + +export default AccessibilityStatement; diff --git a/modules/settings/assets/js/pages/icon-settings.js b/modules/settings/assets/js/pages/icon-settings.js index bfb5556..bb8bb85 100644 --- a/modules/settings/assets/js/pages/icon-settings.js +++ b/modules/settings/assets/js/pages/icon-settings.js @@ -1,3 +1,5 @@ -export const IconSettings = () => { +const IconSettings = () => { return (

IconSettings

); }; + +export default IconSettings; diff --git a/modules/settings/assets/js/pages/index.js b/modules/settings/assets/js/pages/index.js index 142329d..e2531a2 100644 --- a/modules/settings/assets/js/pages/index.js +++ b/modules/settings/assets/js/pages/index.js @@ -1,3 +1,3 @@ -export { AccessibilityStatement } from './accessibility-statement'; -export { IconSettings } from './icon-settings'; -export { Menu } from './menu'; +export { default as AccessibilityStatement } from './accessibility-statement'; +export { default as IconSettings } from './icon-settings'; +export { default as Menu } from './menu'; diff --git a/modules/settings/assets/js/pages/menu.js b/modules/settings/assets/js/pages/menu.js index f4bbf45..2b618ce 100644 --- a/modules/settings/assets/js/pages/menu.js +++ b/modules/settings/assets/js/pages/menu.js @@ -1,3 +1,5 @@ -export const Menu = () => { +const Menu = () => { return (

Menu

); }; + +export default Menu; diff --git a/package-lock.json b/package-lock.json index 6f980a2..aee1775 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,10 @@ "@wordpress/date": "^5.10.0", "@wordpress/element": "^6.10.0", "@wordpress/i18n": "^5.10.0", - "@wordpress/url": "^4.10.0" + "@wordpress/url": "^4.10.0", + "husky": "^9.1.6", + "prop-types": "^15.8.1", + "react-colorful": "^5.6.1" }, "devDependencies": { "@eslint/js": "^9.13.0", @@ -25,6 +28,7 @@ "@wordpress/eslint-plugin": "^21.3.0", "@wordpress/scripts": "^30.3.0", "eslint": "^8.57.1", + "eslint-import-resolver-webpack": "^0.13.9", "eslint-plugin-babel": "^5.3.1", "eslint-plugin-import": "^2.31.0", "eslint-plugin-jsx-a11y": "^6.10.1", @@ -10782,6 +10786,104 @@ "ms": "^2.1.1" } }, + "node_modules/eslint-import-resolver-webpack": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-webpack/-/eslint-import-resolver-webpack-0.13.9.tgz", + "integrity": "sha512-yGngeefNiHXau2yzKKs2BNON4HLpxBabY40BGL/vUSKZtqzjlVsTTZm57jhHULhm+mJEwKsEIIN3NXup5AiiBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "enhanced-resolve": "^0.9.1", + "find-root": "^1.1.0", + "hasown": "^2.0.0", + "interpret": "^1.4.0", + "is-core-module": "^2.13.1", + "is-regex": "^1.1.4", + "lodash": "^4.17.21", + "resolve": "^2.0.0-next.5", + "semver": "^5.7.2" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "eslint-plugin-import": ">=1.4.0", + "webpack": ">=1.11.0" + } + }, + "node_modules/eslint-import-resolver-webpack/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-webpack/node_modules/enhanced-resolve": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz", + "integrity": "sha512-kxpoMgrdtkXZ5h0SeraBS1iRntpTpQ3R8ussdb38+UAFnMGX5DDyJXePm+OCHOcoXvHDw7mc2erbJBpDnl7TPw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.2.0", + "tapable": "^0.1.8" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/eslint-import-resolver-webpack/node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/eslint-import-resolver-webpack/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-import-resolver-webpack/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/eslint-import-resolver-webpack/node_modules/tapable": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.1.10.tgz", + "integrity": "sha512-jX8Et4hHg57mug1/079yitEKWGB3LCwoxByLsNim89LABq8NqgiX+6iYVOsq0vX8uJHkU+DZ5fnq95f800bEsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, "node_modules/eslint-module-utils": { "version": "2.12.0", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", @@ -15812,6 +15914,13 @@ "resolved": "https://registry.npmjs.org/memize/-/memize-2.1.0.tgz", "integrity": "sha512-yywVJy8ctVlN5lNPxsep5urnZ6TTclwPEyigM9M3Bi8vseJBOfqNrGWN/r8NzuIt3PovM323W04blJfGQfQSVg==" }, + "node_modules/memory-fs": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.2.0.tgz", + "integrity": "sha512-+y4mDxU4rvXXu5UDSGCGNiesFmwCHuefGMoPCO1WYucNYj7DsLqrFaa2fXVI0H+NNiPTwwzKwspn9yTZqUGqng==", + "dev": true, + "license": "MIT" + }, "node_modules/meow": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", diff --git a/package.json b/package.json index 5d8b204..e8767b2 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@wordpress/eslint-plugin": "^21.3.0", "@wordpress/scripts": "^30.3.0", "eslint": "^8.57.1", + "eslint-import-resolver-webpack": "^0.13.9", "eslint-plugin-babel": "^5.3.1", "eslint-plugin-import": "^2.31.0", "eslint-plugin-jsx-a11y": "^6.10.1", @@ -42,7 +43,7 @@ "@wordpress/i18n": "^5.10.0", "@wordpress/url": "^4.10.0", "husky": "^9.1.6", - "react-colorful": "^5.6.1", - "prop-types": "^15.8.1" + "prop-types": "^15.8.1", + "react-colorful": "^5.6.1" } } diff --git a/webpack.config.js b/webpack.config.js index b40d4aa..ae6ffc0 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -17,4 +17,14 @@ module.exports = { ...defaultConfig.output, path: path.resolve( process.cwd(), 'assets/build' ), }, + resolve: { + alias: { + '@ea11y/hooks': path.resolve( __dirname, 'modules/settings/assets/js/hooks/' ), + '@ea11y/components': path.resolve( __dirname, 'modules/settings/assets/js/components/' ), + '@ea11y/icons': path.resolve( __dirname, 'modules/settings/assets/js/icons/' ), + '@ea11y/layouts': path.resolve( __dirname, 'modules/settings/assets/js/layouts/' ), + '@ea11y/pages': path.resolve( __dirname, 'modules/settings/assets/js/pages/' ), + }, + extensions: [ '.js', '.jsx' ], + }, }; From 8bc480fcebfa1982510448b7ee1b338a1d753f39 Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Mon, 25 Nov 2024 21:00:30 +0530 Subject: [PATCH 61/65] fix: height and styling of the layout --- modules/settings/assets/css/style.css | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/modules/settings/assets/css/style.css b/modules/settings/assets/css/style.css index fa53674..988c40e 100644 --- a/modules/settings/assets/css/style.css +++ b/modules/settings/assets/css/style.css @@ -12,23 +12,13 @@ body { html:not([dir='rtl']) #ea11y-app { margin-left: -20px; background: white; - height: 97vh; + height: calc(100vh - 32px); } html[dir='rtl'] #ea11y-app { margin-right: -20px; background: white; - height: 97vh; -} - -html:not([dir='rtl']) #ea11y-app-top-bar { - margin-left: -20px; - margin-bottom: 20px; -} - -html[dir='rtl'] #ea11y-app-top-bar { - margin-right: -20px; - margin-bottom: 20px; + height: calc(100vh - 32px); } #ea11y-app * { From 27177fb35333d47a366e2b757552b1ea857be4af Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Mon, 25 Nov 2024 21:01:06 +0530 Subject: [PATCH 62/65] fix: unhide wp footer --- modules/settings/assets/css/style.css | 3 --- 1 file changed, 3 deletions(-) diff --git a/modules/settings/assets/css/style.css b/modules/settings/assets/css/style.css index 988c40e..8fa3d9b 100644 --- a/modules/settings/assets/css/style.css +++ b/modules/settings/assets/css/style.css @@ -2,9 +2,6 @@ body { background: #fff; } -#wpfooter { - display: none; -} .wrap { margin-top: 0; } From 8d2565f447c7c2485e8bed4f79a2aae7ac7dad35 Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Mon, 25 Nov 2024 21:08:33 +0530 Subject: [PATCH 63/65] update: keep widget menu open on page load (default) --- modules/settings/assets/js/components/sidebar-menu/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/settings/assets/js/components/sidebar-menu/index.js b/modules/settings/assets/js/components/sidebar-menu/index.js index 5c9eb01..9565437 100644 --- a/modules/settings/assets/js/components/sidebar-menu/index.js +++ b/modules/settings/assets/js/components/sidebar-menu/index.js @@ -10,7 +10,7 @@ import { MenuItems } from './menu'; const SidebarMenu = () => { const { openSidebar, selectedMenu, setSelectedMenu } = useSettings(); - const [ expandedItems, setExpandedItems ] = useState( {} ); + const [ expandedItems, setExpandedItems ] = useState( { widget: true } ); const handleSelectedMenu = ( parentKey, childKey ) => { if ( childKey ) { From eb92d9eaaa7ce279d2175ac74925db14a111d9c0 Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Tue, 26 Nov 2024 20:25:20 +0530 Subject: [PATCH 64/65] update: linter rules to move first prop to new line --- .eslintrc | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc b/.eslintrc index a71f548..e17cfe0 100644 --- a/.eslintrc +++ b/.eslintrc @@ -37,6 +37,7 @@ "no-console": "off", "react-hooks/exhaustive-deps": "off", "react/jsx-max-props-per-line": [1, { "maximum": { "single": 2, "multi": 1 } }], + "react/jsx-first-prop-new-line": ["error", "multiline"], "strict": [ "error", "global" ], "curly": "warn", "import/order": [ From 435ab3d82e6da873ee9809ddc1e2ba7f0eac242b Mon Sep 17 00:00:00 2001 From: Nirbhay Date: Tue, 26 Nov 2024 20:25:27 +0530 Subject: [PATCH 65/65] update: linter rules to move first prop to new line --- modules/settings/assets/js/app.js | 6 +- .../js/components/admin-top-bar/index.js | 6 +- .../alignment-matrix-control/index.js | 6 +- .../assets/js/components/bottom-bar/index.js | 3 +- .../js/components/color-picker/index.js | 9 +- .../connect-modal/connect-modal-icon.js | 96 ++++++++++++------- .../js/components/connect-modal/index.js | 9 +- .../assets/js/components/icon-select/index.js | 3 +- .../js/components/my-account-menu/index.js | 3 +- .../js/components/notifications/index.js | 3 +- .../js/components/position-control/index.js | 3 +- .../js/components/sidebar-app-bar/index.js | 6 +- .../js/components/sidebar-menu/index.js | 6 +- .../assets/js/icons/accessibility-controls.js | 6 +- .../assets/js/icons/accessibility-eye.js | 9 +- .../assets/js/icons/accessibility-person.js | 6 +- .../assets/js/icons/accessibility-text.js | 9 +- .../settings/assets/js/icons/credit-card.js | 6 +- .../assets/js/icons/elementor-logo.js | 3 +- .../js/icons/square-rounded-chevrons-left.js | 3 +- .../settings/assets/js/icons/user-arrow.js | 6 +- modules/settings/assets/js/icons/widget.js | 9 +- .../assets/js/layouts/icon-design-settings.js | 9 +- .../js/layouts/position-settings-desktop.js | 9 +- .../assets/js/layouts/position-settings.js | 3 +- modules/settings/assets/js/layouts/sidebar.js | 3 +- 26 files changed, 160 insertions(+), 80 deletions(-) diff --git a/modules/settings/assets/js/app.js b/modules/settings/assets/js/app.js index b794297..1a10f88 100644 --- a/modules/settings/assets/js/app.js +++ b/modules/settings/assets/js/app.js @@ -23,11 +23,13 @@ const App = () => { { ! isConnected && } - - { borderBottom: '1px solid rgba(0, 0, 0, 0.12)' }; return ( - - { { __( 'Default Position', 'pojo-accessibility' ) } - { width: '100px', } } > - { options.map( ( option, i ) => } /> ) } diff --git a/modules/settings/assets/js/components/bottom-bar/index.js b/modules/settings/assets/js/components/bottom-bar/index.js index 1e27212..5ecc9ec 100644 --- a/modules/settings/assets/js/components/bottom-bar/index.js +++ b/modules/settings/assets/js/components/bottom-bar/index.js @@ -4,7 +4,8 @@ import { __ } from '@wordpress/i18n'; export const BottomBar = () => { return ( - { { __( 'Color', 'pojo-accessibility' ) } - { className="widget-settings-color-picker" /> - - { > - { - - - - - - - - - - - - - { strokeLinejoin="round" /> - - - - - - - - - - - - - - - - - - diff --git a/modules/settings/assets/js/components/connect-modal/index.js b/modules/settings/assets/js/components/connect-modal/index.js index 1179e4d..51ea0df 100644 --- a/modules/settings/assets/js/components/connect-modal/index.js +++ b/modules/settings/assets/js/components/connect-modal/index.js @@ -12,7 +12,8 @@ function ConnectModal() { return ( - - { __( 'Connect plugin on your site!', 'pojo-accessibility' ) } -