get_static( 'number_of_plural_translations' ) - 1 ) as $i ) { if ( isset( $args['translations'][ $i ] ) ) { $args[ "translation_$i" ] = $args['translations'][ $i ]; } } unset( $args['translations'] ); } // Reduce range by one since we're starting at 0, see GH#516. foreach ( range( 0, $this->get_static( 'number_of_plural_translations' ) - 1 ) as $i ) { if ( isset( $args[ "translation_$i" ] ) ) { $args[ "translation_$i" ] = $args[ "translation_$i" ]; } } if ( gp_array_get( $args, 'warnings' ) == array() ) { $args['warnings'] = null; } return $args; } public function prepare_fields_for_save( $args ) { $args = parent::prepare_fields_for_save( $args ); /** * Filters the translation fields before they are saved. * * @since 3.0.0 * * @param array $args Translation arguments. * @param GP_Translation $this Translation class. */ $args = apply_filters( 'gp_translation_prepare_for_save', $args, $this ); if ( is_array( gp_array_get( $args, 'warnings' ) ) ) { $args['warnings'] = serialize( $args['warnings'] ); } return $args; } /** * Sets restriction rules for fields. * * @since 1.0.0 * * @param GP_Validation_Rules $rules The validation rules instance. */ public function restrict_fields( $rules ) { $rules->translation_0_should_not_be( 'empty_string' ); $rules->translation_1_should_not_be( 'empty_string' ); $rules->translation_2_should_not_be( 'empty_string' ); $rules->translation_3_should_not_be( 'empty_string' ); $rules->translation_4_should_not_be( 'empty_string' ); $rules->translation_5_should_not_be( 'empty_string' ); $rules->status_should_not_be( 'empty' ); $rules->original_id_should_be( 'positive_int' ); $rules->translation_set_id_should_be( 'positive_int' ); $rules->user_id_should_be( 'positive_int' ); $rules->user_id_last_modified_should_not_be( 'empty_string' ); } /** * Sets fields of the current GP_Thing object. * * @param array $fields Fields for a GP_Thing object. */ public function set_fields( $fields ) { parent::set_fields( $fields ); if ( $this->warnings ) { $this->warnings = maybe_unserialize( $this->warnings ); } } public function for_export( $project, $translation_set, $filters = null ) { return GP::$translation->for_translation( $project, $translation_set, 'no-limit', $filters ? $filters : array( 'status' => 'current_or_untranslated' ) ); } public function for_translation( $project, $translation_set, $page, $filters = array(), $sort = array() ) { global $wpdb; $locale = GP_Locales::by_slug( $translation_set->locale ); $join_type = 'INNER'; $sort_bys = wp_list_pluck( gp_get_sort_by_fields(), 'sql_sort_by' ); $default_sort = get_user_option( 'gp_default_sort' ); if ( ! is_array( $default_sort ) ) { $default_sort = array( 'by' => 'priority', 'how' => 'desc', ); } $sort_by = gp_array_get( $sort_bys, gp_array_get( $sort, 'by' ), gp_array_get( $sort_bys, $default_sort['by'] ) ); $sort_hows = array( 'asc' => 'ASC', 'desc' => 'DESC', ); $sort_how = gp_array_get( $sort_hows, gp_array_get( $sort, 'how' ), gp_array_get( $sort_hows, $default_sort['how'] ) ); $collation = 'yes' === gp_array_get( $filters, 'case_sensitive' ) ? 'BINARY' : ''; $where = array(); if ( gp_array_get( $filters, 'term' ) ) { $like = "LIKE $collation '%" . ( esc_sql( $wpdb->esc_like( gp_array_get( $filters, 'term' ) ) ) ) . "%'"; $term_scope = gp_array_get( $filters, 'term_scope', 'scope_any' ); switch ( $term_scope ) { case 'scope_originals': $scope_array = array( 'o.singular', 'o.plural' ); break; case 'scope_translations': $scope_array = array( 't.translation_0', 't.translation_1' ); break; case 'scope_context': $scope_array = array( 'o.context' ); break; case 'scope_references': $scope_array = array( 'o.references' ); break; case 'scope_both': $scope_array = array( 'o.singular', 't.translation_0', 'o.plural', 't.translation_1' ); break; default: $scope_array = array( 'o.singular', 't.translation_0', 'o.plural', 't.translation_1', 'o.context', 'o.references' ); break; } $mapped_scope_array = array_map( function( $x ) use ( $like ) { return "($x $like)"; }, $scope_array ); $where[] = '(' . implode( ' OR ', $mapped_scope_array ) . ')'; } if ( gp_array_get( $filters, 'before_date_added' ) ) { $where[] = $wpdb->prepare( 't.date_added > %s', gp_array_get( $filters, 'before_date_added' ) ); } if ( gp_array_get( $filters, 'translation_id' ) ) { $where[] = $wpdb->prepare( 't.id = %d', gp_array_get( $filters, 'translation_id' ) ); } if ( gp_array_get( $filters, 'original_id' ) ) { $where[] = $wpdb->prepare( 'o.id = %d', gp_array_get( $filters, 'original_id' ) ); } if ( 'yes' == gp_array_get( $filters, 'warnings' ) ) { $where[] = 't.warnings IS NOT NULL'; $where[] = 't.warnings != ""'; } elseif ( 'no' == gp_array_get( $filters, 'warnings' ) ) { $where[] = 't.warnings IS NULL'; } if ( 'yes' == gp_array_get( $filters, 'with_context' ) ) { $where[] = 'o.context IS NOT NULL'; } if ( 'yes' == gp_array_get( $filters, 'with_comment' ) ) { $where[] = 'o.comment IS NOT NULL AND o.comment <> ""'; } if ( 'yes' === gp_array_get( $filters, 'with_plural' ) ) { $where[] = 'o.plural IS NOT NULL'; } if ( gp_array_get( $filters, 'user_login' ) ) { $user = get_user_by( 'login', $filters['user_login'] ); // do not return any entries if the user doesn't exist $where[] = $wpdb->prepare( 't.user_id = %d', ( $user && $user->ID ) ? $user->ID : -1 ); } if ( ! GP::$permission->current_user_can( 'write', 'project', $project->id ) ) { $where[] = 'o.priority > -2'; } $priorities = gp_array_get( $filters, 'priority' ); if ( $priorities ) { $valid_priorities = array_keys( GP::$original->get_static( 'priorities' ) ); $priorities = array_filter( gp_array_get( $filters, 'priority' ), function( $p ) use ( $valid_priorities ) { return in_array( intval( $p ), $valid_priorities, true ); } ); $priorities_where = array(); foreach ( $priorities as $single_priority ) { $priorities_where[] = $wpdb->prepare( 'o.priority = %s', $single_priority ); } if ( ! empty( $priorities_where ) ) { $priorities_where = '(' . implode( ' OR ', $priorities_where ) . ')'; $where[] = $priorities_where; } }; $join_on = array(); $status = gp_array_get( $filters, 'status', 'current_or_waiting_or_fuzzy_or_untranslated' ); $statuses = explode( '_or_', $status ); if ( in_array( 'untranslated', $statuses, true ) ) { if ( array( 'untranslated' ) == $statuses ) { $where[] = 't.translation_0 IS NULL'; } $join_type = 'LEFT'; $join_on[] = "t.status != 'rejected'"; $join_on[] = "t.status != 'old'"; $statuses = array_filter( $statuses, function( $x ) { return 'untranslated' !== $x; } ); } $all_statuses = $this->get_static( 'statuses' ); $statuses = array_filter( $statuses, function( $s ) use ( $all_statuses ) { return in_array( $s, $all_statuses, true ); } ); if ( ! empty( $statuses ) ) { $statuses_where = array(); foreach ( $statuses as $single_status ) { $statuses_where[] = $wpdb->prepare( 't.status = %s', $single_status ); } $statuses_where = '(' . implode( ' OR ', $statuses_where ) . ')'; $join_on[] = $statuses_where; } /** * Filter the SQL WHERE clause to get available translations. * * @since 1.0.0 * * @param array $where An array of where conditions. * @param GP_Translation_Set $translation_set Current translation set. */ $where = apply_filters( 'gp_for_translation_where', $where, $translation_set ); $where = implode( ' AND ', $where ); if ( $where ) { $where = 'AND ' . $where; } $where = 'o.project_id = ' . (int) $project->id . " AND o.status = '+active' " . $where; $join_on = implode( ' AND ', $join_on ); if ( $join_on ) { $join_on = 'AND ' . $join_on; } $fields = array( 't.*', 'o.*', 't.id as id', 'o.id as original_id', 't.status as translation_status', 'o.status as original_status', 't.date_added as translation_added', 'o.date_added as original_added', ); $join = "$join_type JOIN {$wpdb->gp_translations} AS t ON o.id = t.original_id AND t.translation_set_id = " . (int) $translation_set->id; $orderby = sprintf( $sort_by, $sort_how ); $limit = $this->sql_limit_for_paging( $page, $this->per_page ); /** * Filters the 'for_translation' query SQL clauses. * * @since 2.3.0 * * @param array $pieces { * Translation query SQL clauses. * * @type array $fields Fields to select in the query. * @type string $join JOIN clause of the query. * @type string $join_on Conditions for the JOIN clause. * @type string $where WHERE clause of the query. * @type string $orderby Fields for ORDER BY clause. * @type string $limit LIMIT clause of the query. * } * @param GP_Translation_Set $translation_set The translation set object being queried. * @param array $filters An array of search filters. * @param array $sort An array of sort settings. */ $clauses = apply_filters( 'gp_for_translation_clauses', compact( 'fields', 'join', 'join_on', 'where', 'orderby', 'limit' ), $translation_set, $filters, $sort ); $fields = isset( $clauses['fields'] ) ? implode( ', ', $clauses['fields'] ) : '*'; $join = isset( $clauses['join'] ) ? $clauses['join'] : ''; $join_on = isset( $clauses['join_on'] ) ? $clauses['join_on'] : ''; $where = isset( $clauses['where'] ) ? $clauses['where'] : ''; $orderby = isset( $clauses['orderby'] ) ? 'ORDER BY ' . $clauses['orderby'] : ''; $limit = isset( $clauses['limit'] ) ? $clauses['limit'] : ''; $sql_for_translations = " SELECT SQL_CALC_FOUND_ROWS $fields FROM {$wpdb->gp_originals} as o $join $join_on WHERE $where $orderby $limit"; $rows = $this->many_no_map( $sql_for_translations ); /** * Filters the rows returned from the for_translation query. * * @since 2.3.0 * * @param array $rows Array of arrays returned by the query. * @param GP_Translation_Set $translation_set The translation set object being queried. */ $rows = apply_filters( 'gp_for_translation_rows', $rows, $translation_set ); $this->found_rows = $this->found_rows(); $translations = array(); foreach ( (array) $rows as $row ) { $row->user = $row->user_last_modified = null; if ( $row->user_id && 'no-limit' !== $this->per_page ) { $user = get_userdata( $row->user_id ); if ( $user ) { $row->user = (object) array( 'ID' => $user->ID, 'user_login' => $user->user_login, 'display_name' => $user->display_name, 'user_nicename' => $user->user_nicename, ); } } if ( $row->user_id_last_modified && 'no-limit' !== $this->per_page ) { $user = get_userdata( $row->user_id_last_modified ); if ( $user ) { $row->user_last_modified = (object) array( 'ID' => $user->ID, 'user_login' => $user->user_login, 'display_name' => $user->display_name, 'user_nicename' => $user->user_nicename, ); } } $row->translations = array(); for ( $i = 0; $i < $locale->nplurals; $i++ ) { $row->translations[] = $row->{'translation_' . $i}; } $row->references = preg_split( '/\s+/', $row->references, -1, PREG_SPLIT_NO_EMPTY ); $row->extracted_comments = $row->comment; $row->warnings = $row->warnings ? maybe_unserialize( $row->warnings ) : null; unset( $row->comment ); // Reduce range by one since we're starting at 0, see GH#516. foreach ( range( 0, $this->get_static( 'number_of_plural_translations' ) - 1 ) as $i ) { $member = "translation_$i"; unset( $row->$member ); } $row->row_id = $row->original_id . ( $row->id ? "-$row->id" : '' ); if ( '0' !== $row->priority ) { $row->flags = array( 'gp-priority: ' . GP_Original::$priorities[ $row->priority ], ); } $translations[] = new Translation_Entry( (array) $row ); } unset( $rows ); return $translations; } public function set_as_current() { $result = $this->update( array( 'status' => 'old' ), array( 'original_id' => $this->original_id, 'translation_set_id' => $this->translation_set_id, 'status' => 'current', ) ) && $this->update( array( 'status' => 'old' ), array( 'original_id' => $this->original_id, 'translation_set_id' => $this->translation_set_id, 'status' => 'waiting', ) ) && $this->update( array( 'status' => 'old' ), array( 'original_id' => $this->original_id, 'translation_set_id' => $this->translation_set_id, 'status' => 'fuzzy', ) ) && $this->save( array( 'status' => 'current', 'user_id_last_modified' => get_current_user_id(), ) ); return $result; } public function reject() { $this->set_status( 'rejected' ); } /** * Decides whether the status of a translation can be changed to a desired status. * * @since 2.3.0 * * @param string $desired_status The desired status. * @return bool Whether the status can be set. */ public function can_set_status( $desired_status ) { /** * Filters the decision whether a translation can be set to a status. * * @since 2.3.0 * * @param string|bool $can_set_status Whether the user can set the desired status. * @param GP_Translation $translation The translation to decide this for. * @param string $desired_status The desired status. */ $can_set_status = apply_filters( 'gp_pre_can_set_translation_status', 'no-verdict', $this, $desired_status ); if ( is_bool( $can_set_status ) ) { return $can_set_status; } if ( ! is_bool( $can_set_status ) && 'rejected' === $desired_status && get_current_user_id() === (int) $this->user_id ) { $can_set_status = true; } if ( ! is_bool( $can_set_status ) && ( 'current' === $desired_status || 'rejected' === $desired_status ) ) { if ( ! GP::$permission->current_user_can( 'approve', 'translation', $this->id, array( 'translation' => $this ) ) ) { $can_set_status = false; } } if ( ! is_bool( $can_set_status ) ) { $can_set_status = true; } /** * Filters the decision whether a translation can be set to a status. * * @since 2.3.0 * * @param bool $can_set_status Whether the user can set the desired status. * @param GP_Translation $translation The translation to decide this for. * @param string $desired_status The desired status. */ $can_set_status = apply_filters( 'gp_can_set_translation_status', $can_set_status, $this, $desired_status ); return $can_set_status; } /** * Changes the status of a translation if possible. * * @since 2.3.0 * * @param string $status The status to be set. * @return bool Whether the setting of status was successful. */ public function set_status( $status ) { if ( ! $this->can_set_status( $status ) ) { return false; } if ( 'current' === $status ) { $updated = $this->set_as_current(); } else { $updated = $this->save( array( 'user_id_last_modified' => get_current_user_id(), 'status' => $status, ) ); } if ( $updated ) { gp_clean_translation_set_cache( $this->translation_set_id ); } return $updated; } public function translations() { $translations = array(); // Reduce range by one since we're starting at 0, see GH#516. foreach ( range( 0, $this->get_static( 'number_of_plural_translations' ) - 1 ) as $i ) { $translations[ $i ] = isset( $this->{"translation_$i"} ) ? $this->{"translation_$i"} : null; } return $translations; } /** * Retrieves the last modified date of a translation in a translation set. * * @since 1.0.0 * * @param GP_Translation_Set $translation_set The translation set to retrieve the last modified date. * @return string|false The last modified date on success, false on failure. */ public function last_modified( $translation_set ) { $last_modified = wp_cache_get( $translation_set->id, 'translation_set_last_modified' ); // Cached as "" if no translations. if ( '' === $last_modified ) { return false; } elseif ( false !== $last_modified ) { return $last_modified; } $last_modified = $this->value( "SELECT date_modified FROM {$this->table} WHERE translation_set_id = %d AND status = %s ORDER BY date_modified DESC LIMIT 1", $translation_set->id, 'current' ); wp_cache_set( $translation_set->id, (string) $last_modified, 'translation_set_last_modified' ); return $last_modified; } // Triggers /** * Executes after creating a translation. * * @since 1.0.0 * * @return bool */ public function after_create() { /** * Fires after a translation was created. * * @since 1.0.0 * * @param GP_Translation $translation Translation that was created. */ do_action( 'gp_translation_created', $this ); return true; } /** * Executes after saving a translation. * * @since 1.0.0 * @since 3.0.0 Added the `$original_before` parameter. * * @param GP_Translation $translation_before Translation before the update. * @return bool */ public function after_save( $translation_before ) { /** * Fires after a translation was saved. * * @since 1.0.0 * @since 3.0.0 Added the `$original_before` parameter. * * @param GP_Translation $translation Translation following the update. * @param GP_Translation $translation_before Translation before the update. */ do_action( 'gp_translation_saved', $this, $translation_before ); return true; } /** * Executes after deleting a translation. * * @since 2.0.0 * * @return bool */ public function after_delete() { /** * Fires after a translation was deleted. * * @since 2.0.0 * * @param GP_Translation $translation Translation that was deleted. */ do_action( 'gp_translation_deleted', $this ); return true; } } GP::$translation = new GP_Translation();