Plugin Directory

Changeset 3369834


Ignore:
Timestamp:
09/29/2025 03:16:26 PM (5 months ago)
Author:
suaudeau
Message:

5.1.1

*Release Date - 29 september 2025*

  • BUG : Combinations of privacy options could show the email address in cases where it should have been hidden.
  • BUG : Admin menu - Modify buttons where accidentally inactivated in "table view" tab.
  • CODE : improve coding standard
Location:
mon-laboratoire/trunk
Files:
12 edited

Legend:

Unmodified
Added
Removed
  • mon-laboratoire/trunk/Admin/Forms/class-forms-view.php

    r3361909 r3369834  
    99 *
    1010 * This class provides methods to create various form elements like text fields,
    11  * selects, checkboxes, radio buttons
     11 * selects, checkboxes, and radio buttons.
    1212 *
    1313 * Methods :
     
    3939 *
    4040 * @package MonLabo\Admin\Forms
    41 */
     41 */
    4242class Forms_View extends Generic_Forms_View {
    4343
     
    4545     * Generates HTML code for a generic form field of specified type.
    4646     *
    47      * @param  string   $type_of_field  The unique type/name identifier for the field.
    48      * @param  bool     $is_mandatory   Whether the field is required.
    49      * @param  string   $legend         Label text displayed before the field.
    50      * @param  string   $description    Help text displayed after the field.
    51      * @param  mixed    $initial_value  Initial field value (string|int|null|array).
    52      * @param  string   $css_id         CSS identifier for the field.
    53      * @return string                   Generated HTML for the form field.
     47     * @param string       $type_of_field The unique type/name identifier for the field.
     48     * @param bool         $is_mandatory  Whether the field is required.
     49     * @param string       $legend        Label text displayed before the field.
     50     * @param string       $description   Help text displayed after the field.
     51     * @param mixed $initial_value Initial field value (string|int|null|array).
     52     * @param string       $css_id        CSS identifier for the field.
     53     * @return string Generated HTML for the form field.
    5454     */
    5555    public function field(
     
    6161        string $css_id = ''
    6262    ): string {
    63         if ( is_null( $initial_value ) ) { $initial_value = ''; }
     63        if ( is_null( $initial_value ) ) {
     64            $initial_value = '';
     65        }
     66        $field_name = 'submit_' . $type_of_field;
     67        if( is_array( $initial_value ) ) {
     68            switch ( $type_of_field ) {
     69                case 'wp_post_ids':
     70                    $multipost_forms_view = new Wp_Post_Forms_View();
     71                    return $multipost_forms_view->multi_post_addr_field( $is_mandatory, $legend, $field_name, $description, $initial_value, $css_id );
     72                case 'person_wp_post_ids':
     73                    $multipost_forms_view = new Wp_Post_Forms_View();
     74                    return $multipost_forms_view->person_multi_post_addr_field( $is_mandatory, $legend, 'submit_wp_post_ids', $description, $initial_value, $css_id );
     75                default:
     76                    return ''; //ERROR
     77            }
     78        }
     79
     80        $initial_value = strval( $initial_value );
    6481        switch ( $type_of_field ) {
    6582            case 'descartes_publi_author_id':
    66                 return $this->_number_field( $is_mandatory, $legend, 'submit_' . $type_of_field, $description, strval( $initial_value ), $css_id );
    67             //case 'alternate_image':
     83                return $this->_number_field( $is_mandatory, $legend, $field_name, $description, $initial_value, $css_id );
    6884            case 'external_url':
    69                 return $this->_url_field( $is_mandatory, $legend, 'submit_' . $type_of_field, $description, $initial_value, $css_id );
    70             /*
    71             //Non utilisé
    72             case 'title':
    73                 return generate_text_form_small( $is_mandatory, $legend, 'submit_' . $type_of_field, $description, $initial_value, $css_id );*/
     85                return $this->_url_field( $is_mandatory, $legend, $field_name, $description, $initial_value, $css_id );
    7486            case 'color':
    75                 return $this->_color_field( $is_mandatory, $legend, 'submit_' . $type_of_field, $description, $initial_value, $css_id );
     87                return $this->_color_field( $is_mandatory, $legend, $field_name, $description, $initial_value, $css_id );
    7688            case 'logo':
    77                 return $this->_square_image_selector( 'submit_' . $type_of_field, $description, $initial_value );
     89                return $this->_square_image_selector( $field_name, $description, $initial_value );
    7890            case 'image':
    79                 return $this->_person_image_selector( 'submit_' . $type_of_field, $description, $initial_value );
     91                return $this->_person_image_selector( $field_name, $description, $initial_value );
    8092            case 'adresse':
    8193            case 'external_mentors':
     
    8496            case 'contact':
    8597            case 'contact_alt':
    86                 return $this->_textarea( $is_mandatory, 'submit_' . $type_of_field, $description, $initial_value, $css_id );
     98                return $this->_textarea( $is_mandatory, $field_name, $description, $initial_value, $css_id );
    8799            case 'explicit_disabled':
    88100                return $this->_explicit_disabled_field( $legend, $description );
    89             case 'wp_post_ids':
    90                 $multipost_forms_view = new Wp_Post_Forms_View();
    91                 return $multipost_forms_view->multi_post_addr_field( $is_mandatory, $legend, 'submit_' . $type_of_field, $description, $initial_value, $css_id );
    92             case 'person_wp_post_ids':
    93                 $multipost_forms_view = new Wp_Post_Forms_View();
    94                 return $multipost_forms_view->person_multi_post_addr_field( $is_mandatory, $legend, 'submit_wp_post_ids', $description, $initial_value, $css_id );
    95101            default:
    96                 return $this->_large_text_field( $is_mandatory, $legend, 'submit_' . $type_of_field, $description, $initial_value, $css_id );
     102                return $this->_large_text_field( $is_mandatory, $legend, $field_name, $description, $initial_value, $css_id );
    97103        }
    98104    }
     
    100106    /**
    101107     * Begins a form with proper nonce field.
    102      * @param string $form_id ID of the form
    103      * @return string HTML code
    104      */
    105     public function begin_form( string $form_id ) : string {
     108     *
     109     * @param string $form_id ID of the form.
     110     * @return string HTML code for form opening tag.
     111     */
     112    public function begin_form( string $form_id ): string {
    106113        $tab = sanitize_key( $_GET['tab'] ?? 'tab_adv' );
    107114        $url = add_query_arg(
     
    122129     * @return string HTML code for hidden input fields.
    123130     */
    124     public function silent_transmit_ids( string $form_id, array $table_of_ids ) : string {
     131    public function silent_transmit_ids( string $form_id, array $table_of_ids ): string {
    125132        if ( empty( $table_of_ids ) ) {
    126133            return '';
    127134        }
    128         //Be sure to get a list of int
     135        // Be sure to get a list of int.
    129136        $table_to_encode = array_values( array_map( 'intval', $table_of_ids ) );
    130         //Transmit silently this list
     137        // Transmit silently this list.
    131138        return $this->hidden_field(
    132139            esc_attr( $form_id ) . '_submit_ids',
     
    137144    /**
    138145     * Silently transmits text through a hidden form field.
    139      * @param string $form_id ID of the form
    140      * @param string $text Text to transmit.
     146     *
     147     * @param string $form_id ID of the form.
     148     * @param string $text    Text to transmit.
    141149     * @return string HTML for hidden input field.
    142150     */
    143     public function silent_transmit_text( string $form_id, string $text ) : string {
     151    public function silent_transmit_text( string $form_id, string $text ): string {
    144152        return $this->hidden_field( $form_id . '_submit_text', $text );
    145153    }
     
    147155    /**
    148156     * Retrieves silently transmitted IDs from POST data.
    149      * @param string $post_index ID of the form
    150      * @param int[] $destination structure received from POST
     157     *
     158     * @param string $post_index  ID of the form.
     159     * @param int[]  $destination Reference to destination array.
    151160     * @return void
    152161     */
     
    169178     * @return string HTML code
    170179     */
    171     public function silent_transmit_array_of_struct( string $form_id, array $table_of_struct ) : string {
     180    public function silent_transmit_array_of_struct( string $form_id, array $table_of_struct ): string {
    172181        if ( empty( $table_of_struct ) ) {
    173182            return '';
     
    201210    /**
    202211     * Generates end of form with submit button.
    203      * @param string $form_id ID of the form
    204      * @param string $submit_button_text Text inside the submit button
    205      * @param string $dashicon [OPTIONAL] Code of dashicon to display (https://developer.wordpress.org/resource/dashicons/#plugins-checked)
    206      * @param string $type [OPTIONAL] Type of button as Bootstrap defines (https://getbootstrap.com/docs/4.0/components/buttons/)
    207      * @param bool $disabled [OPTIONAL] Does the button is disabled ?
    208      * @return string HTML code
    209      */
    210     public function end_form( string $form_id, string $submit_button_text, $dashicon = '', $type = 'primary', $disabled = false ) : string {
     212     *
     213     * @param string $form_id            ID of the form.
     214     * @param string $submit_button_text Text inside the submit button.
     215     * @param string $dashicon           Optional. Code of dashicon to display.
     216     * @param string $type               Optional. Type of button (primary, secondary, etc.). Default 'primary'.
     217     * @param bool   $disabled           Optional. Whether the button is disabled. Default false.
     218     * @return string HTML code for form closing.
     219     */
     220    public function end_form( string $form_id, string $submit_button_text, $dashicon = '', $type = 'primary', $disabled = false ): string {
    211221        $output  = wp_nonce_field( $form_id . '_form', $form_id . '_form_wpnonce', true, false );
    212222        $output .= $this->submit_button( $submit_button_text, 'submit_' . $form_id, '', $dashicon, $type, '', '', $disabled );
     
    216226
    217227    /**
    218      * Generate an HTML code for a generic simple choice drop-down list form field
    219      * @param string $name          see _select_generic
    220      * @param array<string|int,string>|array<string|int,array<string,string>> $values see _select_generic
    221      * @param bool $is_mandatory    see _select_generic
    222      * @param string $legend        see _select_generic
    223      * @param string $description   see _select_generic
    224      * @param string|string[]|null $initial_value see _select_generic
    225      * @param string $onchange      see _select_generic
    226      * @param string $prefix prefix to add to name
    227      * @return string HTML code of the drop-down list form field
     228     * Generate a single-choice select dropdown field.
     229     *
     230     * @param string       $name          Name of the dropdown list.
     231     * @param array<string|int,string>|array<string|int,array<string,string>>        $values        Array of values for each option.
     232     * @param bool         $is_mandatory  Whether selection is required.
     233     * @param string       $legend        Label text before the field.
     234     * @param string       $description   Help text after the field.
     235     * @param string|string[]|null $initial_value Pre-selected value(s).
     236     * @param string       $onchange      JavaScript onchange handler.
     237     * @param string       $prefix        Prefix to add to name. Default 'submit_'.
     238     * @return string HTML code for the dropdown field.
    228239     */
    229240    public function select(
     
    241252
    242253    /**
    243      * Generates multi-select dropdown field.
    244      * @param string $name          see _select_generic
    245      * @param array<string|int,string>|array<string|int,array<string,string>> $values see _select_generic
    246      * @param bool $is_mandatory    see _select_generic
    247      * @param string $legend        see _select_generic
    248      * @param string $description   see _select_generic
    249      * @param string|string[]|int|int[]|null $initial_value see _select_generic
    250      * @param string $onchange      see _select_generic
    251      * @param string $prefix prefix to add to name
    252      * @return string HTML code of the drop-down list form field
     254     * Generate a multiple-choice select dropdown field.
     255     *
     256     * @param string       $name          Name of the dropdown list.
     257     * @param array<string|int,string>|array<string|int,array<string,string>>        $values        Array of values for each option.
     258     * @param bool         $is_mandatory  Whether selection is required.
     259     * @param string       $legend        Label text before the field.
     260     * @param string       $description   Help text after the field.
     261     * @param string|string[]|int|int[]|null $initial_value Pre-selected value(s).
     262     * @param string       $onchange      JavaScript onchange handler.
     263     * @param string       $prefix        Prefix to add to name. Default 'submit_'.
     264     * @return string HTML code for the dropdown field.
    253265     */
    254266    public function select_multiple(
     
    266278
    267279    /**
    268      * Generates radio button group.
    269      * @param string $name name of the form field
    270      * @param string[] $values array of value of each radio button
    271      *  Thus each item of the array is 'value' => 'legend'
    272      * @param bool $is_mandatory if true, select a button is mandatory.
    273      * @param string $legend text that is displayed just before the field
    274      * @param string $description text that is displayed after the field
    275      * @param string|null $initial_value Pre-select buttons
    276      * @param string $onchange JavaScript code to execute when changing the
    277      * radio buttons states
    278      * @return string HTML code of the radio buttons form field
     280     * Generate radio button group.
     281     *
     282     * @param string $name          Name of the form field.
     283     * @param string[]  $values        Array of values for each radio button.
     284     * @param bool   $is_mandatory  Whether selection is required.
     285     * @param string $legend        Label text before the field.
     286     * @param string $description   Help text after the field.
     287     * @param string|null $initial_value Pre-selected value.
     288     * @param string $onchange      JavaScript onchange handler.
     289     * @return string HTML code for radio button group.
    279290     */
    280291    public function radio_buttons(
     
    297308                $is_mandatory ? ' required="required"' : '',
    298309                $onchange ? ' onchange="' . esc_js( $onchange ) . '"' : '',
    299                 strlen( $initial_value ) ? checked( $initial_value, $value, false ) :  '',
     310                strlen( $initial_value ) ? checked( $initial_value, $value, false ) : '',
    300311                esc_html( $value )
    301312            );
     
    306317
    307318    /**
    308      * Generate an HTML code for checkboxes form field
    309      * @param string $name name of the form field
    310      * @param string[] $values array of value of each checkbox
    311      *  Thus each item of the array is 'value' => 'legend'
    312      * @param bool $is_mandatory if true, check a box is mandatory.
    313      * @param string $legend text that is displayed just before the field
    314      * @param string $description text that is displayed after the field
    315      * @param string[]|null $initial_value Pre-select box
    316      * @param string $onchange JavaScript code to execute when changing the checkboxes
    317      * state
    318      * @return string HTML code of the checkboxes form field
     319     * Generate checkboxes group.
     320     *
     321     * @param string $name          Name of the form field.
     322     * @param string[]  $values        Array of values for each checkbox.
     323     * @param bool   $is_mandatory  Whether selection is required.
     324     * @param string $legend        Label text before the field.
     325     * @param string $description   Help text after the field.
     326     * @param string[]|null  $initial_value Pre-selected values.
     327     * @param string $onchange      JavaScript onchange handler.
     328     * @return string HTML code for checkboxes group.
    319329     */
    320330    public function checkboxes(
     
    345355
    346356    /**
    347      * Generate an HTML code for a submitting button of a form
    348      * @param string $text id of the person
    349      * @param string $css_id CSS ID of the buttonGENERATION D’ELEMENTS
    350      * @param string $onclick Javascrit code to launch when button is clicked on
    351      * @param string $dashicon Code of dashicon to display (https://developer.wordpress.org/resource/dashicons/#plugins-checked)
    352      * @param string $type Type of button as Bootstrap defines (https://getbootstrap.com/docs/4.0/components/buttons/)
    353      * @param string $name property 'name' of the button
    354      * @param string $class class of the button
    355      * @param bool   $disabled do the button is disabled ?
    356      * @return string HTML code
     357     * Generate submit button.
     358     *
     359     * @param string $text     Button text.
     360     * @param string $css_id   CSS ID for the button.
     361     * @param string $onclick  JavaScript onclick handler.
     362     * @param string $dashicon Dashicon code.
     363     * @param string $type     Button type (primary, secondary, etc.). Default 'primary'.
     364     * @param string $name     Name attribute of the button.
     365     * @param string $class    Additional CSS classes.
     366     * @param bool   $disabled Whether button is disabled. Default false.
     367     * @return string HTML code for submit button.
    357368     */
    358369    public function submit_button(
     
    367378    ): string {
    368379        $button_attrs = array(
    369             'onclick' => $onclick, //already cleaned by esc_js()
    370             'name'  => esc_attr( $name ),
    371             'type'  => 'submit',
     380            'onclick' => $onclick, // Already cleaned by esc_js().
     381            'name'    => esc_attr( $name ),
     382            'type'    => 'submit',
    372383            'class'   => esc_attr( 'btn btn-' . $type . ( $class ? ' ' . $class : '' ) ),
    373             'id'      => esc_attr( $css_id ),
    374         );
    375         // Remove empty attributes
     384            'id'      => esc_attr( $css_id ),
     385        );
     386        // Remove empty attributes.
    376387        $button_attrs = array_filter( $button_attrs );
     388
    377389        if ( $disabled ) {
    378390            $button_attrs['disabled'] = 'disabled';
     
    382394            $attrs_html .= sprintf( ' %s="%s"', $key, $value );
    383395        }
    384         $button_content  = $dashicon ? $this->_html->dashicon( $dashicon ) . '&nbsp;' :  '' ;
     396        $button_content  = $dashicon ? $this->_html->dashicon( $dashicon ) . '&nbsp;' : '';
    385397        $button_content .= wp_kses_post( $text );
    386398        return sprintf( '<button%s>%s</button>', $attrs_html, $button_content );
     
    388400
    389401    /**
    390      * Generate an HTML code for a hidden field
    391      * @param string $name name of the form field
    392      * @param string $value value
    393      * @param string $id id
    394      * @return string HTML code of form field
    395       */
    396       public function hidden_field( string $name, string $value, string $id = '' ): string {
     402     * Generate hidden field.
     403     *
     404     * @param string $name  Name of the field.
     405     * @param string $value Value of the field.
     406     * @param string $id    ID of the field. Default empty.
     407     * @return string HTML code for hidden field.
     408     */
     409    public function hidden_field( string $name, string $value, string $id = '' ): string {
    397410        return sprintf(
    398411            '<input type="hidden" name="%1$s"%3$s value="%2$s" />',
    399412            esc_attr( $name ),
    400413            esc_attr( $value ),
    401             empty( $id ) ? '' : ' id="' . $id . '"'
     414            empty( $id ) ? '' : ' id="' . esc_attr( $id ) . '"'
    402415        );
    403416    }
     
    408421
    409422    /**
    410      * Generates generic select/multi-select field.
    411      * @param string $name name of the  drop-down list
    412      * @param array<string|int,string>|array<string|int,array<string,string>> $values array of value of each line of the list
     423     * Generate generic select/multi-select field.
     424     *
     425     * @param string       $name            Name of the dropdown list.
     426     * @param array<string|int,string>|array<string|int,array<string,string>>        $values          Array of values for each option.
    413427     *  Items can be simple array of values or array of array of values for
    414428     *      grouping items (<optgroup>). Thus each item can be either:
     
    419433     *                                     ....
    420434     *                                   ]
    421      * @param bool $is_mandatory if true, select a value is mandatory.
    422      * @param string $legend text that is displayed just before the field
    423      * @param string $description text that is displayed after the field
    424      * @param bool $select_multiple if true, this is a multiple-select list.
    425      *  if false, user can only select one value.
    426      * @param string|string[]|int|int[]|null $initial_value Pre-select items of the list
     435     * @param bool         $is_mandatory    Whether selection is required.
     436     * @param string       $legend          Label text before the field.
     437     * @param string       $description     Help text after the field.
     438     * @param bool         $select_multiple Whether multiple selection is allowed.
     439     * @param string|string[]|int|int[]|null $initial_value   Pre-selected value(s).
    427440     * - Single choice drop-down list: the item to pre-select
    428441     * - Multiple choice drop-down list : array of items to pre-select
    429      * @param string $onchange JavaScript code to execute when changing the selection
    430      *  of the drop-down list.
    431      * @param string $prefix prefix to add to name
    432      * @return string HTML code of the drop-down list form field
     442     * @param string       $onchange        JavaScript onchange handler.
     443     * @param string       $prefix          Prefix to add to name.
     444     * @return string HTML code for select field.
    433445     * @access private
    434446     * @SuppressWarnings(PHPMD.ElseExpression)
     
    450462        $select_attrs = array(
    451463            'multiple' => $select_multiple ? 'multiple' : null,
    452             'class' => 'form-control',
    453             'name'  => esc_attr( $prefix . $name . ( $select_multiple ? '[]' : '' ) ),
    454             'id'       => esc_attr( $prefix . $name ),
     464            'class'    => 'form-control',
     465            'name'    => esc_attr( $prefix . $name . ( $select_multiple ? '[]' : '' ) ),
     466            'id'       => esc_attr( $prefix . $name ),
    455467            'required' => $is_mandatory ? 'required' : null,
    456             'onchange' => $onchange ?: null, //already escaped
     468            'onchange' => $onchange ?: null, // Already escaped.
    457469        );
    458470        // Remove null attributes
    459471        $select_attrs = array_filter( $select_attrs );
     472
    460473        $attrs_html = '';
    461474        foreach ( $select_attrs as $key => $value ) {
     
    494507
    495508    /**
    496      * Checks if an option should be selected.
     509     * Check if an option should be selected.
    497510     *
    498511     * @param  string|int   $key                Option key to check.
     
    502515     * @access private
    503516     */
    504     private function _is_option_selected( $key, $initial_value, $select_multiple ) {
     517    private function _is_option_selected( $key, $initial_value, bool $select_multiple ): bool {
    505518        if ( $select_multiple ) {
    506519            return is_array( $initial_value ) && in_array( $key, $initial_value, true );
     
    510523
    511524    /**
    512      * Generate an HTML code for a disabled form field
    513      * @param string $legend text that is displayed just before the field
    514      * @param string $description text that is displayed after the field
    515      * @return string HTML code of form field
    516      * @access private
    517       */
    518     private function _explicit_disabled_field(
    519         string $legend,
    520         string $description
    521     ): string {
     525     * Generate disabled field display.
     526     *
     527     * @param string $legend      Label text.
     528     * @param string $description Help text.
     529     * @return string HTML code for disabled field.
     530     * @access private
     531     */
     532    private function _explicit_disabled_field( string $legend, string $description ): string {
    522533        return sprintf(
    523             '<div class="input-group"><div class="input-group-addon"></div><label>%1$s</label>',
     534            '<div class="input-group"><div class="input-group-addon"></div><label>%1$s</label>%2$s</div>',
    524535            esc_html( $legend ),
    525             ) . $this->description( $description ) . '</div>';
    526     }
    527 
    528     /**
    529      * Generates textarea form field.
    530      * @param bool $is_mandatory if true, fill this area is mandatory.
    531      * @param string $name name of the form field
    532      * @param string $description text that is displayed after the field
    533      * @param string $initial_value text area initial value
    534      * @param string $css_id CSS id for the input-group tag.
    535      * @return string HTML code of the text area form field
     536            $this->description( $description )
     537        );
     538    }
     539
     540    /**
     541     * Generate textarea field.
     542     *
     543     * @param bool   $is_mandatory  Whether field is required.
     544     * @param string $name          Name of the field.
     545     * @param string $description   Help text.
     546     * @param string $initial_value Initial value.
     547     * @param string $css_id        CSS ID for the field.
     548     * @return string HTML code for textarea.
    536549     * @access private
    537550     */
     
    562575
    563576    /**
    564      * Generate an HTML code for an URL input form field
    565      * @param bool $is_mandatory if true, fill this field is mandatory.
    566      * @param string $legend text that is displayed just before the field
    567      * @param string $name name of the form field
    568      * @param string $description text that is displayed after the field
    569      * @param string $initial_value initial value
    570      * @param string $css_id CSS id for the <input> tag.
    571      * @return string HTML code of form field
    572      * @access private
    573       */
     577     * Generate URL input field.
     578     *
     579     * @param bool   $is_mandatory  Whether field is required.
     580     * @param string $legend        Label text.
     581     * @param string $name          Name of the field.
     582     * @param string $description   Help text.
     583     * @param string $initial_value Initial value.
     584     * @param string $css_id        CSS ID for the field.
     585     * @return string HTML code for URL field.
     586     * @access private
     587     */
    574588    private function _url_field(
    575589        bool $is_mandatory,
     
    585599
    586600    /**
    587      * Generate an HTML code for a number input form field
    588      * @param bool $is_mandatory if true, fill this field is mandatory.
    589      * @param string $legend text that is displayed just before the field
    590      * @param string $name name of the form field
    591      * @param string $description text that is displayed after the field
    592      * @param string $initial_value initial value
    593      * @param string $css_id CSS id for the <input> tag.
    594      * @return string HTML code of form field
    595      * @access private
    596       */
     601     * Generate number input field.
     602     *
     603     * @param bool   $is_mandatory  Whether field is required.
     604     * @param string $legend        Label text.
     605     * @param string $name          Name of the field.
     606     * @param string $description   Help text.
     607     * @param string $initial_value Initial value.
     608     * @param string $css_id        CSS ID for the field.
     609     * @return string HTML code for number field.
     610     * @access private
     611     */
    597612    private function _number_field(
    598613        bool $is_mandatory,
     
    603618        string $css_id = ''
    604619    ): string {
    605         return $this->_input_field(  $is_mandatory, $legend, 'number', $name, $description, 'input-group-normal nopadding-input', $css_id,
     620        return $this->_input_field( $is_mandatory, $legend, 'number', $name, $description, 'input-group-normal nopadding-input', $css_id,
    606621                        $initial_value );
    607622    }
    608623
    609624    /**
    610      * Generate an HTML code for a color input form field
    611      * @param bool $is_mandatory if true, fill this field is mandatory.
    612      * @param string $legend text that is displayed just before the field
    613      * @param string $name name of the form field
    614      * @param string $description text that is displayed after the field
    615      * @param string $initial_value initial value
    616      * @param string $css_id CSS id for the <input> tag.
    617      * @return string HTML code of form field
    618      * @access private
    619       */
     625     * Generate color input field.
     626     *
     627     * @param bool   $is_mandatory  Whether field is required.
     628     * @param string $legend        Label text.
     629     * @param string $name          Name of the field.
     630     * @param string $description   Help text.
     631     * @param string $initial_value Initial value.
     632     * @param string $css_id        CSS ID for the field.
     633     * @return string HTML code for color field.
     634     * @access private
     635     */
    620636    private function _color_field(
    621637        bool $is_mandatory,
     
    626642        string $css_id = ''
    627643    ): string {
    628         return $this->_input_field( $is_mandatory, $legend, 'text', $name, $description, 'input-group-normal nopadding-input', $css_id,
    629                         $initial_value, 'my-color-field', 'data-default-color="#effeff"' );
    630     }
    631 
    632     /**
    633      * Generate an HTML code for the choice of a square image
    634      * @param string $name name of the form
    635      * @param string $description comment displayed just above the form
    636      * @param string $initial_value Initial value of the form
    637      * @return string HTML code
     644        return $this->_input_field(
     645            $is_mandatory,
     646            $legend,
     647            'text',
     648            $name,
     649            $description,
     650            'input-group-normal nopadding-input',
     651            $css_id,
     652            $initial_value,
     653            'my-color-field',
     654            'data-default-color="#effeff"'
     655        );
     656    }
     657
     658    /**
     659     * Generate square image selector.
     660     *
     661     * @param string $name          Name of the field.
     662     * @param string $description   Help text.
     663     * @param string $initial_value Initial value.
     664     * @return string HTML code for image selector.
    638665     * @access private
    639666     */
     
    648675            wp_kses_post( $description )
    649676        );
    650         // "None" button
     677        // "None" button.
    651678        $output .= sprintf(
    652679            '<input id="%1$s_no_logo_button" class="button" value="%2$s" onclick="MonLabo.selectNoPicture(\'%1$s-image-preview\', \'%1$s\');" type="button" />',
     
    654681            _x( 'None', 'logo', 'mon-laboratoire' )
    655682        );
    656         // Media selection button
     683        // Media selection button.
    657684        $output .= sprintf(
    658685            '&nbsp;<input id="%1$s-upload-image-button" type="button" class="button" value="%2$s" onclick="MonLabo.imageMediaMenu(\'%3$s\', \'%4$s\', \'%1$s-image-preview\', \'%1$s\');" />',
     
    662689            __( 'use this picture', 'mon-laboratoire' )
    663690        );
    664         // Hidden input for value
     691        // Hidden input for value.
    665692        $output .= $this->hidden_field( $name, $initial_value, $name ) . '&nbsp;';
    666         // Image preview
     693        // Image preview.
    667694        $output .= sprintf(
    668695            '<a href="#" class="hover-zoom-square30"><img width="30" height="30" id="%1$s-image-preview" class="wp-image-8 wp-post-image%2$s" src="%3$s" alt="%4$s"></a>',
     
    677704
    678705    /**
    679      * Generate an HTML code for the choice of an image for a person
    680      * @param string $name name of the form
    681      * @param string $description comment displayed just above the form
    682      * @param string $initial_value Initial value of the form
    683      * @return string HTML code
     706     * Generate person image selector.
     707     *
     708     * @param string $name          Name of the field.
     709     * @param string $description   Help text.
     710     * @param string $initial_value Initial value.
     711     * @return string HTML code for image selector.
    684712     * @access private
    685713     */
     
    689717        string $initial_value
    690718    ): string {
    691         $options2 = get_option( 'MonLabo_settings_group2' );
     719        $options2      = get_option( 'MonLabo_settings_group2' );
    692720        $default_image = $this->_html->image_from_id_or_url( $options2['MonLabo_img_par_defaut'] );
    693721        $output = sprintf(
     
    696724            wp_kses_post( $description )
    697725        );
    698         // "None" button
     726        // "None" button.
    699727        $output .= sprintf(
    700728            '<input id="%1$s_no_logo_button" class="button" value="%2$s" onclick="MonLabo.selectDefaultPicture(\'%1$s-image-preview\', \'%1$s\', \'%3$s\');" type="button" />',
     
    703731            esc_js( $default_image )
    704732        );
    705         // Media selection button
     733        // Media selection button.
    706734        $output .= sprintf(
    707735            '&nbsp;<input id="%1$s-upload-image-button" type="button" class="button" value="%2$s" onclick="MonLabo.imageMediaMenu(\'%3$s\', \'%4$s\', \'%1$s-image-preview\', \'%1$s\');" />',
     
    711739            esc_js( __( 'use this picture', 'mon-laboratoire' ) )
    712740        );
    713         // Hidden input for value
     741        // Hidden input for value.
    714742        $output .= $this->hidden_field( $name, $initial_value, $name ) . '&nbsp;';
    715         // Image preview
     743        // Image preview.
    716744        $url = $this->_html->image_from_id_or_url( $initial_value );
    717745        if ( empty( $initial_value ) || 'DEFAULT' === $initial_value ) {
  • mon-laboratoire/trunk/Admin/Forms/class-generic-forms-view.php

    r3361909 r3369834  
    22namespace MonLabo\Admin\Forms;
    33
    4 use MonLabo\Frontend\{Html};
     4use MonLabo\Frontend\Html;
    55
    66defined( 'ABSPATH' ) || die( 'No direct script access allowed' );
    77
    88/**
    9  * Class \MonLabo\Admin\Generic_Forms_View
    10  * Facade class providing unified form view
     9 * Class \MonLabo\Admin\Forms\Generic_Forms_View
     10 *
     11 * Facade class providing unified form view functionality for the admin interface.
    1112 *
    12  * Methods :
     13 * This abstract class serves as a facade for generating form elements
     14 * with consistent styling and behavior across the plugin's admin forms.
     15 * It provides methods for creating various types of form fields with
     16 * proper HTML escaping and accessibility features.
     17 *
     18 * Methods:
     19 *     _input_field( bool $is_mandatory, string $legend, string $input_type,
     20 *                   string $name, string $description, string $input_group_class,
     21 *                   string $input_id, string $initial_value, string $input_class,
     22 *                   string $input_custom )
     23 *     _large_text_field( bool $is_mandatory, string $legend, string $name,
     24 *                        string $description, string $initial_value, string $css_id )
     25 *     _maybe_add_field_label( string $name, string $legend, bool $is_mandatory )
     26 *     description( string $description )
    1327 *
    14  *   _input_field( $is_mandatory, $legend, $input_type, $name, $description, $input_group_class, $input_id, $initial_value, $input_class, $input_custom)
    15  *   _large_text_field( $is_mandatory, $legend, $name, $description, $initial_value, $css_id)
    16  *   _maybe_add_field_label( $name, $legend, $is_mandatory )
    17  *   description( string $description )
    18 
    19 *
    2028 * @package MonLabo\Admin\Forms
    2129*/
     
    2432    /**
    2533     * Instance of the Html helper class.
    26      * @access protected
    27      * @var Html
     34     *
     35     * Provides HTML generation utilities for form elements.
     36     *
     37     * @access protected
     38     * @var    Html Instance of the Html helper class.
    2839     */
    2940    protected $_html = null;
     
    3142    /**
    3243     * Constructor.
     44     *
     45     * Initializes the Html helper instance.
     46     *
    3347     * @access public
    3448     */
     
    3751    }
    3852
    39 
    40     /**
    41      * Generate an HTML code for a generic input form field
    42      * @param bool $is_mandatory if true, fill this field is mandatory.
    43      * @param string $legend text that is displayed just before the field
    44      * @param string $input_type type of the form field (text, url...)
    45      * @param string $name name of the form field
    46      * @param string $description text that is displayed after the field
    47      * @param string $input_group_class CSS class of the input-group <div> tag
    48      * @param string $input_id CSS id for the <input> tag.
    49      * @param string $initial_value text initial value
    50      * @param string $input_class additional CSS class for the <input> tag.
    51      * @param string $input_custom additional custom things for the <input> tag.
    52      * @return string HTML code of form field
    53      * @access protected
    54       */
     53    /**
     54     * Generates HTML for a generic input form field.
     55     *
     56     * Creates an input field with Bootstrap-style input group wrapper,
     57     * including proper HTML escaping and optional field requirements.
     58     *
     59     *
     60     * @param  bool   $is_mandatory       Whether the field is required.
     61     * @param  string $legend             Label text displayed as placeholder.
     62     * @param  string $input_type         HTML input type (text, url, email, etc).
     63     * @param  string $name               Field name attribute.
     64     * @param  string $description        Help text displayed below the field.
     65     * @param  string $input_group_class  Additional CSS classes for the wrapper div. Default ''.
     66     * @param  string $input_id           HTML ID attribute for the input element. Default ''.
     67     * @param  string $initial_value      Initial field value. Default ''.
     68     * @param  string $input_class        Additional CSS classes for the input element. Default ''.
     69     * @param  string $input_custom       Additional custom HTML attributes. Default ''.
     70     * @return string                     Generated HTML for the form field.
     71     * @access protected
     72     */
    5573    protected function _input_field(
    5674        bool $is_mandatory,
     
    6583        string $input_custom = ''
    6684    ): string {
    67         $output = sprintf( '<div class="input-group%s">', $input_group_class ? ' ' . esc_attr( $input_group_class ) : '' );
     85        // Open wrapper div with optional additional classes.
     86        $output = '<div class="input-group';
     87        if ( ! empty( $input_group_class ) ) {
     88            $output .= ' ' . esc_attr( $input_group_class );
     89        }
     90        $output .= '">';
     91       
     92        // Add input group addon for Bootstrap styling.
    6893        $output .= '<div class="input-group-addon"></div>';
     94       
     95        // Modify legend for optional fields.
    6996        if ( ! $is_mandatory ) {
    7097            $legend .= ' (' . __( 'optional', 'mon-laboratoire' ) . ')';
    7198        }
     99       
     100        // Build input attributes array.
    72101        $input_attrs = array(
    73             'type'          => esc_attr( $input_type ),
    74             'class'         => 'form-control' . ( $input_class ? ' ' . esc_attr( $input_class ) : '' ),
    75             'name'          => esc_attr( $name) ,
    76             'placeholder'   => esc_attr( $legend ),
    77             'id'            => esc_attr( $input_id ),
    78             'value'         => strval( esc_attr( $initial_value ) ),
    79             'required'      => $is_mandatory ? 'required' : null,
     102            'type'        => esc_attr( $input_type ),
     103            'class'       => 'form-control',
     104            'name'        => esc_attr( $name ),
     105            'placeholder' => esc_attr( $legend ),
     106            'id'          => esc_attr( $input_id ),
     107            'value'       => '__EMPTY__',
     108            'required'    => $is_mandatory ? 'required' : null,
    80109        );
     110       
     111        // Add optional attributes if provided.
     112        if ( ! empty( $input_class ) ) {
     113            $input_attrs['class'] .= ' ' . esc_attr( $input_class );
     114        }
     115
    81116        // Remove empty attributes
    82         if ( '0' === $input_attrs['value'] ) {
    83             $input_attrs['value'] = 'ZERO_TO_KEEP';
    84         }
    85117        $input_attrs = array_filter( $input_attrs );
    86         if ( isset( $input_attrs['value'] ) && 'ZERO_TO_KEEP' === $input_attrs['value'] ) {
    87             $input_attrs['value'] = '0';
    88         }
     118        // Handle initial value, including special case for '0'.
     119        if ( '' !== $initial_value ) {
     120            $input_attrs['value'] = esc_attr( $initial_value );
     121        }
     122        if ( '__EMPTY__' === $input_attrs['value'] ) {
     123            unset( $input_attrs['value'] );
     124        }
     125       
     126        // Build HTML attributes string.
    89127        $attrs_html = '';
    90128        foreach ( $input_attrs as $key => $value ) {
    91             $attrs_html .= sprintf( ' %s="%s"', $key,  $value );
    92         }
    93         if ( $input_custom ) {
     129            $attrs_html .= sprintf( ' %s="%s"', $key, $value );
     130        }
     131       
     132        // Add custom attributes if provided.
     133        if ( ! empty( $input_custom ) ) {
    94134            $attrs_html .= ' ' . $input_custom;
    95135        }
     136       
     137        // Build final HTML output.
    96138        $output .= sprintf( '<input%s />', $attrs_html );
    97139        $output .= $this->description( $description ) . '</div>';
     
    100142
    101143    /**
    102      * Generates large text field.
    103      * @param bool $is_mandatory if true, fill this field is mandatory.
    104      * @param string $legend text that is displayed just before the field
    105      * @param string $name name of the form field
    106      * @param string $description text that is displayed after the field
    107      * @param string $initial_value initial value
    108      * @param string $css_id CSS id for the <input> tag.
    109      * @return string HTML code of form field
    110      * @access protected
    111       */
     144     * Generates a large text input field.
     145     *
     146     * Creates a text input field with larger styling applied via the
     147     * 'input-group-large' CSS class. This is a convenience wrapper around
     148     * the _input_field method.
     149     *
     150     * @param  bool   $is_mandatory   Whether the field is required.
     151     * @param  string $legend         Label text displayed as placeholder.
     152     * @param  string $name           Field name attribute.
     153     * @param  string $description    Help text displayed below the field.
     154     * @param  string $initial_value  Initial field value. Default ''.
     155     * @param  string $css_id         HTML ID attribute for the input element. Default ''.
     156     * @return string                 Generated HTML for the large text field.
     157     * @access protected
     158     */
    112159    protected function _large_text_field(
    113160        bool $is_mandatory,
     
    118165        string $css_id = ''
    119166    ): string {
    120         return $this->_input_field( $is_mandatory, $legend, 'text', $name, $description, 'input-group-large', $css_id,
    121                             $initial_value );
    122     }
    123 
    124     /**
    125      * Adds a label to a form field if needed.
    126      *
    127      * @param  string $name         Field name attribute.
    128      * @param  string $legend       Label text.
    129      * @param  bool   $is_mandatory Whether the field is required.
    130      * @return string               Generated HTML for the label.
    131      * @access protected
    132      */
    133     protected function _maybe_add_field_label( $name, $legend, $is_mandatory ) {
     167        return $this->_input_field(
     168            $is_mandatory,
     169            $legend,
     170            'text',
     171            $name,
     172            $description,
     173            'input-group-large',
     174            $css_id,
     175            $initial_value
     176        );
     177    }
     178
     179    /**
     180     * Generates a label element for a form field.
     181     *
     182     * Creates an HTML label element with proper accessibility attributes
     183     * and optional indicator for non-required fields.
     184     *
     185     * @param  string $name         Field name attribute (used for 'for' attribute).
     186     * @param  string $legend       Label text content.
     187     * @param  bool   $is_mandatory Whether the field is required.
     188     * @return string               Generated HTML for the label, or empty string if not needed.
     189     * @access protected
     190     */
     191    protected function _maybe_add_field_label( string $name, string $legend, bool $is_mandatory ): string {
     192        // Don't generate label if legend is empty and field is mandatory.
    134193        if ( empty( $legend ) && $is_mandatory ) {
    135194            return '';
    136195        }
    137196        $output  = sprintf( '<label for="%s">%s', esc_attr( $name ), esc_html( $legend ) );
    138         $output .= ! $is_mandatory ? sprintf( ' <small>(%s)</small>', __( 'optional', 'mon-laboratoire' ) ) : '';
     197       
     198        // Add optional indicator for non-mandatory fields.
     199        if ( ! $is_mandatory ) {
     200            $output .= sprintf( ' <small>(%s)</small>', __( 'optional', 'mon-laboratoire' ) );
     201        }
     202       
    139203        $output .= '</label>';
     204       
    140205        return $output;
    141206    }
    142207
    143208    /**
    144      * Generate an HTML code for description
    145      * @param string $description text
    146      * @return string HTML code
     209     * Generates HTML for a field description.
     210     *
     211     * Creates a properly escaped description div element for form field
     212     * help text. Returns empty string if no description is provided.
     213     *
     214     * @param  string $description The description text to display.
     215     * @return string              Generated HTML for the description, or empty string.
    147216     */
    148217    public function description( string $description ): string {
    149         return empty( $description ) ?
    150                 '' : sprintf( '<div class="description">%s</div>', wp_kses_post( $description ) );
    151     }
    152 
     218        if ( empty( $description ) ) {
     219            return '';
     220        }
     221       
     222        return sprintf( '<div class="description">%s</div>', wp_kses_post( $description ) );
     223    }
    153224}
    154225?>
  • mon-laboratoire/trunk/Admin/Forms/class-options-forms-view.php

    r3361909 r3369834  
    1111 * Class \MonLabo\Admin\Options_Forms_View
    1212 *
    13  * Handles rendering of settings fields in the WordPress admin
     13 * Handles rendering of settings fields in the WordPress admin.
    1414 *
    1515 * This class provides methods for rendering various types of form fields
     
    2727 *  add()
    2828 *  select_generic_render( array $args )
     29 *  _get_multisite_items()
    2930 *  radio_generic_render( array $args )
     31 *  _get_radio_items( string $option_name )
     32 *  _get_publication_server_items()
     33 *  _get_language_config_items( $translate, $html, $polylang_interface )
    3034 *  hidden_generic_render( array $args )
    3135 *  text_field_generic_render( array $args )
     
    4145 *  button_test_send_email_generic_render( array $args )
    4246 *  button_create_default_parent_page_generic_render( array $args )
     47 *  _create_parent_page( string $option_name, array &$options )
    4348 *  checkbox_generic_render( array $args )
    4449 *  checkbox2_generic_render( array $args )
     50 *  hidden_order_generic_render( array $args )
    4551 *  number_generic_render( array $args )
    4652 *  select_page_generic_render( array $args )
     
    5157
    5258    /**
    53      * Current instance of Forms_View
     59     * Current instance of Forms_View.
     60     *
    5461     * @access protected
    55      * @var Forms_View
     62     * @var    Forms_View
    5663     */
    5764    protected $_forms_view;
    5865
    5966    /**
    60      * Cache of WordPress options
    61      *
    62      *    Stores retrieved option values to avoid repeated database calls.
     67     * Cache of WordPress options.
     68     *
     69     * Stores retrieved option values to avoid repeated database calls.
    6370     *
    6471     * @access private
    65      * @var array<string, string[]> Key is option group, value is array of options
     72     * @var    array<string, string[]> Key is option group, value is array of options.
    6673     */
    6774    private $_options = array();
    6875
    6976    /**
    70      * Constructor for Options_Forms_View class
     77     * Constructor for Options_Forms_View class.
    7178     *
    7279     * Initializes the options cache by loading all option groups
     
    8188
    8289    /**
    83      * Display a <SELECT> option list form field
     90     * Add a settings field to the admin page.
    8491     * Called with "add_field( 'select' ... "
    85      * @param string $type Type of the field
    86      * @param string $title Text before the form field
    87      * @param string $page Current section of a  menu
    88      * @param string $section Current page of a  menu
     92     *
     93     * @param string $type    Type of the field (select, radio, text_field, etc.).
     94     * @param string $title   Text before the form field.
     95     * @param string $page    Current page of a menu.
     96     * @param string $section Current section of a menu.
    8997     * @param array{option_name:string,description:string,settings_group:string,disable:bool,hide:bool,class:string} $args Parameters :
    9098     *      - $args['settings_group'] : Group of option to update
     
    117125            'hidden_order',
    118126        );
    119         if (   isset( $args['settings_group']   )
    120             and isset( $args['option_name']     )
    121             and isset( $args['description']     )
    122             and isset( $args['disable']         )
    123             and in_array( $type, $valid_types   )
     127
     128        if ( ! isset( $args['settings_group'], $args['option_name'], $args['description'], $args['disable'] ) ) {
     129            return $this;
     130        }
     131        if ( ! in_array( $type, $valid_types ) ) {
     132            return $this;
     133        }
     134
     135        // $args['class'] can complete $args['option_name'] as a class.
     136        $class       = $args['class'] ?? '';
     137        $option_name = $args['option_name'];
     138       
     139        if ( $option_name !== $class ) {
     140            $class = empty( $class ) ? $option_name : $option_name . ' ' . $class;
     141        }
     142       
     143        $hideclass = '';
     144        if ( ( isset( $args['hide'] ) && true === $args['hide'] )
     145            || ( isset( $args['disable'] ) && true === $args['disable'] )
    124146        ) {
    125             //$args['class'] can complete $args['option_name'] ass a class
    126             $class = $args['class'] ?? '';
    127             $option_name = $args['option_name'];
    128             if ( $option_name !== $class ) {
    129                 $class = empty( $class ) ? $option_name : $option_name . ' ' . $class;
    130             }
    131             $hideclass = '';
    132             if (
    133                 ( isset( $args['hide'] ) && $args['hide'] === true )
    134                 || ( isset( $args['disable'] ) && $args['disable'] === true )
    135             ) {
    136                 $hideclass = ' MonLabo_hide';
    137             }
    138             $title_text = empty( $class . $hideclass ) ? '' : '<span class="' . $class . $hideclass . '">'. $title . '</span>';
    139             $title_text2 = empty( $title ) ? '' : $title_text;
    140             add_settings_field(
    141                 $args['option_name'],
    142                 $title_text2 ,
    143                 array( &$this, $type . '_generic_render' ),
    144                 $page,
    145                 $section,
    146                 $args
    147             );
    148         }
    149         return $this;
    150     }
    151 
    152 
    153     /**
    154      * Display a <SELECT> option list form field
     147            $hideclass = ' MonLabo_hide';
     148        }
     149        $title = ( '' === $title ? '&nbsp;' : $title );
     150        $title_text  = empty( $class . $hideclass ) ? '' : '<span class="' . $class . $hideclass . '">' . $title . '</span>';
     151        $title_text2 = empty( $title ) ? '' : $title_text;
     152       
     153        add_settings_field(
     154            $args['option_name'],
     155            $title_text2,
     156            array( &$this, $type . '_generic_render' ),
     157            $page,
     158            $section,
     159            $args
     160        );
     161
     162        return $this;
     163    }
     164
     165    /**
     166     * Render a select dropdown field.
    155167     * Called with "add_field( 'select' ... "
    156168     * @param array{option_name:string,description:string,settings_group:string,disable:bool,hide:bool,class:string,on_change?:string} $args
     
    161173     *      - $args['disable'] : true if disabled form
    162174     *      - $args['on_change'] : Optional. JavaScript for onchange.
    163      * @return Options_Forms_View For method chaining.
     175     * @return Options_Forms_View Returns self for method chaining.
    164176     */
    165177    public function select_generic_render( array $args ): self {  // @phan-suppress-current-line PhanUnreferencedPublicMethod
     
    167179            return $this->hidden_generic_render( $args );
    168180        }
    169 
    170181        switch ( $args['option_name'] ) {
    171182            case 'MonLabo_hal_publi_style':
     
    173184                break;
    174185            case 'MonLabo_DescartesPubmed_format':
    175                 $items =  array( 'html_default'=>'html_default', 'html_hal'=>'html_hal' );
     186                $items = array( 'html_default' => 'html_default', 'html_hal' => 'html_hal' );
    176187                break;
    177188            case 'MonLabo_multisite_db_to_use':
    178                 $items = array(
    179                     '___no_change___' =>  __( '--- No change ---', 'mon-laboratoire' ),
    180                     '___manual_edit___' =>  __( '--- Manual edit (use field "Prefix" below) ---', 'mon-laboratoire' ),
    181                 );
    182                 if ( is_multisite()
    183                     && function_exists( 'get_sites' )
    184                     && class_exists( 'WP_Site_Query' ) //WP version >= 4.6
    185                 ) {
    186                     //get_main_site_id
    187                     $sites = get_sites();
    188                     if ( !empty( $sites ) ) {
    189                         $actual_site = get_site();
    190                         foreach ( $sites as $site ) {
    191                             $suffix = '';
    192                             if ( $site->blog_id === $actual_site->blog_id ) {
    193                                 $suffix = ' — '. __( 'Current site', 'mon-laboratoire' );
    194                             }
    195                             global $wpdb;
    196                             $prefix = $wpdb->get_blog_prefix( $site->blog_id );
    197                             $items[$prefix] = $prefix . ' : ' . $site->__get('blogname') . ' (' . $site->__get('siteurl') . ')' . $suffix;
    198                         }
    199                     }
    200                 }
    201                 break;                 
     189                $items = $this->_get_multisite_items();
     190                break;
    202191            default:
    203192                return $this; /* error */
    204193        }
    205         $name = $args['settings_group'] . '[' . $args['option_name'] . ']';
    206         $onchange = empty( $args['on_change'] ) ? '' : esc_attr( $args['on_change'] );
     194        $name          = $args['settings_group'] . '[' . $args['option_name'] . ']';
     195        $onchange      = empty( $args['on_change'] ) ? '' : esc_attr( $args['on_change'] );
    207196        $initial_value = $this->_options[ $args['settings_group'] ][ $args['option_name'] ];
    208         echo $this->_forms_view->select( $name, $items, true, '', '', $initial_value, $onchange, '' );
    209         return $this;
    210     }
    211 
    212     /**
    213      * Renders a radio button group field.
     197        echo $this->_forms_view->select( $name, $items, true, '', '', $initial_value, $onchange, '' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped //output escaping is handled by called methods
     198        return $this;
     199    }
     200
     201    /**
     202     * Get multisite database items for select field.
     203     *
     204     * @return array<string,string> Array of multisite options.
     205     * @access private
     206     */
     207    private function _get_multisite_items(): array {
     208        $items = array(
     209            '___no_change___'   => __( '--- No change ---', 'mon-laboratoire' ),
     210            '___manual_edit___' => __( '--- Manual edit (use field "Prefix" below) ---', 'mon-laboratoire' ),
     211        );
     212        if (
     213                ! is_multisite()
     214                || ! function_exists( 'get_sites' )
     215                || ! class_exists( 'WP_Site_Query' ) //WP version < 4.6
     216        ) {
     217            return $items;
     218        }
     219        //get_main_site_id
     220        $sites = get_sites();
     221        if ( empty( $sites ) ) {
     222            return $items;
     223        }
     224        $actual_site = get_site();
     225        global $wpdb;
     226        foreach ( $sites as $site ) {
     227            $suffix = '';
     228            if ( $site->blog_id === $actual_site->blog_id ) {
     229                $suffix = ' — ' . __( 'Current site', 'mon-laboratoire' );
     230            }
     231            $prefix = $wpdb->get_blog_prefix( $site->blog_id );
     232            $items[ $prefix ] = $prefix . ' : ' . $site->__get( 'blogname' ) . ' (' . $site->__get( 'siteurl' ) . ')' . $suffix;
     233        }
     234        return $items;
     235    }
     236
     237    /**
     238     * Render a radio button group field.
     239     *
    214240     * @param array{option_name:string,description:string,settings_group:string,disable:bool,hide:bool,class:string,on_change?:string} $args
    215241     *   Field configuration options.
     
    219245     *      - $args['disable'] : true if disabled form
    220246     *      - $args['hide'] :    true if CSS display is hide
    221      *      - $args['on_change'] : Optional. JavaScript for onchange.
    222      * @return Options_Forms_View For method chaining.
     247     *      - $args['on_change'] : Optional. JavaScript for onchange event.
     248     * @return Options_Forms_View Returns self for method chaining.
    223249     */
    224250    public function radio_generic_render( array $args ): self { // @phan-suppress-current-line PhanUnreferencedPublicMethod
     
    226252            return $this->hidden_generic_render( $args );
    227253        }
    228         $translate = new Translate();
    229         $html = new Html();
    230         $Polylang_Interface = new Polylang_Interface();
    231         switch ( $args['option_name'] ) {
    232             case 'MonLabo_publication_server_type':
    233                 $items = array( 'hal'           => '<a href="https://hal.science/"><img width="61" height="30" class="wp-image-8 alignleft wp-post-image" '. 'src="' . plugins_url( 'images/logoHAL.png', __DIR__ ) . '" alt="'. __( 'logo of HAL', 'mon-laboratoire' ) . '" /></a>&nbsp;'. __( 'HAL (Public API open to all)', 'mon-laboratoire' ),
    234                                 'DescartesPubli'=> '<img width="61" height="34" class="wp-image-8 alignleft wp-post-image" src="' . plugins_url( 'images/DescartesPubli.logo.png', __DIR__ ) . '" alt="'. __( 'logo of Descartes publi', 'mon-laboratoire' ) . '" />&nbsp;'. __( 'Descartes Publi (API reserved for the University of ParisDescartes)', 'mon-laboratoire' ),
    235                                 'both'          => __( 'Both! (HAL is used by default if the option <i>base</i> is not specified in <i>[publication_list]</i>)', 'mon-laboratoire' ),
    236                                 'aucun'         => _x( 'None', 'option-langage', 'mon-laboratoire' ) );
    237                 break;
    238 
    239             case 'MonLabo_trusted_visitors':
    240                 $items = array(
    241                     App::PRIVACY_TRUST_EVERYBODY        => __( 'Everybody', 'mon-laboratoire' ),
    242                     App::PRIVACY_TRUST_LOGGED_IN_USERS  => __( 'Logged-in users', 'mon-laboratoire' ),
    243                     App::PRIVACY_TRUST_IP_ZONE          => __( 'Logged-in users and visitors from trusted IP addresses', 'mon-laboratoire' ),
    244                 );
    245                 break;
    246 
    247             case 'MonLabo_email_privacy':
    248                 $items = array(
    249                     App::PRIVACY_SHOW               => __( 'Always show', 'mon-laboratoire' ),
    250                     App::PRIVACY_HIDE               => __( 'Always hide', 'mon-laboratoire' ),
    251                     App::PRIVACY_LOCAL_SHOW         => __( 'Only show to trusted visitors', 'mon-laboratoire' ),
    252                     App::PRIVACY_FORM_LOCAL_SHOW    => __( 'Only show to trusted visitors, replace with contact form for others', 'mon-laboratoire' )
    253                 );
    254                 break;
    255            
    256             case 'MonLabo_phone_privacy':
    257                 $items = array(
    258                     App::PRIVACY_SHOW                       => __( 'Always show', 'mon-laboratoire' ),
    259                     App::PRIVACY_HIDE                       => __( 'Always hide', 'mon-laboratoire' ),
    260                     App::PRIVACY_LOCAL_SHOW                 => __( 'Only show to trusted visitors', 'mon-laboratoire' ),
    261                     App::PRIVACY_SWITCHBOARD_LOCAL_SHOW     => __( 'Only show to trusted visitors, replace with switchboard for others', 'mon-laboratoire' )
    262                 );
    263                 break;
    264 
    265             case 'MonLabo_room_privacy':
    266             case 'MonLabo_photo_privacy':
    267                 $items = array(
    268                     App::PRIVACY_SHOW               => __( 'Always show', 'mon-laboratoire' ),
    269                     App::PRIVACY_HIDE               => __( 'Always hide', 'mon-laboratoire' ),
    270                     App::PRIVACY_LOCAL_SHOW         => __( 'Only show to trusted visitors', 'mon-laboratoire' )
    271                 );
    272                 break;
    273 
    274             case 'MonLabo_language_config':
    275                 $items = array( 'en'        => __( 'English', 'mon-laboratoire' ) . ' ' . $html->get_translation_flag( 'en' ),
    276                                 'fr'        => __( 'French', 'mon-laboratoire' ). ' ' . $html->get_translation_flag( 'fr' ),
    277                                 'browser'   => __( 'Visitor\'s browser language', 'mon-laboratoire' ) . ' <small>(' . __( 'here', 'mon-laboratoire' ) . ' : <em>' .$translate->get_browser_language() . '</em>)</small>',
    278                                 'Polylang'  => __( 'Multilingual, using the Polylang translation plugin', 'mon-laboratoire' ) . ' <small>(' . __( 'Polylang plugin status', 'mon-laboratoire' ). ' : <em>' . $Polylang_Interface->get_polylang_plugin_status( "translated" ) . '</em>)</small>' ,
    279                                 'WordPress' =>
    280                                     sprintf(
    281                                         __( 'Language configured %1$s in WordPress %2$s or by a translation plugin' , 'mon-laboratoire' )
    282                                         , '<a href="' . admin_url( 'options-general.php' ) . '">'
    283                                         , '</a>'
    284                                     )
    285                                     . ' <small>(' . __( 'currently', 'mon-laboratoire' ) . ' : <em>'
    286                                     . ( 'activated' === $Polylang_Interface->get_polylang_plugin_status()
    287                                             ? __( 'defined by Polylang', 'mon-laboratoire' )
    288                                             : get_locale()
    289                                         )
    290                                     . '</em>)</small>',
    291                                 );
    292                 break;
    293                 default:
    294                 return $this; /* Error */
    295         }
    296         $hideclass = ( isset($args['hide']) && $args['hide'] === true ) ? ' class="MonLabo_hide"' : '';
    297         echo( '<fieldset' . $hideclass . '>');
    298         if (! empty( trim( $args['description'])) ){
     254        $items = $this->_get_radio_items( $args['option_name'] );
     255       
     256        if ( empty( $items ) ) {
     257            return $this;
     258        }
     259
     260        $hideclass = ( isset( $args['hide'] ) && true === $args['hide'] ) ? ' class="MonLabo_hide"' : '';
     261        echo( '<fieldset' . $hideclass . '>' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
     262        if ( ! empty( trim( $args['description'] ) ) ) {
    299263            echo( '<legend>' . wp_kses_post( $args['description'] ) . '</legend>' );
    300264        }
    301         foreach ( $items as $itemKey => $item ) {
     265        foreach ( $items as $item_key => $item ) {
    302266            $checked = ( isset( $this->_options[ $args['settings_group'] ][ $args['option_name'] ] ) )
    303                         ? checked( $this->_options[ $args['settings_group'] ][ $args['option_name'] ], $itemKey, false )
     267                        ? checked( $this->_options[ $args['settings_group'] ][ $args['option_name'] ], $item_key, false )
    304268                        : '';
    305269            $input_id = uniqid();
    306270            echo( '<div class="MonLabo_radio">'
    307                 .'<input type="radio"'
    308                     .' id="' . $input_id . '"'
    309                     .' name="' . esc_attr( $args['settings_group'] . '[' . $args['option_name'] . ']' ) . '"'
    310                     .' value="' . wp_kses_post( $itemKey ) . '"'
    311                     . $checked . ' />'
    312                 . '<label for="' . $input_id . '">' . $item . '</label>'
    313             .'</div>' );
    314 
     271                . '<input type="radio"'
     272                    . ' id="' . $input_id . '"'
     273                    . ' name="' . esc_attr( $args['settings_group'] . '[' . $args['option_name'] . ']' ) . '"'
     274                    . ' value="' . wp_kses_post( $item_key ) . '"'
     275                    . $checked . ' />' // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
     276                . '<label for="' . $input_id . '">' . $item . '</label>' // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEs
     277            . '</div>' );
    315278        }
    316279        echo '</fieldset>';
    317280        return $this;
     281    }
     282
     283    /**
     284     * Get radio button items based on option name.
     285     *
     286     * @param string $option_name Name of the option.
     287     * @return array<string,string> Array of radio items.
     288     * @access private
     289     */
     290    private function _get_radio_items( string $option_name ): array {
     291        $translate           = new Translate();
     292        $html                = new Html();
     293        $polylang_interface  = new Polylang_Interface();
     294        switch ( $option_name ) {
     295            case 'MonLabo_publication_server_type':
     296                return $this->_get_publication_server_items();
     297
     298            case 'MonLabo_trusted_visitors':
     299                return array(
     300                    App::PRIVACY_TRUST_EVERYBODY       => __( 'Everybody', 'mon-laboratoire' ),
     301                    App::PRIVACY_TRUST_LOGGED_IN_USERS => __( 'Logged-in users', 'mon-laboratoire' ),
     302                    App::PRIVACY_TRUST_IP_ZONE         => __( 'Logged-in users and visitors from trusted IP addresses', 'mon-laboratoire' ),
     303                );
     304
     305            case 'MonLabo_email_privacy':
     306                return array(
     307                    App::PRIVACY_SHOW            => __( 'Always show', 'mon-laboratoire' ),
     308                    App::PRIVACY_HIDE            => __( 'Always hide', 'mon-laboratoire' ),
     309                    App::PRIVACY_LOCAL_SHOW      => __( 'Only show to trusted visitors', 'mon-laboratoire' ),
     310                    App::PRIVACY_FORM_LOCAL_SHOW => __( 'Only show to trusted visitors, replace with contact form for others', 'mon-laboratoire' ),
     311                );
     312
     313            case 'MonLabo_phone_privacy':
     314                return array(
     315                    App::PRIVACY_SHOW                    => __( 'Always show', 'mon-laboratoire' ),
     316                    App::PRIVACY_HIDE                    => __( 'Always hide', 'mon-laboratoire' ),
     317                    App::PRIVACY_LOCAL_SHOW              => __( 'Only show to trusted visitors', 'mon-laboratoire' ),
     318                    App::PRIVACY_SWITCHBOARD_LOCAL_SHOW  => __( 'Only show to trusted visitors, replace with switchboard for others', 'mon-laboratoire' ),
     319                );
     320
     321            case 'MonLabo_room_privacy':
     322            case 'MonLabo_photo_privacy':
     323                return array(
     324                    App::PRIVACY_SHOW       => __( 'Always show', 'mon-laboratoire' ),
     325                    App::PRIVACY_HIDE       => __( 'Always hide', 'mon-laboratoire' ),
     326                    App::PRIVACY_LOCAL_SHOW => __( 'Only show to trusted visitors', 'mon-laboratoire' ),
     327                );
     328
     329            case 'MonLabo_language_config':
     330                return $this->_get_language_config_items( $translate, $html, $polylang_interface );
     331
     332            default:
     333                return array(); /* Error */
     334        }
     335    }
     336
     337    /**
     338     * Get publication server type items for radio field.
     339     *
     340     * @return array<string,string> Array of publication server options.
     341     * @access private
     342     */
     343    private function _get_publication_server_items(): array {
     344        return array(
     345            'hal' => '<a href="https://hal.science/"><img width="61" height="30" class="wp-image-8 alignleft wp-post-image" '
     346                . 'src="' . plugins_url( 'images/logoHAL.png', __DIR__ ) . '" alt="' . __( 'logo of HAL', 'mon-laboratoire' ) . '" /></a>&nbsp;'
     347                . __( 'HAL (Public API open to all)', 'mon-laboratoire' ),
     348            'DescartesPubli' => '<img width="61" height="34" class="wp-image-8 alignleft wp-post-image" src="'
     349                . plugins_url( 'images/DescartesPubli.logo.png', __DIR__ ) . '" alt="' . __( 'logo of Descartes publi', 'mon-laboratoire' ) . '" />&nbsp;'
     350                . __( 'Descartes Publi (API reserved for the University of ParisDescartes)', 'mon-laboratoire' ),
     351            'both' => __( 'Both! (HAL is used by default if the option <i>base</i> is not specified in <i>[publication_list]</i>)', 'mon-laboratoire' ),
     352            'aucun' => _x( 'None', 'option-langage', 'mon-laboratoire' ),
     353        );
     354    }
     355
     356    /**
     357     * Get language configuration items for radio field.
     358     *
     359     * @param Translate           $translate          Translate instance.
     360     * @param Html                $html               HTML helper instance.
     361     * @param Polylang_Interface  $polylang_interface Polylang interface instance.
     362     * @return array<string,string> Array of language configuration options.
     363     * @access private
     364     */
     365    private function _get_language_config_items( $translate, $html, $polylang_interface ): array {
     366        return array(
     367            'en' => __( 'English', 'mon-laboratoire' ) . ' ' . $html->get_translation_flag( 'en' ),
     368            'fr' => __( 'French', 'mon-laboratoire' ) . ' ' . $html->get_translation_flag( 'fr' ),
     369            'browser' => __( 'Visitor\'s browser language', 'mon-laboratoire' ) . ' <small>(' . __( 'here', 'mon-laboratoire' )
     370                . ' : <em>' . $translate->get_browser_language() . '</em>)</small>',
     371            'Polylang' => __( 'Multilingual, using the Polylang translation plugin', 'mon-laboratoire' ) . ' <small>('
     372                . __( 'Polylang plugin status', 'mon-laboratoire' ) . ' : <em>'
     373                . $polylang_interface->get_polylang_plugin_status( 'translated' ) . '</em>)</small>',
     374            'WordPress' => sprintf(
     375                __( 'Language configured %1$s in WordPress %2$s or by a translation plugin', 'mon-laboratoire' ),
     376                '<a href="' . admin_url( 'options-general.php' ) . '">',
     377                '</a>'
     378            ) . ' <small>(' . __( 'currently', 'mon-laboratoire' ) . ' : <em>'
     379                . ( 'activated' === $polylang_interface->get_polylang_plugin_status()
     380                    ? __( 'defined by Polylang', 'mon-laboratoire' )
     381                    : get_locale()
     382                ) . '</em>)</small>',
     383        );
    318384    }
    319385
     
    328394    public function hidden_generic_render( array $args ): self {
    329395        echo $this->_forms_view->hidden_field(
    330             $args['settings_group'] . '[' . $args['option_name'] . ']' ,
     396            $args['settings_group'] . '[' . $args['option_name'] . ']',
    331397            strval( $this->_options[ $args['settings_group'] ][ $args['option_name'] ] )
    332398        );
     
    343409     *      - $args['disable'] : true if disabled form
    344410     *      - $args['on_change'] : js to launch if any change
    345      *      - $args['hide'] :    true if CSS display is hide
    346      * @return Options_Forms_View For method chaining.
     411     *      - $args['hide'] :    true if CSS display is hide
     412     * @return Options_Forms_View Returns self for method chaining.
    347413     */
    348414    public function text_field_generic_render( array $args ): self {  // @phan-suppress-current-line PhanUnreferencedPublicMethod
     
    361427     *      - $args['on_change'] : js to launch if any change
    362428     *      - $args['hide'] :    true if CSS display is hide
    363      * @return Options_Forms_View For method chaining.
     429     * @return Options_Forms_View Returns self for method chaining.
    364430     */
    365431    public function email_field_generic_render( array $args ): self {  // @phan-suppress-current-line PhanUnreferencedPublicMethod
     
    369435
    370436    /**
    371      * Display a email form field
     437     * Render a generic input field.
    372438     * Called with "add_field( 'text' ... "
    373439     * @param array{option_name:string,description:string,settings_group:string,disable:bool,hide:bool,on_change:string} $args Parameters :
    374      *      - $args['settings_group'] : Group of option to update
    375      *      - $args['option_name'] : Name of option to update
    376      *      - $args['description'] : Description to display
    377      *      - $args['disable'] : true if disabled form
     440     *      - $args['option_name'] : Name of option to update
     441     *      - $args['description'] : Description to display
     442     *      - $args['settings_group'] : Group of option to update
     443     *      - $args['disable'] : true if disabled form
     444     *      - $args['hide'] :    true if CSS display is hide
    378445     *      - $args['on_change'] : js to launch if any change
    379      *      - $args['hide'] :    true if CSS display is hide
    380      * @param string $type type of text field
    381      * @return Options_Forms_View For method chaining.
     446     * @param string $type Type of input field.
     447     * @return Options_Forms_View Returns self for method chaining.
    382448     */
    383449    private function _input_generic_render( array $args, string $type ): self {  // @phan-suppress-current-line PhanUnreferencedPublicMethod
    384450        $on_change = '';
    385         if ( !empty( $args['on_change'] ) ) {
     451        if ( ! empty( $args['on_change'] ) ) {
    386452            $on_change = sprintf( ' onchange="%s"', esc_attr( $args['on_change'] ) );
    387453        }
    388         $type = ( $args['disable'] ? 'hidden' : $type );
     454        $type      = ( $args['disable'] ? 'hidden' : $type );
    389455        $hideclass = '';
    390         if (
    391                 ( isset( $args['hide'] ) && $args['hide'] === true )
    392                 || ( isset( $args['disable'] ) && $args['disable'] === true )
    393             ) {
     456        if ( ( isset( $args['hide'] ) && true === $args['hide'] )
     457            || ( isset( $args['disable'] ) && true === $args['disable'] )
     458        ) {
    394459            $hideclass = ' MonLabo_hide';
    395460        }
     
    399464            esc_attr( $args['settings_group'] . '[' . $args['option_name'] . ']' ),
    400465            esc_attr( $this->_options[ $args['settings_group'] ][ $args['option_name'] ] ),
    401             $on_change,
     466            $on_change, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
    402467            wp_kses_post( $args['description'] )
    403468        );
     
    406471
    407472    /**
    408      * Display a quadruple text form field for translation in fr/en and singular/plural
     473     * Render four text fields for translation (fr/en, singular/plural).
    409474     * Called with "add_field( 'four_text_fields' ... "
    410475     * @param array{option_name:string,description:string,settings_group:string,disable:bool,hide:bool,class:string} $args Parameters :
     
    413478     *      - $args['disable'] : true if disabled form
    414479     *      - $args['hide'] :    true if CSS display is hide
    415      * @return Options_Forms_View For method chaining.
     480     * @return Options_Forms_View Returns self for method chaining.
    416481     */
    417482    public function four_text_fields_generic_render( array $args ): self {  // @phan-suppress-current-line PhanUnreferencedPublicMethod
    418483        $html = new Html();
    419484        $array_options = array(
    420             $args['option_name'] . '_en' => $html->get_translation_flag( 'en' ) . ' ' . __( 'English singular', 'mon-laboratoire' ),
    421             $args['option_name'] . '_fr' => $html->get_translation_flag( 'fr' ) . ' ' . __( 'French singular', 'mon-laboratoire' ),
     485            $args['option_name'] . '_en'  => $html->get_translation_flag( 'en' ) . ' ' . __( 'English singular', 'mon-laboratoire' ),
     486            $args['option_name'] . '_fr'  => $html->get_translation_flag( 'fr' ) . ' ' . __( 'French singular', 'mon-laboratoire' ),
    422487            $args['option_name'] . 's_en' => $html->get_translation_flag( 'en' ) . ' ' . __( 'English plural', 'mon-laboratoire' ),
    423488            $args['option_name'] . 's_fr' => $html->get_translation_flag( 'fr' ) . ' ' . __( 'French plural', 'mon-laboratoire' ),
     
    428493
    429494    /**
    430      * Display a double text form field for translation in fr/en
     495     * Render two text fields for translation (fr/en).
    431496     * Called with "add_field( 'two_text_fields' ... "
    432497     * @param array{option_name:string,description:string,settings_group:string,disable:bool,hide:bool,class:string} $args Parameters :
     
    435500     *      - $args['disable'] : true if disabled form
    436501     *      - $args['hide'] :    true if CSS display is hide
    437      * @return Options_Forms_View For method chaining.
     502     * @return Options_Forms_View Returns self for method chaining.
    438503     */
    439504    public function two_text_fields_generic_render( array $args ): self {  // @phan-suppress-current-line PhanUnreferencedPublicMethod
    440505        $html = new Html();
    441506        $array_options = array(
    442             $args['option_name'] . '_en' =>   $html->get_translation_flag( 'en' ) . ' ' . __( 'English', 'mon-laboratoire' ),
    443             $args['option_name'] . '_fr' =>   $html->get_translation_flag( 'fr' ) . ' ' . __( 'French', 'mon-laboratoire' ),
     507            $args['option_name'] . '_en' => $html->get_translation_flag( 'en' ) . ' ' . __( 'English', 'mon-laboratoire' ),
     508            $args['option_name'] . '_fr' => $html->get_translation_flag( 'fr' ) . ' ' . __( 'French', 'mon-laboratoire' ),
    444509        );
    445510        echo( $this->_generate_array_options_field( $array_options, $args['settings_group'],  $args['disable'], $args['hide'] ) );
     
    448513
    449514    /**
    450      * Generate a double text form field for translation in fr/en
     515     * Generate multiple text form fields.
    451516     * @param string[] $array_options List of each text options
    452517     * @param string $settings_group Group of option to update
     
    456521     * @access private
    457522     */
    458     private function _generate_array_options_field( array $array_options, string $settings_group,  bool $disable = false, bool $hide = false ): string {
     523    private function _generate_array_options_field( array $array_options, string $settings_group, bool $disable = false, bool $hide = false ): string {
    459524        if ( empty( $array_options ) ) {
    460525            return '';
    461526        }
    462         $options_DEFAULT = App::get_options_DEFAULT();
    463         $type = ( $disable ? 'hidden' : 'text' );
    464         $out = '';
    465         $hideclass = $hide ? ' MonLabo_hide' : '';
     527        $options_default = App::get_options_DEFAULT();
     528        $type            = ( $disable ? 'hidden' : 'text' );
     529        $out             = '';
     530        $hideclass       = $hide ? ' MonLabo_hide' : '';
    466531        if ( ! $disable ) {
    467532            $out .= '<table class="MonLabo-group-form' . $hideclass . '" role="presentation"><thead><tr>';
     
    479544                esc_attr( $this->_options[ $settings_group ][ $option_name ] )
    480545            );
    481             if ( ! $disable && ! empty( $options_DEFAULT[ $settings_group ][ $option_name ] ) ) {
     546            if ( ! $disable && ! empty( $options_default[ $settings_group ][ $option_name ] ) ) {
    482547                $out2 .= sprintf(
    483548                    'Ex: <em>%s</em>',
    484                     esc_html( (string) $options_DEFAULT[ $settings_group ][ $option_name ] )
     549                    esc_html( (string) $options_default[ $settings_group ][ $option_name ] )
    485550                );
    486551            }
    487552            $out2 .= ( $disable ? '' : '</td>' );
    488553        }
    489         $out .= $out2 . ( $disable ? '' : '</tr></tbody></table>' ) ;
     554        $out .= $out2 . ( $disable ? '' : '</tr></tbody></table>' );
    490555        return $out;
    491556    }
    492557
    493558    /**
    494      * Display an image choice form field
     559     * Render an image selector field.
    495560     * Called with "add_field( 'img_field' ... "
    496561     * @param array{option_name:string,description:string,settings_group:string,disable:bool,hide:bool,class:string} $args Parameters :
     
    498563     *      - $args['option_name'] : Name of option to update
    499564     *      - $args['disable'] : true if disabled form
    500      * @return Options_Forms_View For method chaining.
     565     * @return Options_Forms_View Returns self for method chaining.
    501566     */
    502567    public function img_field_generic_render( array $args ): self {  // @phan-suppress-current-line PhanUnreferencedPublicMethod
     
    506571            echo( '<ul>'
    507572                    . '<li><a href="#">'
    508                         . '<img width="60" height="60" id="image-preview" class="wp-image-8 alignleft img-arrondi wp-post-image" src="'
    509                         . $html->image_from_id_or_url( $this->_options[ $args['settings_group'] ][ $args['option_name'] ] )
    510                         . '" alt="' . esc_attr( $this->_options[ $args['settings_group'] ][ $args['option_name'] ] )
    511                         . '" />'
    512                     . '</a>' );
    513             echo( '</li></ul></div></div>' );
     573                    . '<img width="60" height="60" id="image-preview" class="wp-image-8 alignleft img-arrondi wp-post-image" src="'
     574                    . esc_url( $html->image_from_id_or_url( $this->_options[ $args['settings_group'] ][ $args['option_name'] ] ) )
     575                    . '" alt="' . esc_attr( $this->_options[ $args['settings_group'] ][ $args['option_name'] ] )
     576                    . '" />'
     577                . '</a></li></ul>' );
     578            echo( '</div></div>' );
    514579            printf(
    515580                '<input type="button" class="upload-image-button button" value="%s" onclick="MonLabo.imageMediaMenu(\'%s\', \'%s\', \'image-preview\', \'image_attachment_id\');" />',
     
    522587            $args['settings_group'] . '[' . $args['option_name'] . ']',
    523588            strval( $this->_options[ $args['settings_group'] ][ $args['option_name'] ] ),
    524             "image_attachment_id"
    525         );
    526         return $this;
    527     }
    528 
    529     /**
    530      * Display a color picker form field
     589            'image_attachment_id'
     590        );
     591        return $this;
     592    }
     593
     594    /**
     595     * Render a color picker field.
    531596     * Called with "add_field( 'color_picker' ... "
    532597     * @param array{option_name:string,description:string,settings_group:string,disable:bool,hide:bool,class:string} $args Parameters :
     
    535600     *      - $args['description'] : Description to display
    536601     *      - $args['disable'] : true if disabled form
    537      * @return Options_Forms_View  self For method chaining.
     602     * @return Options_Forms_View Returns self for method chaining.
    538603     */
    539604    public function color_picker_generic_render( array $args ): self {  // @phan-suppress-current-line PhanUnreferencedPublicMethod
     
    557622
    558623    /**
    559      * Display a text area form field
     624     * Render a textarea field.
    560625     * Called with "add_field( 'text_area' ... "
    561626     * @param array{option_name:string,description:string,settings_group:string,disable:bool,hide:bool,class:string} $args Parameters :
     
    564629     *      - $args['description'] : Description to display
    565630     *      - $args['disable'] : true if disabled form
    566      * @return Options_Forms_View self For method chaining.
     631     * @return Options_Forms_View Returns self for method chaining.
    567632     */
    568633    public function text_area_generic_render( array $args ): self {  // @phan-suppress-current-line PhanUnreferencedPublicMethod
     
    584649
    585650    /**
    586      * Display a button for empty cache
     651     * Render a button to clear cache.
    587652     * Called with "add_field( 'text_area' ... "
    588653     * @param array{option_name:string,description:string,settings_group:string,disable:bool,hide:bool,class:string} $args Parameters :
     
    591656     *      - $args['description'] : Description to display
    592657     *      - $args['disable'] : true if disabled form
    593      * @return Options_Forms_View For method chaining.
     658     * @return Options_Forms_View Returns self for method chaining.
    594659     */
    595660    public function button_clear_cache_generic_render( array $args ): self { // @phan-suppress-current-line PhanUnreferencedPublicMethod
     
    599664
    600665        $webservice = new Contact_Webservices();
    601         $options = get_option( $args['settings_group'] );
     666        $options    = get_option( $args['settings_group'] );
    602667        if ( ! empty( $options[ $args['option_name'] ] ) ) {
    603668            $webservice->clear_transients();
    604             echo( '<strong>'. __( 'Cache emptied!', 'mon-laboratoire' ) . ' </strong>' );
     669            echo( '<strong>' . __( 'Cache emptied!', 'mon-laboratoire' ) . '</strong>' );
    605670            unset( $options[ $args['option_name'] ] );
    606             update_option( $args['settings_group'] , $options );
    607             // Hide counter of cache without need to reload page
     671            update_option( $args['settings_group'], $options );
     672            // Hide counter of cache without need to reload page.
    608673            echo( '<script>'
    609674                . 'document.addEventListener("DOMContentLoaded", function() {'
     
    616681        }
    617682        $transient_count = $webservice->number_of_transient_entries();
    618 
    619         $hide_class = '';
    620         if ( 0 === $transient_count ) {
    621             $hide_class = 'MonLabo_hide';
    622         }
     683        $hide_class      = ( 0 === $transient_count ) ? 'MonLabo_hide' : '';
     684       
    623685        echo( $this->_forms_view->submit_button(
    624686            __( 'Clear publications cache', 'mon-laboratoire' ),
     
    630692            $hide_class
    631693        ));
    632         echo( ' ' . $transient_count . ' ' . wp_kses_post( $args['description'] ) );
    633         echo $this->_forms_view->hidden_field( $args['settings_group'] . '[' . $args['option_name'] . ']', "", $args['option_name'] . '_trigger' );
    634         return $this;
    635     }
    636 
    637     /**
    638      * Display a form for testing sending email
     694        echo( ' ' . strval( $transient_count ) . ' ' . wp_kses_post( $args['description'] ) );
     695        echo( $this->_forms_view->hidden_field(
     696                $args['settings_group'] . '[' . $args['option_name'] . ']',
     697            '',
     698            $args['option_name'] . '_trigger'
     699        ) );
     700        return $this;
     701    }
     702
     703    /**
     704     * Render a test email sending form.
    639705     * Called with "add_field( 'text_area' ... "
    640706     * @param array{option_name:string,description:string,settings_group:string,disable:bool,hide:bool,class:string} $args Parameters :
     
    644710     *      - $args['disable'] : true if disabled form
    645711     *      - $args['hide'] :    true if CSS display is hide
    646      * @return Options_Forms_View For method chaining.
     712     * @return Options_Forms_View Returns self for method chaining.
    647713     */
    648714    public function button_test_send_email_generic_render( array $args ): self { // @phan-suppress-current-line PhanUnreferencedPublicMethod
     
    650716            return $this->hidden_generic_render( $args );
    651717        }
    652         $hideclass = ( isset($args['hide']) && $args['hide'] === true ) ? ' MonLabo_hide' : '';
    653         echo('<div class="MonLabo_test_send_email' . $hideclass . '">');
     718        $hideclass = ( isset( $args['hide'] ) && true === $args['hide'] ) ? ' MonLabo_hide' : '';
     719        echo( '<div class="MonLabo_test_send_email' . esc_attr( $hideclass ) . '">');
    654720        $options = get_option( $args['settings_group'] );
    655721        if ( ! empty( $options[ $args['option_name'] ] ) ) {
    656             $to = $options['MonLabo_data_privacy_test_email'] ;
     722            $to      = $options['MonLabo_data_privacy_test_email'];
    657723            $subject = '[Test Mail from plugin Mon-Laboratoire of "' . get_bloginfo( 'name' ) . '"]';
    658724            $content = 'Success!!';
    659725            echo( '<strong>' );
    660             if ( ( ! filter_var( $to, FILTER_VALIDATE_EMAIL ) ) || ! wp_mail( $to, $subject, $content ) ) {
    661                 echo(  '<p>'. __( 'ERROR : The email could not be sent. Possible reason: your host may have disabled the PHP mail function.', 'mon-laboratoire' ) . '</p>');
    662                 echo(  '<p>'. __( 'Possible solution : Try to install a plugin for reconfiguring wp_mail() function like WP Mail SMTP.', 'mon-laboratoire' ) . ' <a href="https://wordpress.org/plugins/wp-mail-smtp/">WP Mail SMTP plugin page<a></a></p>');           
     726            if ( ! filter_var( $to, FILTER_VALIDATE_EMAIL ) || ! wp_mail( $to, $subject, $content ) ) {
     727                echo(  '<p>' . __( 'ERROR : The email could not be sent. Possible reason: your host may have disabled the PHP mail function.', 'mon-laboratoire' ) . '</p>' );
     728                echo(  '<p>' . __( 'Possible solution : Try to install a plugin for reconfiguring wp_mail() function like WP Mail SMTP.', 'mon-laboratoire' ) );
     729                echo( ' <a href="https://wordpress.org/plugins/wp-mail-smtp/">WP Mail SMTP plugin page<a></a></p>' );
    663730            } else {
    664                 printf( __( 'Mail sent to %s.', 'mon-laboratoire' ) , $to );
     731                printf( __( 'Mail sent to %s.', 'mon-laboratoire' ) , esc_html( $to ) );
    665732            }
    666733            echo( '</strong> ' );
    667734            unset( $options[ $args['option_name'] ] );
    668             update_option( $args['settings_group'] , $options );
     735            update_option( $args['settings_group'], $options );
    669736        }
    670737        echo( $this->_forms_view->submit_button(
    671             __( 'Send test email', 'mon-laboratoire' ) ,
     738            __( 'Send test email', 'mon-laboratoire' ),
    672739            $args['option_name'] . '_button',
    673740            'MonLabo.fillTriggerAndSubmit(\'' . esc_js( $args['option_name'] ) . '_trigger\')',
     
    676743            'button_' . $args['settings_group'] . '[' . $args['option_name'] . ']',
    677744            ''
    678         ));
     745        ) );
    679746        echo( wp_kses_post( $args['description'] ) . '</div>' );
    680         echo $this->_forms_view->hidden_field( $args['settings_group'] . '[' . $args['option_name'] . ']', "", $args['option_name'] . '_trigger' );
    681         return $this;
    682     }
    683 
    684     /**
    685      * Display a button for create default parent page
     747        echo( $this->_forms_view->hidden_field(
     748            $args['settings_group'] . '[' . $args['option_name'] . ']',
     749            '',
     750            $args['option_name'] . '_trigger'
     751        ) );
     752        return $this;
     753    }
     754
     755    /**
     756     * Render a button to create default parent page.
    686757     * Called with "add_field( 'text_area' ... "
    687758     * @param array{option_name:string,description:string,settings_group:string,disable:bool,hide:bool,class:string} $args Parameters :
     
    690761     *      - $args['description'] : Description to display
    691762     *      - $args['disable'] : true if disabled form
    692      * @return Options_Forms_View For method chaining.
     763     * @return Options_Forms_View Returns self for method chaining.
    693764     */
    694765    public function button_create_default_parent_page_generic_render( array $args ): self { // @phan-suppress-current-line PhanUnreferencedPublicMethod
     
    700771
    701772        if ( ! empty( $options[ $args['option_name'] ] ) ) {
    702             switch ( $args['option_name'] ) {
    703                 case 'MonLabo_do_create_perso_page_parent':
    704                     $page = new Wordpress_Page( 'person_parent' );
    705                     $options['MonLabo_perso_page_parent'] = $page->get_post_id();
    706                     break;
    707                 case 'MonLabo_do_create_team_page_parent':
    708                     $page = new Wordpress_Page( 'team_parent' );
    709                     $options['MonLabo_team_page_parent'] = $page->get_post_id();
    710                     break;
    711                 case 'MonLabo_do_create_thematic_page_parent':
    712                     $page = new Wordpress_Page( 'thematic_parent' );
    713                     $options['MonLabo_thematic_page_parent'] = $page->get_post_id();
    714                     break;
    715                 case 'MonLabo_do_create_unit_page_parent':
    716                     $page = new Wordpress_Page( 'unit_parent' );
    717                     $options['MonLabo_unit_page_parent'] = $page->get_post_id();
    718                     break;                                                         
    719             }
    720 
    721             echo( '<strong>'. __( 'Page created!' , 'mon-laboratoire' ) . ' </strong>' );
     773            $this->_create_parent_page( $args['option_name'], $options );
     774
     775            echo( '<strong>' . __( 'Page created!', 'mon-laboratoire' ) . '</strong>' );
    722776            $options[ $args['option_name'] ] = '';
    723             update_option( $args['settings_group'] , $options );
    724             echo('<script type="text/javascript">'
    725                 .'setTimeout(function() {'
     777            update_option( $args['settings_group'], $options );
     778            echo( '<script type="text/javascript">'
     779                . 'setTimeout(function() {'
    726780                .   'window.location.reload(1); ' // Force reload page
    727781                . '}, 500);'
    728                 . '</script>');
     782                . '</script>' );
    729783        }
    730784       
    731785        echo( $this->_forms_view->submit_button(
    732             $args['description'], 'submit_' . $args['option_name'],
     786            $args['description'],
     787            'submit_' . $args['option_name'],
    733788            'MonLabo.fillTriggerAndSubmit(\'' . esc_js( $args['option_name'] ) . '_trigger\')',
    734             '', 'primary',  'button_' . $args['settings_group'] . '[' . $args['option_name'] . ']', '',
     789            '',
     790            'primary',
     791            'button_' . $args['settings_group'] . '[' . $args['option_name'] . ']',
     792            '',
    735793            $args['disable']
    736794        ) );
    737         echo $this->_forms_view->hidden_field( $args['settings_group'] . '[' . $args['option_name'] . ']', "", $args['option_name'] . '_trigger' );
    738         return $this;
    739     }
    740 
    741     /**
    742      * Display a checkbox form field
     795        echo $this->_forms_view->hidden_field( $args['settings_group'] . '[' . $args['option_name'] . ']', '', $args['option_name'] . '_trigger' );
     796        return $this;
     797    }
     798
     799    /**
     800     * Create parent page based on option name.
     801     *
     802     * @param string $option_name Name of the option.
     803     * @param array<string,string>  &$options    Reference to options array.
     804     * @return void
     805     * @access private
     806     */
     807    private function _create_parent_page( string $option_name, array &$options ) {
     808        $page_mappings = array(
     809            'MonLabo_do_create_perso_page_parent'    => array( 'type' => 'person_parent', 'option' => 'MonLabo_perso_page_parent' ),
     810            'MonLabo_do_create_team_page_parent'     => array( 'type' => 'team_parent', 'option' => 'MonLabo_team_page_parent' ),
     811            'MonLabo_do_create_thematic_page_parent' => array( 'type' => 'thematic_parent', 'option' => 'MonLabo_thematic_page_parent' ),
     812            'MonLabo_do_create_unit_page_parent'     => array( 'type' => 'unit_parent', 'option' => 'MonLabo_unit_page_parent' ),
     813        );
     814
     815        if ( isset( $page_mappings[ $option_name ] ) ) {
     816            $page = new Wordpress_Page( $page_mappings[ $option_name ]['type'] );
     817            $options[ $page_mappings[ $option_name ]['option'] ] = $page->get_post_id();
     818        }
     819    }
     820
     821    /**
     822     * Render a checkbox field (style 2).
    743823     * Called with "add_field( 'checkbox2' ... "
    744824     * @param array{option_name:string,description:string,settings_group:string,disable:bool,hide:bool,class:string} $args Parameters :
     
    748828     *      - $args['disable'] : true if disabled form
    749829     *      - $args['hide'] :    true if CSS display is hide
    750      * @return Options_Forms_View For method chaining.
     830     * @return Options_Forms_View Returns self for method chaining.
    751831     */
    752832    public function checkbox2_generic_render( array $args ): self {  // @phan-suppress-current-line PhanUnreferencedPublicMethod
     
    754834            return $this;
    755835        }
    756         $hideclass = ( isset($args['hide']) && $args['hide'] === true ) ? ' MonLabo_hide' : '';
    757         echo( '<div class="checkbox2 ' . $args['option_name'] . $hideclass . '">' );
     836        $hideclass = ( isset( $args['hide'] ) && true === $args['hide'] ) ? ' MonLabo_hide' : '';
     837        echo( '<div class="checkbox2 ' . esc_attr( $args['option_name'] . $hideclass ) . '">' );
    758838        $input_id = uniqid( 'checkbox_' );
    759         $name = esc_attr( $args['settings_group'] . '[' . $args['option_name'] . ']' );
    760 
    761         echo( '<input type="checkbox" id="' . esc_attr( $input_id ) . '" name="' . $name . '"' );
     839        $name     = esc_attr( $args['settings_group'] . '[' . $args['option_name'] . ']' );
     840
     841        echo( '<input type="checkbox" id="' . esc_attr( $input_id ) . '" name="' . esc_attr( $name ) . '"' );
    762842        if ( isset( $this->_options[ $args['settings_group'] ][ $args['option_name'] ] ) ) {
    763             echo( " " . checked( $this->_options[ $args['settings_group'] ][ $args['option_name'] ], '1' , false ) );
     843            checked( $this->_options[ $args['settings_group'] ][ $args['option_name'] ], '1', true );
    764844        }
    765845        echo( ' value="1">' );
    766846        echo( '<label for="' . esc_attr( $input_id ) . '"><span class="ui">&nbsp;</span></label>' );
    767847        echo( '<p class="description_checkbox2">' );
    768         echo( $args['description'] );
     848        echo( wp_kses_post( $args['description'] ) );
    769849        echo( '</p></div>' );
    770850        return $this;
    771851    }
    772    
    773     /**
    774      * Display a checkbox form field
     852
     853    /**
     854     * Render a checkbox field.
    775855     * Called with "add_field( 'checkbox' ... "
    776856     * @param array{option_name:string,description:string,settings_group:string,disable:bool,hide:bool,class:string} $args Parameters :
     
    779859     *      - $args['description'] : Description to display
    780860     *      - $args['disable'] : true if disabled form
    781      * @return Options_Forms_View For method chaining.
     861     * @return Options_Forms_View Returns self for method chaining.
    782862     */
    783863    public function checkbox_generic_render( array $args ): self {  // @phan-suppress-current-line PhanUnreferencedPublicMethod
     
    787867        echo( '<div class="checkbox">' );
    788868        $input_id = uniqid( 'checkbox_' );
    789         $name = esc_attr( $args['settings_group'] . '[' . $args['option_name'] . ']' );
    790 
    791         echo( '<input type="checkbox" id="' . esc_attr( $input_id ) . '" name="' . $name . '"' );
     869        $name     = esc_attr( $args['settings_group'] . '[' . $args['option_name'] . ']' );
     870
     871        echo( '<input type="checkbox" id="' . esc_attr( $input_id ) . '" name="' . esc_attr( $name ) . '"' );
    792872        if ( isset( $this->_options[ $args['settings_group'] ][ $args['option_name'] ] ) ) {
    793             echo( " " . checked( $this->_options[ $args['settings_group'] ][ $args['option_name'] ], '1' , false ) );
     873            checked( $this->_options[ $args['settings_group'] ][ $args['option_name'] ], '1', true );
    794874        }
    795875        echo( ' value="1">' );
    796876        echo( '<label for="' . esc_attr( $input_id ) . '"><span class="ui">&nbsp;</span></label>' );
    797877        echo( '<p class="description_checkbox">' );
    798         echo( $args['description'] );
     878        echo( wp_kses_post( $args['description'] ) );
    799879        echo( '</p></div>' );
    800880        return $this;
     
    802882
    803883    /**
    804      * Display a hidden order to execute
     884     * Render a hidden order field.
    805885     * Called with "add_field( 'hidden_order' ... "
    806886     * @param array{option_name:string,description:string,settings_group:string,disable:bool,hide:bool,class:string} $args Parameters :
     
    808888     *      - $args['option_name'] : Name of option to update
    809889     *      - $args['disable'] : true if disabled form
    810      * @return Options_Forms_View For method chaining.
     890     * @return Options_Forms_View Returns self for method chaining.
    811891     */
    812892    public function hidden_order_generic_render( array $args ): self {  // @phan-suppress-current-line PhanUnreferencedPublicMethod
     
    814894            $this->hidden_generic_render( $args );
    815895        } else {
    816             /*
    817             ---- No more necessary, cash erase has been moved to a specialized page ----
    818             if ( isset( $this->_options[ $args['settings_group'] ][ $args['option_name'] ] )
    819                     &&  ( !empty( $this->_options[ $args['settings_group'] ][ $args['option_name'] ] ) )
    820             ) {
    821                     if ( 'MonLabo_ask_erase_cache_after_cfg' === $args['option_name'] ) {
    822                             $webservice = new Contact_Webservices();
    823                             $webservice->clear_transients();
    824                             unset( $this->_options[ $args['settings_group'] ][ $args['option_name'] ] );
    825                             update_option( $args['settings_group'], $this->_options[ $args['settings_group'] ]  );
    826                     }
    827             }*/
    828896            echo $this->_forms_view->hidden_field( $args['settings_group'] . '[' . $args['option_name'] . ']', "1"  );
    829897        }
     
    832900
    833901    /**
    834      * Display a number form field
     902     * Render a number input field.
    835903     * Called with "add_field( 'number' ... "
    836904     * @param array{option_name:string,description:string,settings_group:string,disable:bool,hide:bool,class:string} $args Parameters :
     
    839907     *      - $args['description'] : Description to display
    840908     *      - $args['disable'] : true if disabled form
    841      * @return Options_Forms_View For method chaining.
     909     * @return Options_Forms_View Returns self for method chaining.
    842910     */
    843911    public function number_generic_render( array $args ): self {  // @phan-suppress-current-line PhanUnreferencedPublicMethod
     
    855923
    856924    /**
    857      * Display a page choice form field
     925     * Render a page selector field.
    858926     * Called with "add_field( 'page' ... "
    859927     * @param array{option_name:string,description:string,settings_group:string,disable:bool,hide:bool,class:string} $args Parameters :
     
    862930     *      - $args['description'] : Description to display
    863931     *      - $args['disable'] : true if disabled form
    864      * @return Options_Forms_View For method chaining.
     932     * @return Options_Forms_View Returns self for method chaining.
    865933     */
    866934    public function select_page_generic_render( array $args ): self {  // @phan-suppress-current-line PhanUnreferencedPublicMethod
     
    869937        }
    870938
    871         //Convert Option name to number in order to be used by multi-pages functions
     939        // Convert option name to number in order to be used by multi-pages functions.
    872940        $name_to_number = array(
    873             'MonLabo_perso_page_parent'  => 1,
    874             'MonLabo_team_page_parent'    => 2,
    875             'MonLabo_thematic_page_parent'  => 3,
    876             'MonLabo_unit_page_parent'    => 4,
     941            'MonLabo_perso_page_parent'    => 1,
     942            'MonLabo_team_page_parent'     => 2,
     943            'MonLabo_thematic_page_parent' => 3,
     944            'MonLabo_unit_page_parent'     => 4,
    877945        );
    878946        if ( ! array_key_exists( $args['option_name'], $name_to_number ) ) {
    879947            return $this;
    880948        }
    881         $retval = '';
    882         $pages_published  = get_pages( );
     949        $pages_published = get_pages();
    883950
    884951        if ( ! empty( $pages_published ) ) {
    885             //Initial values
    886             $name = $args['settings_group'] . '[' . $args['option_name'] . ']';
    887             $initial_value = strval( $this->_options[ $args['settings_group'] ][ $args['option_name'] ] );
     952            // Initial values.
     953            $name              = $args['settings_group'] . '[' . $args['option_name'] . ']';
     954            $initial_value     = strval( $this->_options[ $args['settings_group'] ][ $args['option_name'] ] );
    888955            $multipost_forms_view = new Wp_Post_Forms_View();
    889             echo $multipost_forms_view->page_selector( $name, true, '', '', $initial_value, 0, 1, false );
    890             $nbpage = $name_to_number[ $args['option_name'] ] ;
    891             $retval .= '<div id="delayedLoadDivThumbnail_' . strval( $nbpage ) . '" class="delayedLoadDivThumbnail"><!-- Nous allons afficher ici la suite en asynchone grâce à ajax. --></div>' . "\n";
    892         }   
    893 
    894         echo $retval;
    895         return $this;
    896     }
    897 
     956            echo( $multipost_forms_view->page_selector( $name, true, '', '', $initial_value, 0, 1, false ) );
     957            $nbpage = $name_to_number[ $args['option_name'] ];
     958            printf(
     959                '<div id="delayedLoadDivThumbnail_%d" class="delayedLoadDivThumbnail"><!-- Nous allons afficher ici la suite en asynchone grâce à ajax. --></div>' . "\n",
     960                intval( $nbpage )
     961            );
     962        }
     963
     964        return $this;
     965    }
    898966}
    899967?>
  • mon-laboratoire/trunk/Admin/Forms/class-wp-post-forms-view.php

    r3361909 r3369834  
    55use MonLabo\Lib\Access_Data\{Access_Data};
    66use MonLabo\Admin\Wordpress_Page;
    7 use MonLabo\Frontend\{Person_Or_Structure_View};
     7use MonLabo\Frontend\Person_Or_Structure_View;
    88use MonLabo\Lib\Person_Or_Structure\Person_Or_Structure;
    99
     
    1111
    1212/**
    13  * Class \MonLabo\Admin\Wp_Post_Forms_View
    14  * Handles the generation of HTML form elements and controls for WordPress pages
     13 * Class \MonLabo\Admin\Forms\Wp_Post_Forms_View
     14 *
     15 * Handles the generation of HTML form elements for WordPress page management.
    1516 *
    16  * Methods :
    17  *   _page_radio_buttons( $page_number, $name, $description, $initial_value, $type )
    18  *   _default_hidden_zone( $text, $css_id )
    19  *   _input_wp_post_id_or_url( $is_mandatory, $legend, $name, $description, $initial_value, $page_number)
    20  *   _input_person_wp_post_id_or_url( $is_mandatory, $legend, $name, $description, $initial_value, $page_number)
    21  *    _dropdown_pages( string $name, string $wp_post_id)
    22  *   _page_thumbnail( $wp_post_id, $page_number )
    23  *  _tell_if_page_is_already_attributed( $type, $item_id, $wp_post_id )
    24  *   person_multi_post_addr_field( $is_mandatory,$legend, $name, $description, $initial_value , $css_id)
    25  *   multi_post_addr_field( $is_mandatory,$legend, $name, $description, $initial_value , $css_id)
    26  *   page_selector( $name, $is_mandatory, $legend,$description, $initial_value, $child_of, $depth, $show_drafts)
    27  *   update_page_infobox( $item_id, $page_number, $wp_post_id, $type )
     17 * This class extends Generic_Forms_View to provide specialized form controls
     18 * for managing WordPress pages associated with persons, teams, thematics,
     19 * and units. It includes methods for creating page selectors, thumbnails,
     20 * radio button controls, and multi-page field management.
     21 *
     22 * Methods:
     23 *     _default_hidden_zone( string $text, string $css_id )
     24 *     _page_radio_buttons( int $page_number, string $name, string $description,
     25 *                          string $initial_value, string $type )
     26 *     _determine_initial_radio_state( string $initial_value, string $type )
     27 *     _render_radio_buttons( int $page_number, string $radio_init )
     28 *     _input_person_wp_post_id_or_url( bool $is_mandatory, string $legend, string $name,
     29 *                                      string $description, string $initial_value, int $page_number )
     30 *     _input_wp_post_id_or_url( bool $is_mandatory, string $legend, string $name,
     31 *                               string $description, string $initial_value, int $page_number )
     32 *     _page_thumbnail( int $wp_post_id, int $page_number )
     33 *     _dropdown_pages( string $name, string $wp_post_id )
     34 *     _tell_if_page_is_already_attributed( string $type, int $item_id, $wp_post_id )
     35 *     person_multi_post_addr_field( bool $is_mandatory, string $legend, string $name,
     36 *                                   string $description, $initial_value, string $css_id )
     37 *     multi_post_addr_field( bool $is_mandatory, string $legend, string $name,
     38 *                            string $description, $initial_value, string $css_id )
     39 *     page_selector( string $name, bool $is_mandatory, string $legend, string $description,
     40 *                    string $initial_value, int $child_of, int $depth, bool $show_drafts )
     41 *     _generate_page_dropdown( string $name, string $initial_value, array $pages_published,
     42 *      array $pages_draft, int $depth, bool $show_drafts ):
     43 *     update_page_infobox( $item_id, int $page_number, $wp_post_id, string $type )
     44 *     _generate_page_infobox_content( string $wp_post_id, int $page_number, $item_id, string $type ): string
     45 *     _generate_page_info_for_numeric_id( int $wp_post_id, int $page_number, $item_id, string $type ): string
     46 *     _generate_page_info_for_url( string $wp_post_id )
    2847 *
    2948 * @package MonLabo\Admin\Forms
    30 */
     49 */
    3150class Wp_Post_Forms_View extends Generic_Forms_View {
    3251
    3352    /**
    34      * Generates hidden zone with default content.
    35      * @param string $text id of the person
    36      * @param string $css_id CSS ID of the showing button
    37      * @return string HTML code
    38      * @access private
    39      */
    40     private function _default_hidden_zone(
    41         string $text,
    42         string $css_id = ''
    43     ): string {
     53     * Generates a hidden HTML zone with default content.
     54     *
     55     * Creates a div element that is initially hidden and can be shown
     56     * dynamically via JavaScript interactions.
     57     *
     58     * @param  string $text   Content to be placed in the hidden zone.
     59     * @param  string $css_id HTML ID attribute for the div element. Default ''.
     60     * @return string         Generated HTML for the hidden zone.
     61     * @access private
     62     */
     63    private function _default_hidden_zone( string $text, string $css_id = '' ): string {
    4464        return sprintf(
    4565            '<div id="%1$s" style="display:none;">%2$s</div>',
     
    5070
    5171    /**
    52      * Generate an HTML code radio buttons for managing pages of Persons, Teams, Thematics or Units
    53      * @param int $page_number
    54      * @param string $name name of the form field
    55      * @param string $description text that is displayed after the field
    56      * @param string $initial_value initial value
    57      * @return string HTML code of form field
    58      * @param string $type of page 'person', 'team', 'thematic' or 'unit'
    59      * @access private
    60       */
     72     * Generates radio button controls for page management.
     73     *
     74     * Creates a set of radio buttons for creating, choosing, editing,
     75     * or removing page associations for persons, teams, thematics, or units.
     76     *
     77     * @param  int    $page_number   Index of the page field (for multiple pages).
     78     * @param  string $name          Field name attribute.
     79     * @param  string $description   Description text displayed with the radio buttons.
     80     * @param  string $initial_value Initial page ID or URL value.
     81     * @param  string $type          Entity type: 'person', 'team', 'thematic' or 'unit'.
     82     * @return string                Generated HTML for the radio button controls.
     83     * @access private
     84     */
    6185    private function _page_radio_buttons(
    6286        int $page_number,
     
    6589        string $initial_value,
    6690        string $type
    67     ) : string {
    68         $radio_name = sprintf( 'pageradio[%d]', $page_number );
    69         $radio_id   = sprintf( 'pageradio%d_', $page_number );
    70         $output  = sprintf(
     91    ): string {
     92        $output = sprintf(
    7193            '<div class="input-group MonLabo-wpPostId"><label for="submit_%1$s" class="radiobutton-label">%2$s&nbsp;:</label>',
    7294            esc_attr( $name ),
     
    7496        );
    7597
    76         $radio_init  = 'edit';
    77         $supl_choose_test = true;
    78 
     98        // Determine initial radio button state.
     99        $radio_init = $this->_determine_initial_radio_state( $initial_value, $type );
     100
     101        // Generate radio buttons.
     102        $output .= '<div class="radiobuttons input-group">';
     103        $output .= $this->_render_radio_buttons( $page_number, $radio_init );
     104        $output .= '</div>';
     105        return $output;
     106    }
     107
     108    /**
     109     * Determines the initial state for radio buttons.
     110     *
     111     * @param  string $initial_value Initial page ID or URL.
     112     * @param  string $type          Entity type.
     113     * @return string                Radio button state: 'new', 'choose', 'edit', or 'none'.
     114     * @access private
     115     */
     116    private function _determine_initial_radio_state( string $initial_value, string $type ): string {
     117        if ( '' === $initial_value ) {
     118            return 'none';
     119        }
     120        if ( ! is_numeric( $initial_value ) || ! get_post( intval( $initial_value ) ) ) {
     121            return 'edit';
     122        }
     123        // Additional check for person pages.
    79124        if ( 'person' === $type ) {
    80125            $page = new Wordpress_Page( 'from_id', $initial_value );
    81             $supl_choose_test = $page->is_a_person_page();
    82         }
    83 
    84         if ( is_numeric( $initial_value ) &&
    85             get_post( intval( $initial_value ) ) &&
    86             $supl_choose_test
    87         ) { //Si la page existe et est une page de personne
    88             $radio_init = 'choose';
    89         }
    90         if ( '' === $initial_value ) {
    91             $radio_init = 'none';
    92         }
    93 
    94         $radio_buttons = array(
    95             'A' => array( 'value' => 'new',     'label' => __( 'Create', 'mon-laboratoire' ),       'dashicon' => 'plus-alt2',      'class' => 'roundleft'  ),
    96             'B' => array( 'value' => 'choose',  'label' => __( 'Choose', 'mon-laboratoire' ),       'dashicon' => 'arrow-down-alt2'                         ),
    97             'C' => array( 'value' => 'edit',    'label' => __( 'Edit', 'mon-laboratoire' ),         'dashicon' => 'admin-customizer'                        ),
    98             'D' => array( 'value' => 'none',    'label' => _x( 'None', 'page', 'mon-laboratoire' ), 'dashicon' => 'no-alt',         'class' => 'roundright' ),
    99         );
    100         $output .= '<div class="radiobuttons input-group">';
    101         foreach ( $radio_buttons as $key => $button ) {
     126            if ( ! $page->is_a_person_page() ) {
     127                return 'edit';
     128            }
     129        }
     130        return 'choose';
     131    }
     132
     133    /**
     134     * Renders radio buttons HTML.
     135     *
     136     * @param  int    $page_number Page number for JavaScript.
     137     * @param  string $radio_init  Initially checked value.
     138     * @return string              Generated HTML for radio buttons.
     139     * @access private
     140     */
     141    private function _render_radio_buttons( int $page_number, string $radio_init ): string {
     142        $buttons = array(
     143            'A' => array( 'value'    => 'new',      'label'    => __( 'Create', 'mon-laboratoire' ),        'dashicon' => 'plus-alt2',      'class'    => 'roundleft' ),
     144            'B' => array( 'value'    => 'choose',   'label'    => __( 'Choose', 'mon-laboratoire' ),        'dashicon' => 'arrow-down-alt2' ),
     145            'C' => array( 'value'    => 'edit',     'label'    => __( 'Edit', 'mon-laboratoire' ),          'dashicon' => 'admin-customizer' ),
     146            'D' => array( 'value'    => 'none',     'label'    => _x( 'None', 'page', 'mon-laboratoire' ),  'dashicon' => 'no-alt',         'class'    => 'roundright' ),
     147        );
     148        $radio_name = sprintf( 'pageradio[%d]', $page_number );
     149        $radio_id   = sprintf( 'pageradio%d_', $page_number );
     150
     151        $output = '';
     152        foreach ( $buttons as $key => $button ) {
     153            // Generate radio input.
    102154            $output .= sprintf(
    103155                '<input type="radio" id="%1$s" name="%2$s" value="%3$s" onclick="MonLabo.pageButtonClick(\'%3$s\', \'%4$d\')" %5$s>',
    104156                esc_attr( $radio_id . $key ),
    105                 esc_attr( $radio_name ),
     157                $radio_name,
    106158                esc_attr( $button['value'] ),
    107159                $page_number,
    108160                checked( $radio_init, $button['value'], false )
    109161            );
     162            // Generate label.
     163            $label_class = ! empty( $button['class'] ) ? sprintf( ' class="%s"', esc_attr( $button['class'] ) ) : '';
    110164            $output .= sprintf(
    111165                '<label for="%1$s"%2$s>%3$s&nbsp;%4$s</label>',
    112166                esc_attr( $radio_id . $key ),
    113                 ! empty( $button['class'] ) ? sprintf( ' class="%s"', esc_attr( $button['class'] ) ) : '',
     167                $label_class,
    114168                esc_html( $button['label'] ),
    115169                $this->_html->dashicon( $button['dashicon'] )
    116170            );
    117171        }
    118         $output .= '</div>';
    119         return $output;
    120     }
    121 
    122 
    123     /**
    124      * Generate an HTML code for an input of wp_post_id or a URL
    125      * @param bool $is_mandatory if true, fill this field is mandatory.
    126      * @param string $legend text that is displayed just before the field
    127      * @param string $name name of the form field
    128      * @param string $description text that is displayed after the field
    129      * @param string $initial_value initial value
    130      * @param int $page_number
    131      * @return string HTML code of form field
     172        return $output;
     173    }
     174
     175    /**
     176     * Generates input field for person WordPress post ID or URL.
     177     *
     178     * Creates a specialized input field for person pages with parent page
     179     * selection from plugin settings.
     180     *
     181     * @param  bool   $is_mandatory   Whether the field is required.
     182     * @param  string $legend         Label text for the field.
     183     * @param  string $name           Field name attribute.
     184     * @param  string $description    Description text.
     185     * @param  string $initial_value  Initial page ID or URL. Default NO_PAGE_OPTION_VALUE.
     186     * @param  int    $page_number    Index for multiple page fields. Default 0.
     187     * @return string                 Generated HTML for the input field.
    132188     * @access private
    133189     */
     
    141197    ): string {
    142198        $options10 = get_option( 'MonLabo_settings_group10' );
     199       
     200        // Generate radio buttons.
    143201        $output = $this->_page_radio_buttons( $page_number, $name, $description, $initial_value, 'person' );
    144202        $output .= '<div class="input-group-addon">';
     203       
     204        // Add page selector if parent page is configured.
    145205        if ( isset( $options10['MonLabo_perso_page_parent'] ) ) {
     206            $parent_id = intval( $options10['MonLabo_perso_page_parent'] );
    146207            $output .= $this->_default_hidden_zone(
    147                     $this->page_selector( "dropdown_$name", true, '', '', $initial_value, intval( $options10['MonLabo_perso_page_parent'] ) ),
     208                    $this->page_selector( "dropdown_$name", true, '', '', $initial_value, $parent_id ),
    148209                    sprintf( 'hidd_drop_wp_post_ids_%d', $page_number )
    149210                );
    150211        }
    151212        $output .= '</div>';
     213        // Add text input field.
    152214        $output .= $this->_large_text_field( $is_mandatory, $legend, $name, '', $initial_value, sprintf( 'submit_wp_post_ids_%d', $page_number ) );
     215        // Add thumbnail container.
    153216        $output .= sprintf(
    154217            '<div id="delayedLoadDivThumbnail_%1$d" class="delayedLoadDivThumbnail"><!-- Asynchronous ajax content will be loaded here --></div></div>',
     
    158221    }
    159222
    160 
    161     /**
    162      * Generate an HTML code for an input of Wp_post_id or a URL
    163      * @param bool $is_mandatory if true, fill this field is mandatory.
    164      * @param string $legend text that is displayed just before the field
    165      * @param string $name name of the form field
    166      * @param string $description text that is displayed after the field
    167      * @param string $initial_value initial value
    168      * @param int $page_number
    169      * @return string HTML code of form field
     223    /**
     224     * Generates input field for WordPress post ID or URL.
     225     *
     226     * Creates a general input field for page selection with dropdown
     227     * for all available pages.
     228     *
     229     * @param  bool   $is_mandatory   Whether the field is required.
     230     * @param  string $legend         Label text for the field.
     231     * @param  string $name           Field name attribute.
     232     * @param  string $description    Description text.
     233     * @param  string $initial_value  Initial page ID or URL. Default NO_PAGE_OPTION_VALUE.
     234     * @param  int    $page_number    Index for multiple page fields. Default 0.
     235     * @return string                 Generated HTML for the input field.
    170236     * @access private
    171237     */
     
    178244        int $page_number = 0
    179245    ): string {
     246        // Generate radio buttons.
    180247        $output = $this->_page_radio_buttons( $page_number, $name, $description, $initial_value, 'item' );
    181248        $output .= '<div class="input-group-addon">';
     249       
     250        // Add dropdown page selector.
    182251        $output .= $this->_default_hidden_zone(
    183                 $this->_dropdown_pages( 'dropdown_' . $name, $initial_value ),
    184                 sprintf( 'hidd_drop_wp_post_ids_%d', $page_number )
    185             );
     252            $this->_dropdown_pages( 'dropdown_' . $name, $initial_value ),
     253            sprintf( 'hidd_drop_wp_post_ids_%d', $page_number )
     254        );
    186255        $output .= '</div>';
     256       
     257        // Add text input field.
    187258        $output .= $this->_large_text_field( $is_mandatory, $legend, $name, '', $initial_value, sprintf( 'submit_wp_post_ids_%d', $page_number ) );
     259       
     260        // Add thumbnail container.
    188261        $output .= sprintf(
    189262            '<div id="delayedLoadDivThumbnail_%1$d" class="delayedLoadDivThumbnail"><!-- Asynchronous ajax content will be loaded here --></div></div>',
    190263            $page_number
    191264        );
    192         return $output;
    193     }
    194 
    195     /**
    196      * Generate an HTML code for a thumbnail of a page of a person
    197      * @param int $wp_post_id ID of the post to display thumbnail
    198      * @param int $page_number number of the page (persons can have several pages)
    199      * @return string HTML code
     265       
     266        return $output;
     267    }
     268
     269    /**
     270     * Generates HTML for a page thumbnail.
     271     *
     272     * Creates a thumbnail preview for a WordPress post/page if it has
     273     * a featured image set.
     274     *
     275     * @param  int $wp_post_id   WordPress post/page ID.
     276     * @param  int $page_number  Index for multiple page fields.
     277     * @return string            Generated HTML for the thumbnail.
     278     * @access private
    200279     */
    201280    private function _page_thumbnail( int $wp_post_id, int $page_number ): string {
    202281        $output = '<a class="hover-zoom-square30-no-border">';
     282       
    203283        if ( $wp_post_id > 0 && has_post_thumbnail( $wp_post_id ) ) {
    204284            $thumbnail = get_the_post_thumbnail(
    205285                $wp_post_id,
    206286                array( 150, 150 ),
    207                 array( 'class' => '',  'id' => 'image-preview_' . $page_number )
     287                array( 'class' => '', 'id' => 'image-preview_' . $page_number )
    208288            );
    209             return $output . $thumbnail . '</a>';
    210         }
    211         return $output . '</a>';
    212     }
    213 
    214     /**
    215      * Generate an HTML code for a selecting a page
    216      * @param string $name name of the form field
    217      * @param string $wp_post_id id of page selected
    218      * @return string HTML code of form field
    219      * @access private
    220       */
    221     private function _dropdown_pages( string $name, string $wp_post_id): string {
     289            $output .= $thumbnail;
     290        }
     291       
     292        $output .= '</a>';
     293       
     294        return $output;
     295    }
     296
     297    /**
     298     * Generates a dropdown selector for WordPress pages.
     299     *
     300     * Creates a select element populated with all available WordPress pages.
     301     *
     302     * @param  string $name       Field name attribute.
     303     * @param  string $wp_post_id Currently selected page ID.
     304     * @return string             Generated HTML for the dropdown.
     305     * @access private
     306     */
     307    private function _dropdown_pages( string $name, string $wp_post_id ): string {
    222308        return wp_dropdown_pages(
    223309            array(
    224                 'name'            => $name ,
    225                 'echo'            => 0,
     310                'name'              => $name,
     311                'echo'              => 0,
    226312                'show_option_none'  => '&mdash;' . __( 'Select', 'mon-laboratoire' ) . '&mdash;',
    227313                'option_none_value' => '0',
    228                 'selected'        => is_numeric( $wp_post_id ) ? intval( $wp_post_id ) : '',
     314                'selected'          => is_numeric( $wp_post_id ) ? intval( $wp_post_id ) : '',
    229315            )
    230316        );
     
    232318
    233319    /**
    234      * Checks and indicates if a page is already attributed to other items.
    235      * @param string $type of page 'person', 'team', 'thematic' or 'unit'
    236      * @param int $item_id id of the item (person or stucture ID)
    237      * @param string|int|null $wp_post_id Page ID to test
    238      * @return string HTML message about page attribution.
     320     * Checks if a page is already attributed to other items.
     321     *
     322     * Verifies if a WordPress page is already assigned to other persons,
     323     * teams, thematics, or units and generates a warning message.
     324     *
     325     * @param  string         $type       Entity type: 'person', 'team', 'thematic' or 'unit'.
     326     * @param  int            $item_id    Current item ID.
     327     * @param  string|int|null $wp_post_id WordPress page ID to check.
     328     * @return string                     HTML message about page attribution conflicts.
     329     * @access private
    239330     */
    240331    private function _tell_if_page_is_already_attributed(
     
    242333        int $item_id,
    243334        $wp_post_id
    244         //$alternate_image = ''
    245335    ): string {
    246336        if ( empty( $wp_post_id ) ) {
     
    249339        $access_data = new Access_Data();
    250340        $other_items = array();
    251         $output   = '';
    252         $page_types = array( 'person', 'team', 'thematic', 'unit' );
    253         $item_view = new Person_Or_Structure_View();
     341        $page_types  = array( 'person', 'team', 'thematic', 'unit' );
     342        $item_view   = new Person_Or_Structure_View();
     343        // Check all entity types for page usage.
    254344        foreach ( $page_types as $check_type ) {
    255345            $item_class = '\MonLabo\Lib\Person_Or_Structure\\' . ucfirst( $check_type );
    256346            $page_items = $access_data->get_itemIds_from_wpPostId( $check_type, $wp_post_id );
    257347            foreach ( $page_items as $key => $value ) {
    258                 if ( $check_type !== $type || $value !== $item_id ) {
    259                     $item = new $item_class( 'from_id', $value );
    260                     if (  ! $item instanceof Person_Or_Structure ) {
    261                         wp_trigger_error( __METHOD__, "Object of class '$item_class' cannot be instantiated." );
    262                         continue;
    263                     }
    264                     $other_items[ $check_type ][ $key ] = $item_view->item_name_with_admin_link( $check_type, $item->info );
     348                // Skip if it's the current item.
     349                if ( $check_type === $type && $value === $item_id ) {
     350                    continue;
    265351                }
     352                $item = new $item_class( 'from_id', $value );
     353                if ( ! $item instanceof Person_Or_Structure ) {
     354                    wp_trigger_error( __METHOD__, "Object of class '$item_class' cannot be instantiated." );
     355                    continue;
     356                }
     357                $other_items[ $check_type ][ $key ] = $item_view->item_name_with_admin_link( $check_type, $item->info );
    266358            }
    267359        }
    268         if ( ! empty( $other_items ) ) {
    269             $output .= sprintf( '<br /><strong>%s </strong>', __( 'Attention this page is already assigned to', 'mon-laboratoire' ) );
    270             $item_types = array(
    271                 'person'    => __( 'Person(s):', 'mon-laboratoire' ),
    272                 'team'    => __( 'Team(s):', 'mon-laboratoire' ),
    273                 'thematic'  => __( 'Thematic(s):', 'mon-laboratoire' ),
    274                 'unit'    => __( 'Unit(s):', 'mon-laboratoire' ),
    275             );
    276             foreach ( $item_types as $item_type => $label ) {
    277                 if ( isset( $other_items[ $item_type ] ) ) {
    278                     $output .= sprintf( '<br />%s %s', esc_html( $label ), wp_kses_post( Lib::secured_implode( ', ', $other_items[ $item_type ] ) ) );
    279                 }
     360        // Generate warning message if page is used elsewhere.
     361        if ( empty( $other_items ) ) {
     362            return '';
     363        }
     364        $output = sprintf(
     365            '<br /><strong>%s </strong>',
     366            __( 'Attention this page is already assigned to', 'mon-laboratoire' )
     367        );
     368        $item_labels = array(
     369            'person'   => __( 'Person(s):', 'mon-laboratoire' ),
     370            'team'     => __( 'Team(s):', 'mon-laboratoire' ),
     371            'thematic' => __( 'Thematic(s):', 'mon-laboratoire' ),
     372            'unit'     => __( 'Unit(s):', 'mon-laboratoire' ),
     373        );
     374        foreach ( $item_labels as $item_type => $label ) {
     375            if ( isset( $other_items[ $item_type ] ) ) {
     376                $output .= sprintf( '<br />%s %s', esc_html( $label ), wp_kses_post( Lib::secured_implode( ', ', $other_items[ $item_type ] ) ) );
    280377            }
    281378        }
     
    284381
    285382    /**
    286      * Generates person multi-post address field.
    287      * @param bool $is_mandatory if true, fill this field is mandatory.
    288      * @param string $legend text that is displayed just before the field
    289      * @param string $name name of the form field
    290      * @param string $general_description text that is displayed after the field
    291      * @param string|string[] $initial_value initial value
    292      * @param string $css_id CSS id for the <input> tag.
    293      * @return string HTML code of form field
     383     * Generates multi-page address fields for persons.
     384     *
     385     * Creates multiple page input fields specifically for person entities,
     386     * with special handling for main and additional pages.
     387     *
     388     * @param  bool              $is_mandatory        Whether at least one page is required.
     389     * @param  string            $legend              Label text for the field group.
     390     * @param  string            $name                Base field name attribute.
     391     * @param  string            $general_description General description for the field group.
     392     * @param  string|string[]   $initial_value       Initial page IDs or URLs. Default empty array.
     393     * @param  string            $css_id              CSS ID for the wrapper div. Default ''.
     394     * @return string                                 Generated HTML for the multi-page fields.
    294395     * @access public
    295       */
    296       public function person_multi_post_addr_field(
     396     */
     397    public function person_multi_post_addr_field(
    297398        bool $is_mandatory,
    298399        string $legend,
     
    302403        string $css_id = ''
    303404    ): string {
    304         $output  = $css_id ? sprintf( '<div class="%s">', esc_attr( $css_id ) ) : '';
    305         $output .= $general_description ? sprintf( '<p>%s</p>', esc_html( $general_description ) ) : '';
     405        // Open wrapper if CSS ID provided.
     406        $output = ! empty( $css_id ) ? sprintf( '<div class="%s">', esc_attr( $css_id ) ) : '';
     407        // Add general description if provided.
     408        if ( ! empty( $general_description ) ) {
     409            $output .= sprintf( '<p>%s</p>', esc_html( $general_description ) );
     410        }
     411        // Ensure initial value is array.
    306412        if ( ! is_array( $initial_value ) ) {
    307413            $initial_value = explode( ',', $initial_value ); //Retrocompatibily in case of damaged DB
     
    310416            $initial_value = array( '' );
    311417        }
     418        // Generate existing page fields.
    312419        $count = 0;
    313420        foreach ( $initial_value as $value ) {
    314             if ( ! empty( $value ) ) {
    315                 $output .= $this->_input_person_wp_post_id_or_url(
    316                     $is_mandatory,
    317                     '',
    318                     $name . '[' . $count . ']',
    319                     $count ? sprintf( __( 'Page %d', 'mon-laboratoire' ), $count + 1 ) : __( 'Main page', 'mon-laboratoire' ),
    320                     $value,
    321                     $count
    322                 ) . '<br />';
    323                 $count += 1;
     421            if ( empty( $value ) ) {
     422                continue;
    324423            }
    325         }
    326         $description = $count ? __( 'Additional page', 'mon-laboratoire' ) : __( 'Main page', 'mon-laboratoire' );
    327         $output .= $this->_input_person_wp_post_id_or_url(  $is_mandatory, $legend, $name . '[' . $count . ']',
    328                  $description, '', $count );
    329         $output .= $css_id ? '</div>' : '';
    330         return $output;
    331     }
    332 
    333 
    334     /**
    335      * Generates multi-post address field.
    336      * @param bool $is_mandatory if true, fill this field is mandatory.
    337      * @param string $legend text that is displayed just before the field
    338      * @param string $name name of the form field
    339      * @param string $general_description text that is displayed after the field
    340      * @param string|string[] $initial_value initial value
    341      * @param string $css_id CSS id for the <input> tag.
    342      * @return string HTML code of form field
    343      * @access public
    344       */
    345       public function multi_post_addr_field(
     424            $description = 0 === $count
     425                ? __( 'Main page', 'mon-laboratoire' )
     426                : sprintf( __( 'Page %d', 'mon-laboratoire' ), $count + 1 );
     427            $output .= $this->_input_person_wp_post_id_or_url(
     428                $is_mandatory,
     429                '',
     430                $name . '[' . $count . ']',
     431                $description,
     432                $value,
     433                $count
     434            ) . '<br />';
     435            $count++;
     436        }
     437        // Add field for new page.
     438        $description = 0 === $count
     439            ? __( 'Main page', 'mon-laboratoire' )
     440            : __( 'Additional page', 'mon-laboratoire' );
     441        $output .= $this->_input_person_wp_post_id_or_url(
     442            $is_mandatory,
     443            $legend,
     444            $name . '[' . $count . ']',
     445            $description,
     446            '',
     447            $count
     448        );
     449        // Close wrapper if CSS ID provided.
     450        if ( ! empty( $css_id ) ) {
     451            $output .= '</div>';
     452        }
     453        return $output;
     454    }
     455
     456    /**
     457     * Generates multi-page address fields for general items.
     458     *
     459     * Creates multiple page input fields for teams, thematics, and units,
     460     * with numbered page labels.
     461     *
     462     * @param  bool              $is_mandatory        Whether at least one page is required.
     463     * @param  string            $legend              Label text for the field group.
     464     * @param  string            $name                Base field name attribute.
     465     * @param  string            $general_description General description for the field group.
     466     * @param  string|string[]   $initial_value       Initial page IDs or URLs. Default empty array.
     467     * @param  string            $css_id              CSS ID for the wrapper div. Default ''.
     468     * @return string                                 Generated HTML for the multi-page fields.
     469     */
     470    public function multi_post_addr_field(
    346471        bool $is_mandatory,
    347472        string $legend,
     
    351476        string $css_id = ''
    352477    ): string {
    353         $output  = $css_id ? sprintf( '<div class="%s">', esc_attr( $css_id ) ) : '';
    354         $output .= $general_description ? sprintf( '<p>%s</p>', esc_html( $general_description ) ) : '';
     478        // Open wrapper if CSS ID provided.
     479        $output = ! empty( $css_id ) ? sprintf( '<div class="%s">', esc_attr( $css_id ) ) : '';
     480        // Add general description if provided.
     481        if ( ! empty( $general_description ) ) {
     482            $output .= sprintf( '<p>%s</p>', esc_html( $general_description ) );
     483        }
     484        // Ensure initial value is array.
    355485        if ( ! is_array( $initial_value ) ) {
    356486                $initial_value = explode( ',', $initial_value ); //Retrocompatibily in case of damaged DB
     
    359489                $initial_value = array( '' );
    360490        }
     491        // Generate existing page fields.
    361492        $count = 0;
    362493        foreach ( $initial_value as $value ) {
    363                 if ( ! empty( $value ) ) {
    364                         $output .= $this->_input_wp_post_id_or_url(
    365                                 $is_mandatory,
    366                                 '',
    367                                 $name . '[' . $count . ']',
    368                                 sprintf( __( 'Page %d', 'mon-laboratoire' ), $count + 1 ),
    369                                 $value,
    370                                 $count
    371                 ) . '<br />';
    372                         $count += 1;
    373                 }
    374         }
    375         $description = $count ? __( 'Additional page', 'mon-laboratoire' ) : sprintf( __( 'Page %d', 'mon-laboratoire' ), 1 );
    376         $output .= $this->_input_wp_post_id_or_url( $is_mandatory, $legend, $name . '[' . $count . ']', $description, '', $count );
    377         $output .= $css_id ? '</div>' : '';
    378         return $output;
    379     }
    380 
    381     /**
    382      * Generate an HTML code for a drop-down list form field of WordPress pages
    383      * @param string $name name of the  drop-down list
    384      * @param bool $is_mandatory if true, select a page is mandatory.
    385      * @param string $legend text that is displayed just before the field
    386      * @param string $description text that is displayed after the field
    387      * @param string $initial_value Pre-select an item of the list
    388      * @param int $child_of Displays the sub-pages of a single Page only; uses the ID for a Page as the value. Defaults to 0 (displays all Pages ).
    389      * @param int $depth This parameter controls how many levels in the hierarchy of pages are to be included in the list generated by wp_list_pages. The default value is 0 (display all pages, including all sub-pages ).
    390      *  0 - Pages and sub-pages displayed in hierarchical ( indented ) form ( Default ).
    391      *  -1 - Pages in sub-pages displayed in flat (no indent ) form.
    392      *  1 - Show only top level Pages
    393      *  2 - Value of 2 (or greater) specifies the depth (or level) to descend in displaying Pages.
    394      * @param bool $show_drafts if true, show draft pages
    395      * @return string HTML code
     494            if ( empty( $value ) ) {
     495                continue;
     496            }
     497            $output .= $this->_input_wp_post_id_or_url(
     498                    $is_mandatory,
     499                    '',
     500                    $name . '[' . $count . ']',
     501                    sprintf( __( 'Page %d', 'mon-laboratoire' ), $count + 1 ),
     502                    $value,
     503                    $count
     504            ) . '<br />';
     505            $count++;
     506        }
     507        // Add field for new page.
     508        $description = $count > 0
     509            ? __( 'Additional page', 'mon-laboratoire' )
     510            : sprintf( __( 'Page %d', 'mon-laboratoire' ), 1 );
     511        $output .= $this->_input_wp_post_id_or_url(
     512            $is_mandatory,
     513            $legend,
     514            $name . '[' . $count . ']',
     515            $description,
     516            '',
     517            $count
     518        );
     519        // Close wrapper if CSS ID provided.
     520        if ( ! empty( $css_id ) ) {
     521            $output .= '</div>';
     522        }
     523        return $output;
     524    }
     525
     526    /**
     527     * Generates a hierarchical page selector dropdown.
     528     *
     529     * Creates a select element with WordPress pages organized hierarchically,
     530     * optionally including draft pages and filtering by parent page.
     531     *
     532     * @param  string $name          Field name attribute.
     533     * @param  bool   $is_mandatory  Whether page selection is required.
     534     * @param  string $legend        Label text for the field.
     535     * @param  string $description   Description text displayed after the field.
     536     * @param  string $initial_value Initially selected page ID. Default NO_PAGE_OPTION_VALUE.
     537     * @param  int    $child_of      Parent page ID to filter by. Default 0 (show all).
     538     * @param  int    $depth         Hierarchy depth to display. Default 1.
     539     *                               0  = Hierarchical display (indented).
     540     *                               -1 = Flat display (no indent).
     541     *                               1  = Top level only.
     542     *                               2+ = Specific depth level.
     543     * @param  bool   $show_drafts   Whether to include draft pages. Default true.
     544     * @return string                Generated HTML for the page selector.
    396545     */
    397546    public function page_selector(
     
    405554        bool $show_drafts = true
    406555    ): string {
     556        // Normalize child_of parameter.
    407557        if ( App::NO_PAGE_OPTION_VALUE == $child_of || empty( $child_of ) ) { //No strinct comparison between string and int
    408558            $child_of = 0;
    409559        }
     560       
     561        // Open wrapper and add label.
    410562        $output = '<div class="input-group">';
    411563        $output .= $this->_maybe_add_field_label( $name, $legend, $is_mandatory );
    412564        $output .= '<div class="input-group-addon"></div>';
    413         $pages_published = get_pages( array( 'child_of' =>  $child_of ) );
    414         $pages_draft  = $show_drafts ? get_pages( array( 'child_of' =>  $child_of, 'post_status' => 'draft' ) ) : array();
     565       
     566        // Get published and draft pages.
     567        $pages_published = get_pages( array( 'child_of' => $child_of ) );
     568        $pages_draft = $show_drafts
     569            ? get_pages( array( 'child_of' => $child_of, 'post_status' => 'draft' ) )
     570            : array();
     571       
     572        // Generate dropdown if pages exist.
    415573        if ( ! empty( $pages_published ) || ! empty( $pages_draft ) ) {
    416             $args =    array(
    417                 'value_field'   => 'ID',
    418                 'selected'      => $initial_value,
     574            $output .= $this->_generate_page_dropdown(
     575                $name,
     576                $initial_value,
     577                $pages_published,
     578                $pages_draft,
     579                $depth,
     580                $show_drafts
    419581            );
    420             $output .= sprintf( '<select name="%1$s" id="%1$s">', esc_attr( $name ) ) . "\n";
     582        }
     583        $output .= $this->description( $description ) . '</div>';
     584        return $output;
     585    }
     586
     587
     588    /**
     589     * Generates the page dropdown HTML.
     590     *
     591     * @param  string          $name            Field name.
     592     * @param  string          $initial_value   Selected value.
     593     * @param  \WP_Post[]|false $pages_published Published pages.
     594     * @param  \WP_Post[]|false $pages_draft     Draft pages.
     595     * @param  int             $depth           Hierarchy depth.
     596     * @param  bool            $show_drafts     Whether to show drafts.
     597     * @return string                           Generated HTML for dropdown.
     598     * @access private
     599     */
     600    private function _generate_page_dropdown(
     601        string $name,
     602        string $initial_value,
     603        $pages_published,
     604        $pages_draft,
     605        int $depth,
     606        bool $show_drafts
     607    ): string {
     608        $args = array(
     609            'value_field' => 'ID',
     610            'selected'    => $initial_value,
     611        );
     612        $output = sprintf( '<select name="%1$s" id="%1$s">', esc_attr( $name ) ) . "\n";
     613        // Add "No page" option.
     614        $output .= sprintf(
     615            "\t" . '<option value="%1$s"%2$s>&mdash; %3$s &mdash; </option>' . "\n",
     616            App::NO_PAGE_OPTION_VALUE,
     617            selected( App::NO_PAGE_OPTION_VALUE, $initial_value, false ),
     618            __( 'No page', 'mon-laboratoire' )
     619        );
     620        // Add published pages.
     621        if ( ! empty( $pages_published ) ) {
     622            if ( $show_drafts ) {
     623                $output .= sprintf( "\t<optgroup label=\"%s\">\n", __( 'Pages published', 'mon-laboratoire' ) );
     624            }
     625            $output .= walk_page_dropdown_tree( $pages_published, $depth, $args );
     626            if ( $show_drafts ) {
     627                $output .= "\t</optgroup>\n";
     628            }
     629        }
     630        // Add draft pages if enabled.
     631        if ( $show_drafts && ! empty( $pages_draft ) ) {
     632            // @codeCoverageIgnoreStart
     633            // Not possible to unit test draft pages - WP checks admin mode.
    421634            $output .= sprintf(
    422                 "\t" . '<option value="%1$s"%2$s>&mdash; %3$s &mdash; </option>' . "\n",
    423                 App::NO_PAGE_OPTION_VALUE,
    424                 selected( App::NO_PAGE_OPTION_VALUE, $initial_value, false ),
    425                 __( 'No page', 'mon-laboratoire' )
     635                "\t<optgroup label=\"%s\">\n%s\t</optgroup>\n",
     636                __( 'Draft pages', 'mon-laboratoire' ),
     637                walk_page_dropdown_tree( $pages_draft, $depth, $args )
    426638            );
    427             if ( ! empty( $pages_published ) ) {
    428                 if ( $show_drafts ) {
    429                     $output .= sprintf( "\t<optgroup label=\"%s\">\n", __( 'Pages published', 'mon-laboratoire' ) );
    430                 }
    431                 $output .= walk_page_dropdown_tree( $pages_published, $depth, $args );
    432                 if ( $show_drafts ) {
    433                     $output .= "\t</optgroup>\n";
    434                 }
    435             }
    436             if ( $show_drafts && ! empty( $pages_draft ) ) {
    437                 // @codeCoverageIgnoreStart
    438                 // Not possible to unit test a draft page because WP test if this is admin mode to enable this code
    439                 $output .= sprintf(
    440                     "\t<optgroup label=\"%s\">\n%s\t</optgroup>\n",
    441                     __( 'Draft pages', 'mon-laboratoire' ),
    442                     walk_page_dropdown_tree( $pages_draft, $depth, $args )
    443                 );
    444                 // @codeCoverageIgnoreEnd
    445             }
    446             $output .= "</select>\n";
    447         }
    448         $output .= $this->description( $description ) . '</div>';
    449         return $output;
    450     }
    451 
    452     /**
    453      * Generates HTML for page infobox and thumbnail display.
    454      * @param int|null $item_id id of the item
    455      * @param int $page_number number of the page (persons can have several pages)
    456      * @param string|int|null $wp_post_id
    457      * @param string $type of item : 'person', 'team', 'thematic' or 'unit'
    458      * @return string ex: array( $id => $value )
     639            // @codeCoverageIgnoreEnd
     640        }
     641        $output .= "</select>\n";
     642        return $output;
     643    }
     644
     645    /**
     646     * Generates HTML for page information box and thumbnail display.
     647     *
     648     * Creates an information box showing page status, thumbnail preview,
     649     * edit links, and attribution warnings for a selected page.
     650     *
     651     * @param  int|null        $item_id     Item ID (person or structure).
     652     * @param  int             $page_number Index for multiple page fields.
     653     * @param  string|int|null $wp_post_id  WordPress page ID, URL, or special value.
     654     * @param  string          $type        Entity type: 'person', 'team', 'thematic' or 'unit'. Default 'person'.
     655     * @return string                       Generated HTML for the page information box.
    459656     * @SuppressWarnings(PHPMD.ElseExpression)
    460657     */
     
    465662        string $type = 'person'
    466663    ): string {
    467         if ( empty( $wp_post_id ) ) { $wp_post_id = App::NEW_PAGE_OPTION_VALUE; }
    468         if ( is_integer( $wp_post_id ) ) { $wp_post_id = strval( $wp_post_id ); }
    469         $output = '';
    470         $html   = '';
    471 
    472         switch ( $wp_post_id ) {
    473             case App::NO_PAGE_OPTION_VALUE: // Person with no page - nothing to display
    474                 break;
    475 
    476             case App::NEW_PAGE_OPTION_VALUE: // New item - nothing to display
    477                 break;
    478 
    479             default: // Item with valid page
    480                 $is_numeric_wp_post_id = ( (string) abs( intval( $wp_post_id ) ) ) ===  $wp_post_id;
    481                 if ( $is_numeric_wp_post_id && get_post( intval( $wp_post_id ) ) ) { //If page exists
    482                     $wp_post_id = intval( $wp_post_id );
    483                     $post_status = get_post_status( $wp_post_id );
    484                     if ( 'draft' === $post_status ) {
    485                         $html .= sprintf( '<fieldset><legend>%s</legend>', __( 'Draft page', 'mon-laboratoire' ) );
    486                     }
    487                     $html .= $this->_page_thumbnail( $wp_post_id, $page_number );
    488                     $polylang_interface = new Polylang_Interface();
    489                     $html .= $this->description(
    490                         $polylang_interface->get_edit_link_if_exists( $wp_post_id )
    491                         . $this->_tell_if_page_is_already_attributed( $type, $item_id, $wp_post_id )
    492                     );
    493                     if ( 'draft' === $post_status ) {
    494                         $html .= '</fieldset>';
    495                     }
    496                 } else {
    497                     $is_valid_url = filter_var( $wp_post_id, FILTER_VALIDATE_URL );
    498                     if ( $is_numeric_wp_post_id ) {
    499                         $html .= '<p>' .  sprintf( __( 'The page #%s does not exist', 'mon-laboratoire' ), $wp_post_id ) . '</p>';
    500                     } elseif ( false === $is_valid_url ) {
    501                         $html .= '<p>' .  sprintf( __( 'URL %s is invalid', 'mon-laboratoire' ), $wp_post_id ) . '</p>';
    502                     }
    503                 }
    504                 break;
    505         }
    506         if ( ! empty( $html ) ) {
    507             $output = sprintf( '<div class="input-group">%s</div>', $html );
    508         }
    509         return $output;
    510     }
    511 
     664        // Normalize wp_post_id.
     665        if ( empty( $wp_post_id ) ) {
     666            $wp_post_id = App::NEW_PAGE_OPTION_VALUE;
     667        }
     668        if ( is_integer( $wp_post_id ) ) {
     669            $wp_post_id = strval( $wp_post_id );
     670        }
     671        // Handle special page values.
     672        if ( App::NO_PAGE_OPTION_VALUE === $wp_post_id || App::NEW_PAGE_OPTION_VALUE === $wp_post_id ) {
     673            return '';
     674        }
     675
     676        // Generate infobox content.
     677        $html = $this->_generate_page_infobox_content( $wp_post_id, $page_number, $item_id, $type );
     678
     679        if ( empty( $html ) ) {
     680            return '';
     681        }
     682        return sprintf( '<div class="input-group">%s</div>', $html );
     683    }
     684
     685    /**
     686     * Generates the content for page information box.
     687     *
     688     * @param  string   $wp_post_id  WordPress page ID or URL.
     689     * @param  int      $page_number Page field index.
     690     * @param  int|null $item_id     Item ID.
     691     * @param  string   $type        Entity type.
     692     * @return string                Generated HTML content.
     693     * @access private
     694     */
     695    private function _generate_page_infobox_content(
     696        string $wp_post_id,
     697        int $page_number,
     698        $item_id,
     699        string $type
     700    ): string {
     701        $is_numeric_id = ( (string) abs( intval( $wp_post_id ) ) ) === $wp_post_id;
     702        // Handle numeric post ID.
     703        if ( $is_numeric_id ) {
     704            return $this->_generate_page_info_for_numeric_id(
     705                intval( $wp_post_id ),
     706                $page_number,
     707                $item_id,
     708                $type
     709            );
     710        }
     711        // Handle URL or invalid input.
     712        return $this->_generate_page_info_for_url( $wp_post_id );
     713    }
     714
     715    /**
     716     * Generates page info for numeric WordPress post ID.
     717     *
     718     * @param  int      $wp_post_id  WordPress post ID.
     719     * @param  int      $page_number Page field index.
     720     * @param  int|null $item_id     Item ID.
     721     * @param  string   $type        Entity type.
     722     * @return string                Generated HTML.
     723     * @access private
     724     */
     725    private function _generate_page_info_for_numeric_id(
     726        int $wp_post_id,
     727        int $page_number,
     728        $item_id,
     729        string $type
     730    ): string {
     731        // Check if page exists.
     732        if ( ! get_post( $wp_post_id ) ) {
     733            return '<p>' . sprintf( __( 'The page #%s does not exist', 'mon-laboratoire' ), $wp_post_id ) . '</p>';
     734        }
     735        $html = '';
     736        $post_status = get_post_status( $wp_post_id );
     737        // Add draft indicator if applicable.
     738        if ( 'draft' === $post_status ) {
     739            $html .= sprintf( '<fieldset><legend>%s</legend>', __( 'Draft page', 'mon-laboratoire' ) );
     740        }
     741        // Add thumbnail.
     742        $html .= $this->_page_thumbnail( $wp_post_id, $page_number );
     743        // Add edit link and attribution warnings.
     744        $polylang_interface = new Polylang_Interface();
     745        $html .= $this->description(
     746            $polylang_interface->get_edit_link_if_exists( $wp_post_id )
     747            . $this->_tell_if_page_is_already_attributed( $type, $item_id, $wp_post_id )
     748        );
     749        // Close draft fieldset if applicable.
     750        if ( 'draft' === $post_status ) {
     751            $html .= '</fieldset>';
     752        }
     753        return $html;
     754    }
     755
     756    /**
     757     * Generates page info for URL input.
     758     *
     759     * @param  string $wp_post_id URL or invalid input.
     760     * @return string             Generated HTML.
     761     * @access private
     762     */
     763    private function _generate_page_info_for_url( string $wp_post_id ): string {
     764        $is_valid_url = filter_var( $wp_post_id, FILTER_VALIDATE_URL );
     765        if ( false === $is_valid_url ) {
     766            return '<p>' . sprintf(
     767                __( 'URL %s is invalid', 'mon-laboratoire' ),
     768                esc_html( $wp_post_id )
     769            ) . '</p>';
     770        }
     771        return '';
     772    }
    512773}
    513774?>
  • mon-laboratoire/trunk/Admin/class-admin-menu.php

    r3355231 r3369834  
    312312        echo( $this->_get_title( esc_html( get_admin_page_title() ) ) );
    313313        settings_errors();
    314         echo( '<form method="POST" id="MonLabo_admin_options" class="MonLabo_admin_options" action="options.php">' );
     314        echo( '<form method="post" id="MonLabo_admin_options" class="MonLabo_admin_options" action="options.php">' );
    315315
    316316        echo( '<h2 class="nav-tab-wrapper">' );
     
    381381        echo wp_kses_post( $this->_get_title( esc_html( get_admin_page_title() ) ) );
    382382        settings_errors();
    383         echo '<form method="POST" id="MonLabo_admin_options" action="options.php">';
     383        echo '<form method="post" id="MonLabo_admin_options" action="options.php">';
    384384        settings_fields( 'pluginSettings_MonLabo_settings_group7' );
    385385        do_settings_sections( 'MonLaboPageConfigPublicationsCache' );
  • mon-laboratoire/trunk/Admin/class-admin-page.php

    r3361909 r3369834  
    200200            }
    201201
    202         $titletext = ( $this->_hide_fields === true ) ? '<span class="MonLabo_hide">' . $title . '</span>' : $title;
     202        $titletext = ( $this->_hide_fields === true ) ? '<span class="MonLabo_hide">' . $title . '&nbsp;</span>' : $title;
    203203        // Add the field using Settings API
    204204        $this->_optionsFormsView->add(
  • mon-laboratoire/trunk/Admin/class-admin-table-view.php

    r3355231 r3369834  
    405405        $out .= '<h3 id="personnes_table">' . __( 'Persons', 'mon-laboratoire' ) . '</h3>';
    406406        $out .= '<h4>' . _x( 'Working', 'nom_pluriel', 'mon-laboratoire' ) . '</h4>';
    407         $out .= wp_kses_post( $this->_generate_table_admin_for_persons( 'actif' ) );
     407        $out .= $this->_generate_table_admin_for_persons( 'actif' );
    408408        $out .= '<h4>' . _x( 'Former members', 'nom_pluriel', 'mon-laboratoire' ) . '</h4>';
    409         $out .= wp_kses_post( $this->_generate_table_admin_for_persons( 'alumni' ) );
     409        $out .= $this->_generate_table_admin_for_persons( 'alumni' );
    410410        $out .= '<a href="#haut">' . __( 'Back to top of page', 'mon-laboratoire' ) . '</a>';
    411411        // Teams table
    412412        $out .= '<h3 id="equipes_table">' . __( 'Teams', 'mon-laboratoire' ) . '</h3>';
    413         $out .= wp_kses_post( $this->_generate_table_admin_for_teams() );
     413        $out .= $this->_generate_table_admin_for_teams();
    414414        $out .= '<a href="#haut">' . __( 'Back to top of page', 'mon-laboratoire' ) . '</a>';
    415415        // Thematics table
    416416        if ( $options->uses['thematics'] ) {
    417417            $out .= '<h3 id="thematiques_table">' . __( 'Thematics', 'mon-laboratoire' ) . '</h3>';
    418             $out .= wp_kses_post( $this->_generate_table_admin_for_thematics() );
     418            $out .= $this->_generate_table_admin_for_thematics();
    419419            $out .= '<a href="#haut">' . __( 'Back to top of page', 'mon-laboratoire' ) . '</a>';
    420420        }
     
    422422        if ( $options->uses['units'] ) {
    423423            $out .= '<h3 id="unites_table">' . __( 'Units', 'mon-laboratoire' ) . '</h3>';
    424             $out .= wp_kses_post( $this->_generate_table_admin_for_units() );
     424            $out .= $this->_generate_table_admin_for_units();
    425425            $out .= '<a href="#haut">' . __( 'Back to top of page', 'mon-laboratoire' ) . '</a>';
    426426        }
  • mon-laboratoire/trunk/Admin/class-wordpress-page.php

    r3355231 r3369834  
    145145     *
    146146     * @return bool True if page has configured person parent
     147     * TODO : préférer plutôt tester que la page est désignées comme page de personne
     148     *        ou a minima faire ce test si $options10['MonLabo_perso_page_parent'] n'est
     149     *        pas défini.
    147150     */
    148151    public function is_a_person_page(): bool {
  • mon-laboratoire/trunk/Lib/class-privacy.php

    r3361909 r3369834  
    22namespace MonLabo\Lib;
    33
    4 defined( 'ABSPATH' ) or die( 'No direct script access allowed' );
     4defined( 'ABSPATH' ) || die( 'No direct script access allowed' );
    55
    66/**
     
    3434     * Privacy options from WordPress options.
    3535     *
    36      * @var array<string,string>
     36     * @var array<string,string> Key-value pairs of privacy settings.
    3737     * @access private
    3838     */
     
    4141    /**
    4242     * Whether the current user is considered trusted.
    43      * 
    44      * @access private
    45      * @var bool
     43     *
     44     * @var    bool True if user is trusted, false otherwise.
     45     * @access private
    4646     */
    4747    private $_is_trusted_user = false;
    4848
    4949    /**
    50      * Initializes a new Privacy instance.
    51      *
    52      * Sets up the privacy options, translation capabilities and
    53      * calculates the trust status of the current user if needed.
     50     * Constructor for Privacy class.
     51     *
     52     * Initializes a new Privacy instance. Sets up the privacy options,
     53     * translation capabilities and calculates the trust status of the
     54     * current user if needed.
    5455     *
    5556     * @return void
     
    5758     */
    5859    private function __construct() {
    59         $this->_options = get_option( 'MonLabo_settings_group11' );
     60        $this->_options         = get_option( 'MonLabo_settings_group11' );
    6061        $this->_is_trusted_user = $this->_calculate_trust_status();
    6162    }
    6263
    6364    /**
    64      * Generic function for is_hide_email(), is_hide_room(), is_hide_phone() and is_hide_photo()
    65      *
    66      * @param string $privacy_option Name of the privacy option
     65     * Generic method to determine if an item should be hidden.
     66     *
     67     * Used internally by is_hide_email(), is_hide_room(), is_hide_phone()
     68     * and is_hide_photo() methods.
     69     *
     70     * @param string $privacy_option Name of the privacy option to check.
    6771     * @return bool True if item should be hidden, false otherwise.
    68      */
    69     public function _is_hide_generic( string $privacy_option ): bool {
    70         if (
    71             ! isset( $this->_options[ $privacy_option ] )
     72     * @access private
     73     */
     74    private function _is_hide_generic( string $privacy_option ): bool {
     75        if ( ! isset( $this->_options[ $privacy_option ] )
    7276            || App::PRIVACY_SHOW === $this->_options[ $privacy_option ]
    7377        ) {
     
    8084    }
    8185
    82 
    8386    /**
    8487     * Determines if emails should be hidden for the current context.
     
    126129     */
    127130    private function _calculate_trust_status(): bool {
    128         if (
    129             ! isset( $this->_options['MonLabo_trusted_visitors'] )
     131        // Check if all visitors are trusted.
     132        if ( ! isset( $this->_options['MonLabo_trusted_visitors'] )
    130133            || App::PRIVACY_TRUST_EVERYBODY === $this->_options['MonLabo_trusted_visitors']
    131134        ) {
     
    133136        }
    134137
    135         // First: test if user logged-in
     138        // Check if user is logged in.
    136139        if ( is_user_logged_in() ) {
    137140            return true;
     141        }
     142
     143        // Check if IP is in trusted range.
     144        if ( App::PRIVACY_TRUST_IP_ZONE !== $this->_options['MonLabo_trusted_visitors'] ) {
     145            return false;
    138146        }
    139147
     
    163171        return false;
    164172    }
    165    
    166     /**
    167      * Determines if switcboard and nature fields should be displayed.
    168      *
    169      * @return bool True if fields have to be displayed
    170      */
    171     public function is_display_switchboard_fields() : bool {
    172         if( ! isset( $this->_options['MonLabo_phone_privacy'] ) ) {
    173             return false;
    174         }
    175         if (    App::PRIVACY_SWITCHBOARD_LOCAL_SHOW === $this->_options['MonLabo_phone_privacy']
    176             ) {
    177             return true;
    178         }
    179         return false;
    180     }
    181 
    182        
    183     /**
    184      * Determines if switcboard and nature fields should be displayed.
    185      *
    186      * @return bool True if fields have to be displayed
    187      */
    188     public function is_display_email_test() : bool {
    189         if( ! isset( $this->_options['MonLabo_email_privacy'] ) ) {
    190             return false;
    191         }
    192         if (    App::PRIVACY_FORM_LOCAL_SHOW === $this->_options['MonLabo_email_privacy']
    193             ) {
    194             return true;
    195         }
    196         return false;
    197     }
    198 
    199 
    200     /**
    201      * Determines if switcboard and nature fields should be displayed.
    202      *
    203      * @return bool True if fields have to be displayed
    204      */
    205     public function is_display_trusted_IP_form() : bool {
    206         if( ! isset( $this->_options['MonLabo_trusted_visitors']  ) ) {
    207             return false;
    208         }
    209         if ( App::PRIVACY_TRUST_IP_ZONE === $this->_options['MonLabo_trusted_visitors'] ) {
    210             return true;
    211         }
    212         return false;
     173
     174    /**
     175     * Determines if switchboard and nature fields should be displayed.
     176     *
     177     * @return bool True if fields should be displayed, false otherwise.
     178     */
     179    public function is_display_switchboard_fields(): bool {
     180        if ( ! isset( $this->_options['MonLabo_phone_privacy'] ) ) {
     181            return false;
     182        }
     183        return App::PRIVACY_SWITCHBOARD_LOCAL_SHOW === $this->_options['MonLabo_phone_privacy'];
     184    }
     185
     186    /**
     187     * Determines if email test fields should be displayed.
     188     *
     189     * @return bool True if email test should be displayed, false otherwise.
     190     */
     191    public function is_display_email_test(): bool {
     192        if ( ! isset( $this->_options['MonLabo_email_privacy'] ) ) {
     193            return false;
     194        }
     195        return App::PRIVACY_FORM_LOCAL_SHOW === $this->_options['MonLabo_email_privacy'];
     196    }
     197
     198    /**
     199     * Determines if trusted IP form fields should be displayed.
     200     *
     201     * @return bool True if trusted IP form should be displayed, false otherwise.
     202     */
     203    public function is_display_trusted_IP_form(): bool {
     204        if ( ! isset( $this->_options['MonLabo_trusted_visitors'] ) ) {
     205            return false;
     206        }
     207        return App::PRIVACY_TRUST_IP_ZONE === $this->_options['MonLabo_trusted_visitors'];
    213208    }
    214209
     
    218213     * @return bool True if phones should be replaced with switchboard number.
    219214     */
    220     public function is_phone_replaced_with_switchboard() : bool {
    221         if( ! isset( $this->_options['MonLabo_phone_privacy']  ) ) {
    222             return false;
    223         }
    224         if (
    225             App::PRIVACY_SWITCHBOARD_LOCAL_SHOW === $this->_options['MonLabo_phone_privacy']
    226             && ! $this->_is_trusted_user
    227         ) {
    228             return true;
    229         }
    230         return false;
    231     }
    232    
     215    public function is_phone_replaced_with_switchboard(): bool {
     216        if ( ! isset( $this->_options['MonLabo_phone_privacy'] ) ) {
     217            return false;
     218        }
     219        return App::PRIVACY_SWITCHBOARD_LOCAL_SHOW === $this->_options['MonLabo_phone_privacy']
     220            && ! $this->_is_trusted_user;
     221    }
     222
    233223    /**
    234224     * Checks if email addresses should be replaced with a contact form.
    235225     *
    236      * @return bool True if emails should be replaced with contact form.
    237      */
    238     public function is_email_replaced_by_form() : bool {
    239         if( ! isset( $this->_options['MonLabo_email_privacy']  ) ) {
    240             return false;
    241         }
    242         if (
    243             App::PRIVACY_FORM_LOCAL_SHOW === $this->_options['MonLabo_email_privacy']
    244             && ! $this->_is_trusted_user
    245         ) {
    246             return true;
    247         }
    248         return false;
    249     }
    250 
    251     /**
    252      * Send switchboard parameters.
     226     * @return bool True if emails should be replaced with contact form, false otherwise.
     227     */
     228    public function is_email_replaced_by_form(): bool {
     229        if ( ! isset( $this->_options['MonLabo_email_privacy'] ) ) {
     230            return false;
     231        }
     232        return App::PRIVACY_FORM_LOCAL_SHOW === $this->_options['MonLabo_email_privacy']
     233            && ! $this->_is_trusted_user;
     234    }
     235
     236    /**
     237     * Get switchboard parameters.
     238     *
     239     * Returns the switchboard number and nature labels in English and French.
    253240     *
    254241     * @return string[] $switchboard_params
  • mon-laboratoire/trunk/mon-laboratoire.php

    r3361909 r3369834  
    1616 * Plugin URI:        http://www.monlabo.org
    1717 * Description:       Simplify the management of a research unit's website
    18  * Version:           5.1
     18 * Version:           5.1.1
    1919 * Requires at least: 5.6
    2020 * Requires PHP:      7.2
     
    4242
    4343defined( 'ABSPATH' ) or die( 'No direct script access allowed' );
    44 define( 'MONLABO_VERSION', '5.1' ); //Currently plugin version, use SemVer - https://semver.org
     44define( 'MONLABO_VERSION', '5.1.1' ); //Currently plugin version, use SemVer - https://semver.org
    4545
    4646require_once ( __DIR__ . '/polyfill.php' );
  • mon-laboratoire/trunk/readme.txt

    r3361909 r3369834  
    55Requires at least: 5.6
    66Tested up to: 6.7
    7 Stable tag: 5.1
     7Stable tag: 5.1.1
    88Requires PHP: 7.2
    99License: GPLv3 or later
     
    4646== Changelog ==
    4747
     48= 5.1.1 =
     49*Release Date - 29 september 2025*
     50* BUG : Combinations of privacy options could show the email address in cases where it should have been hidden.
     51* BUG : Admin menu - Modify buttons where accidentally inactivated in "table view" tab.
     52* CODE : improve coding standard
     53
     54
    4855= 5.1 =
    4956*Release Date - 15 september 2025*
    5057
    5158* New Features:
    52 *   - EVOL : simplify privacy configuration page for managing personal data protection
    53 *
     59    - EVOL : simplify privacy configuration page for managing personal data protection
    5460* Bug Fixes & Reliability:
    55 *   - CODE : class refactoring for improved maintainability:
    56 *       - Html_Forms                                    -> Forms\{Generic_Forms_View, Forms_View, Wp_Post_Forms_View}
    57 *       - Settings_Fields                               -> Forms\Options_Forms_View
    58 *       - Forms_Processing\Forms_Processing             -> Forms\Forms_Processing
    59 *       - Forms_Processing\Forms_Processing_Generic     -> Forms\Forms_Processing_Generic
    60 *       - Forms_Processing\Forms_Processing_Advanced    -> Forms\Forms_Processing_Advanced
    61 *   - BUG : mail form was not translated
    62 *   - BUG : In admin menu for editing person or other item, first page links and translation were not displayed
     61    - CODE : class refactoring for improved maintainability:
     62        - Html_Forms                                    -> Forms\{Generic_Forms_View, Forms_View, Wp_Post_Forms_View}
     63        - Settings_Fields                               -> Forms\Options_Forms_View
     64        - Forms_Processing\Forms_Processing             -> Forms\Forms_Processing
     65        - Forms_Processing\Forms_Processing_Generic     -> Forms\Forms_Processing_Generic
     66        - Forms_Processing\Forms_Processing_Advanced    -> Forms\Forms_Processing_Advanced
     67    - BUG : mail form was not translated
     68    - BUG : In admin menu for editing person or other item, first page links and translation were not displayed
    6369 
    6470= 5.0.4 =
     
    7278
    7379* New Features:
    74 *   - NEW: Comprehensive privacy configuration page for managing personal data protection (page available at URL https://YOURSITE.TLD/wp-admin/admin.php?page=MonLabo_config&tab=tab_privacy)
    75 *       - Can replace email addresses with contact forms on public sites (email is not hidden for internal users)
    76 *       - Can replace phone numbers with switchboard numbers on public sites (full view for internal users)
    77 *       - Can hide personal photos and/or office locations on public sites (full view for internal users)
    78 *       - Can define trusted IP address ranges for internal users with full data access
    79 *   - NEW: Preview version of [publications_list2] shortcode - complete rewrite of [publications_list] using HAL API V3 exclusively
    80 *       - Eliminates dependency on potentially discontinued external tools
    81 *           (exportPubli from haltools.inria.fr and afficheRequetePubli from haltools.archives-ouvertes.fr)
    82 *       - Removes support for legacy Descartes Publi database
    83 *       - Handles pages with thousands of publications without HAL server timeouts
    84 *       - Enables future feature: expanded publication format support
    85 *       - Enables future feature: customizable publication type filtering
    86 *       - Fixes publication ordering issue (now correctly displays most recent first)
    87 *   - EVOL in shortodes :
    88 *       - [person_panel] : small improve of formating
    89 *       - [teams_list] : suppress option "teams_publications_page" useless and unnecessarily complicated
    90 *   - EVOL and IMPROVE in admin space :
    91 *       - Dynamic sub-option display when features are activated
    92 *       - Add publication cache counter on upper bar. Dynamically hide cache counters when they are emptied.
    93 *   - DOC: Added comprehensive contributor credits and version history
    94 *
     80    - NEW: Comprehensive privacy configuration page for managing personal data protection (page available at URL https://YOURSITE.TLD/wp-admin/admin.php?page=MonLabo_config&tab=tab_privacy)
     81        - Can replace email addresses with contact forms on public sites (email is not hidden for internal users)
     82        - Can replace phone numbers with switchboard numbers on public sites (full view for internal users)
     83        - Can hide personal photos and/or office locations on public sites (full view for internal users)
     84        - Can define trusted IP address ranges for internal users with full data access
     85    - NEW: Preview version of [publications_list2] shortcode - complete rewrite of [publications_list] using HAL API V3 exclusively
     86        - Eliminates dependency on potentially discontinued external tools
     87            (exportPubli from haltools.inria.fr and afficheRequetePubli from haltools.archives-ouvertes.fr)
     88        - Removes support for legacy Descartes Publi database
     89        - Handles pages with thousands of publications without HAL server timeouts
     90        - Enables future feature: expanded publication format support
     91        - Enables future feature: customizable publication type filtering
     92        - Fixes publication ordering issue (now correctly displays most recent first)
     93    - EVOL in shortodes :
     94        - [person_panel] : small improve of formating
     95        - [teams_list] : suppress option "teams_publications_page" useless and unnecessarily complicated
     96    - EVOL and IMPROVE in admin space :
     97        - Dynamic sub-option display when features are activated
     98        - Add publication cache counter on upper bar. Dynamically hide cache counters when they are emptied.
     99    - DOC: Added comprehensive contributor credits and version history
    95100* Bug Fixes & Reliability:
    96 *   - BUG : Repair import tool (it was often not functionnal because of a bad check of file extension)
    97 *   - BUG : In shortcode [publications_list], repair wilcard selection ('*') for persons, teams or unit
    98 *   - CODE: Major class refactoring for improved maintainability:
    99 *       - Access_Data      -> Access_Data\{ Access_Generic, Access_Data, Core\Data_Accessor, Core\Data_Creator, Core\Data_Remover }
    100 *       - Html             -> { Html, Person_Or_Structure_View,
    101 *                               Shortcodes\Generic_View, Shortcodes\Chart_view, Shortcodes\Table_view, Shortcodes\List_view }
    102 *       - Edit_Members     -> Edit_Members\{ Edit_Members, Edit_Members_Generic, Edit_Members_Comment, Edit_Members_Advanced }
    103 *       - Forms_Processing -> Forms_Processing\{ Forms_Processing, Forms_Processing_Generic, Forms_Processing_Advanced }
    104 *       - Admin_Ui         -> { Admin_Init_Pages, Forms_Processing_Generic, Forms_Processing_Advanced }
    105 *       - Page             -> Wordpress_Page
    106 *       - Admin            -> { Admin, Admin_Init }
    107 *       - Admin_Render     -> Admin_Menu
    108 *   - CODE : Comprehensive rewrite of all classes:
    109 *       - Enhanced PHPDoc comments throughout all classes
    110 *       - Improved coding standards compliance across all classes
    111 *       - Strengthened security with proper escaping (esc_attr(), esc_js(), wp_kses_post(), esc_html())
    112 *       - Cleaner HTML generation (consistent double quotes, optimized spacing...)
    113 *       - Reduced code complexity through helper methods, conditional execution...
    114 *   - CODE: Eliminated global functions for better architecture:
    115 *       - Migrated inc-lib-tables.php to new Admin_Table_View class
    116 *       - Migrated inc-lib-modal.php to Html class
    117 *       - Migrated main code to new Mon_Laboratoire class
    118 *       - Migrated admin main code to Admin class
    119 *   - CODE: Now uses null coalescing operator (??) - requires PHP 7.0+ that has been already required in former versions.
    120 *   - CODE: Fully tested and compatible with WordPress 6.8
    121 *   - CODE: Updated page URLs from ?p= to ?page_id= format
    122 *   - CODE: Refactored admin JavaScript following WordPress best practices
     101    - BUG : Repair import tool (it was often not functionnal because of a bad check of file extension)
     102    - BUG : In shortcode [publications_list], repair wilcard selection ('*') for persons, teams or unit
     103    - CODE: Major class refactoring for improved maintainability:
     104        - Access_Data      -> Access_Data\{ Access_Generic, Access_Data, Core\Data_Accessor, Core\Data_Creator, Core\Data_Remover }
     105        - Html             -> { Html, Person_Or_Structure_View,
     106                                Shortcodes\Generic_View, Shortcodes\Chart_view, Shortcodes\Table_view, Shortcodes\List_view }
     107        - Edit_Members     -> Edit_Members\{ Edit_Members, Edit_Members_Generic, Edit_Members_Comment, Edit_Members_Advanced }
     108        - Forms_Processing -> Forms_Processing\{ Forms_Processing, Forms_Processing_Generic, Forms_Processing_Advanced }
     109        - Admin_Ui         -> { Admin_Init_Pages, Forms_Processing_Generic, Forms_Processing_Advanced }
     110        - Page             -> Wordpress_Page
     111        - Admin            -> { Admin, Admin_Init }
     112        - Admin_Render     -> Admin_Menu
     113    - CODE : Comprehensive rewrite of all classes:
     114        - Enhanced PHPDoc comments throughout all classes
     115        - Improved coding standards compliance across all classes
     116        - Strengthened security with proper escaping (esc_attr(), esc_js(), wp_kses_post(), esc_html())
     117        - Cleaner HTML generation (consistent double quotes, optimized spacing...)
     118        - Reduced code complexity through helper methods, conditional execution...
     119    - CODE: Eliminated global functions for better architecture:
     120        - Migrated inc-lib-tables.php to new Admin_Table_View class
     121        - Migrated inc-lib-modal.php to Html class
     122        - Migrated main code to new Mon_Laboratoire class
     123        - Migrated admin main code to Admin class
     124    - CODE: Now uses null coalescing operator (??) - requires PHP 7.0+ that has been already required in former versions.
     125    - CODE: Fully tested and compatible with WordPress 6.8
     126    - CODE: Updated page URLs from ?p= to ?page_id= format
     127    - CODE: Refactored admin JavaScript following WordPress best practices
    123128
    124129= 4.9.1 =
  • mon-laboratoire/trunk/todo.txt

    r3361909 r3369834  
    1010
    1111Hal:
     12    TODO: Mettre en gras les personnels alumni dans les publications.
    1213    TODO: Faire un bouton "page suivante" si l'on dépasse le max de requête HAL
    1314    ÉVOL: Pouvoir avec HAL faire des requêtes avec des IdHal et des struct en même temps (ou consécutivement et fusionnées)
Note: See TracChangeset for help on using the changeset viewer.