      1: <?php
      2: /**
      3:  *
      4:  */
      5: class Sidecar_Form {
      6:   /**
      7:    * @var Sidecar_Plugin_Base
      8:    */
      9:   var $plugin;
     10:
     11:   /**
     12:    * @var Sidecar_Admin_Page
     13:    */
     14:   var $admin_page;
     15:
     16:   /**
     17:    * @var string
     18:    */
     19:   var $form_name;
     20:
     21:   /**
     22:    * @var string
     23:    */
     24:   var $form_label;
     25:
     26:   /**
     27:    * @var bool
     28:    */
     29:   var $requires_api;
     30:
     31:   /**
     32:    * @var Sidecar_Form_Settings
     33:    */
     34:   private $_settings;
     35:
     36:   /**
     37:    * @var array
     38:    */
     39:   private $_default_settings_values;
     40:
     41:   /**
     42:    * @var array
     43:    */
     44:   private $_fields = array();
     45:
     46:   /**
     47:    * @var array
     48:    */
     49:   private $_sections = array();
     50:
     51:   /**
     52:    * @var array
     53:    */
     54:   private $_buttons = array();
     55:
     56:   /**
     57:    * @var bool
     58:    */
     59:   private $_initialized = false;
     60:
     61:   /**
     62:    * @var array
     63:    */
     64:   private $_required_field_names;
     65:
     66:   /**
     67:    * @param string $form_name
     68:    * @param array $args
     69:    */
     70:   function __construct( $form_name, $args = array() ) {
     71:     $this->form_name = $form_name;
     72:     /**
     73:      * Copy properties in from $args, if they exist.
     74:      */
     75:     foreach( $args as $property => $value ) {
     76:       if ( property_exists( $this, $property ) ) {
     77:         $this->$property = $value;
     78:       } else if ( property_exists( $this, $property = "form_{$property}" ) ) {
     79:         $this->$property = $value;
     80:       }
     81:     }
     82:
     83:   }
     84:
     85:   /**
     86:    * @return array
     87:    */
     88:   function get_sections() {
     89:     return $this->_sections;
     90:   }
     91:
     92:   /**
     93:    * @return array
     94:    */
     95:   function get_fields() {
     96:     return $this->_fields;
     97:   }
     98:
     99:   /**
    100:    * @param string $field_name
    101:    * @return bool|Sidecar_Field
    102:    */
    103:   function get_field( $field_name ) {
    104:     return isset( $this->_fields[$field_name] ) ? $this->_fields[$field_name] : false;
    105:   }
    106:
    107:   /**
    108:    * @param string $field_name
    109:    * @return bool
    110:    */
    111:   function has_field( $field_name ) {
    112:     return isset( $this->_fields[$field_name] );
    113:   }
    114:
    115:   /**
    116:    * @return bool
    117:    */
    118:   function has_fields() {
    119:     return 0 < count( $this->_fields ) ;
    120:   }
    121:
    122:   /**
    123:    * @param string $section_name
    124:    * @return bool
    125:    */
    126:   function get_section( $section_name ) {
    127:     return isset( $this->_sections[$section_name] ) ? $this->_sections[$section_name] : false;
    128:   }
    129:
    130:   /**
    131:    * @param string $section_name
    132:    * @return bool
    133:    */
    134:   function has_section( $section_name ) {
    135:     return isset( $this->_sections[$section_name] );
    136:   }
    137:
    138:   /**
    139:    * @return bool
    140:    */
    141:   function has_sections() {
    142:     return 0 < count( $this->_sections ) ;
    143:   }
    144:
    145:   /**
    146:    * @param string $section_name
    147:    * @return array
    148:    */
    149:   function get_field_names( $section_name ) {
    150:     return array_keys( $this->_sections[$section_name]->fields );
    151:   }
    152:
    153:   /**
    154:    * @param bool|string $section_name
    155:    *
    156:    * @return bool
    157:    */
    158:   function has_section_fields( $section_name = false ) {
    159:     $has_fields = false;
    160:     if ( $section_name && $this->has_section( $section_name ) ) {
    161:       $has_fields = 0 < count( $this->_sections[$section_name]->fields );
    162:     }
    163:     return $has_fields;
    164:   }
    165:
    166:   /**
    167:    * @param bool|string $section_name
    168:    *
    169:    * @return array
    170:    */
    171:   function get_section_fields( $section_name = false ) {
    172:     $fields = array();
    173:     if ( $section_name && $this->has_section( $section_name ) ) {
    174:       $fields = $this->_sections[$section_name]->fields;
    175:     }
    176:     return $fields;
    177:   }
    178:
    179:   /**
    180:    * @return array
    181:    */
    182:   function the_form() {
    183:     echo $this->get_html();
    184:     return $this;
    185:   }
    186:
    187:   /**
    188:    * @return string
    189:    */
    190:   function get_html() {
    191:     ob_start();
    192:     /**
    193:      * Get the HTML for the hidden fields from the Settings API
    194:      */
    195:     settings_fields( $settings_group = $this->plugin->option_name );
    196:
    197:     /**
    198:      * Hide hidden fields from Settings API by removing them from global $wp_settings_fields
    199:      * Adding to internal $hidden_fields
    200:      */
    201:     global $wp_settings_fields;
    202:     $save_wp_settings_fields = $wp_settings_fields;
    203:     $hidden_fields = array();
    204:     $fields = $this->get_fields();
    205:     if ( isset( $wp_settings_fields[$this->plugin->option_name] ) ) {
    206:       foreach( $wp_settings_fields[$this->plugin->option_name] as $section_name => $section ) {
    207:         foreach( $section as $field_name => $field ) {
    208:           $unset = false;
    209:           if ( ! isset( $fields[$field_name] ) ) {
    210:             $unset = true;
    211:           } else if ( 'hidden' == $field['args']['field']->field_type ) {
    212:             $hidden_fields[] = $field['args']['field'];
    213:             $unset = true;
    214:           }
    215:           if ( $unset ) {
    216:             unset( $wp_settings_fields[$this->plugin->option_name][$section_name][$field_name] );
    217:           } else {
    218:             /**
    219:              * @var Sidecar_Field $field_object
    220:              */
    221:             $field_object = $field['args']['field'];
    222:             $wp_settings_fields[$this->plugin->option_name][$section_name][$field_name]['args']['label_for'] = $field_object->get_wrapper_id();
    223:           }
    224:         }
    225:       }
    226:     }
    227:     /**
    228:      * Extract the hidden fields so they won't display
    229:      * @var Sidecar_Field $hidden_field
    230:      */
    231:     $hidden_fields_html = array();
    232:     foreach( $hidden_fields as $hidden_field ) {
    233:       $hidden_fields_html[] = $hidden_field->get_html();
    234:     }
    235:     $hidden_fields_html = implode( "\n", $hidden_fields_html );
    236:
    237:     /**
    238:      * Output each of the sections.
    239:      */
    240:     do_settings_sections( $this->plugin->option_name );
    241:
    242:     $form_fields_html = ob_get_clean();
    243:
    244:     $options_url = admin_url( 'options.php' );
    245:
    246:     if ( ! $this->admin_page->has_tabs() ) {
    247:       $hidden_section_input_html = '';
    248:     } else {
    249:       $tab_slug = $this->admin_page->get_current_tab()->tab_slug;
    250:
    251:       $hidden_section_input_html = <<<HTML
    252: <input type="hidden" name="{$this->plugin->option_name}[_sidecar_form_meta][plugin]" value="{$this->plugin->plugin_name}" />
    253: <input type="hidden" name="{$this->plugin->option_name}[_sidecar_form_meta][page]" value="{$this->admin_page->page_name}" />
    254: <input type="hidden" name="{$this->plugin->option_name}[_sidecar_form_meta][tab]" value="{$tab_slug}" />
    255: <input type="hidden" name="{$this->plugin->option_name}[_sidecar_form_meta][form]" value="{$this->form_name}" />
    256: HTML;
    257:     }
    258:     $buttons_html = array();
    259:     foreach( $this->_buttons as $button ) {
    260:       $button_html = get_submit_button(
    261:         $button->button_text,
    262:         $button->button_type,
    263:         $button->input_name,
    264:         $button->button_wrap,
    265:         $button->other_attributes
    266:         );
    267:       $buttons_html[] = preg_replace( '#^<p(.*)</p>#', '<span$1</span>', $button_html );
    268:     }
    269:     $buttons_html = implode( "\n", $buttons_html );
    270:     $form = <<<HTML
    271: <form action="{$options_url}" method="POST">
    272:   {$hidden_section_input_html}
    273:   <div class="form-fields">
    274:     {$hidden_fields_html}
    275:     {$form_fields_html}
    276:   </div>
    277:   <div class="form-buttons">
    278:     {$buttons_html}
    279:   </div>
    280: </form>
    281: HTML;
    282:     $wp_settings_fields = $save_wp_settings_fields;
    283:     return $form;
    284:   }
    285:
    286:   /**
    287:    * @param string  $button_name
    288:    * @param string  $button_text
    289:    * @param array   $args
    290:
    291:    *  string $type The type of button. One of: primary, secondary, delete
    292:    *  string $name The HTML name of the submit button. Defaults to "submit". If no id attribute
    293:    *               is given in $other_attributes below, $name will be used as the button's id.
    294:    *  bool $wrap True if the output button should be wrapped in a paragraph tag,
    295:    *               false otherwise. Defaults to true
    296:    *  array|string $other_attributes Other attributes that should be output with the button,
    297:    *                     mapping attributes to their values, such as array( 'tabindex' => '1' ).
    298:    *                     These attributes will be output as attribute="value", such as tabindex="1".
    299:    *                     Defaults to no other attributes. Other attributes can also be provided as a
    300:    *                     string such as 'tabindex="1"', though the array format is typically cleaner.   */
    301:   function add_button( $button_name, $button_text, $args = array() ) {
    302:     $args['button_name'] =  $button_name;
    303:     $args['button_text'] =  $button_text;
    304:
    305:     $this->_buttons[$button_name] = (object)$args;
    306:   }
    307:
    308:   /**
    309:    * @param string $button_name
    310:    *
    311:    * @return bool
    312:    */
    313:   function get_button( $button_name ) {
    314:     return isset( $this->_buttons[$button_name] ) ? $this->_buttons[$button_name] : false;
    315:   }
    316:
    317:   /**
    318:    */
    319:   function initialize() {
    320:     if ( ! $this->_initialized ) {
    321:
    322:       if ( ! $this->has_fields()  ) {
    323:         $this->plugin->initialize_form( $this );
    324:       }
    325:       $this->_initialized = true;
    326:
    327:     }
    328:   }
    329:
    330:   /**
    331:    * @param Sidecar_Plugin_Base $plugin
    332:    */
    333:   function initialize_sections( $plugin ) {
    334:     $settings = $this->get_settings();
    335:     foreach( $this->get_sections() as $section_name => $section ) {
    336:       if ( ! $section->section_handler )
    337:         $section->section_handler = array( $plugin, 'the_form_section' );
    338:       add_settings_section( $section_name, $section->section_title, $section->section_handler, $this->plugin->option_name, array(
    339:         'section' => $section,
    340:         'form' => $this,
    341:         'plugin' => $plugin,
    342:         'settings' => $settings,
    343:         ));
    344:       foreach( $section->fields as $field_name => $field ) {
    345:         if ( ! $field->field_handler )
    346:           $field->field_handler = array( $this, '_the_form_field_callback' );
    347:         $field_label = 'checkbox' != $field->field_type ? $field->field_label : false;
    348:         add_settings_field( $field_name, $field_label, $field->field_handler, $this->plugin->option_name, $section_name, array(
    349:           'field' => $field,
    350:           'section' => $section,
    351:           'form' => $this,
    352:           'plugin' => $plugin,
    353:           'settings' => $settings,
    354:           ));
    355:       }
    356:     }
    357:   }
    358:
    359:   /**
    360:    * @param array $args
    361:    */
    362:   function _the_form_field_callback( $args ) {
    363:     $this->plugin->the_form_field( $args['field']->field_name, $args['form']->form_name );
    364:   }
    365:
    366:   /**
    367:    * @param Sidecar_Plugin_Base $plugin
    368:    */
    369:   function initialize_buttons( $plugin ) {
    370:     foreach( $this->_buttons as $button_name => $button ) {
    371:
    372:       if ( ! isset( $button->button_type ) )
    373:         $button->button_type = 'primary';
    374:
    375:       if ( ! isset( $button->input_name ) )
    376:         if ( 'primary' == $button->button_type ) {
    377:           $button->input_name = 'submit';
    378:         } else {
    379:           $button->input_name = "{$this->plugin->option_name}[action][{$button_name}]";
    380:         }
    381:
    382:       if ( ! isset( $button->button_wrap ) )
    383:         $button->button_wrap = true;
    384:
    385:       if ( ! isset( $button->other_attributes ) )
    386:         $button->other_attributes = false;
    387:
    388:     }
    389:   }
    390:
    391:   /**
    392:    * @param string  $section_name
    393:    * @param array   $args
    394:    * @return object
    395:    */
    396:   function add_section( $section_name, $args = array() ) {
    397:     $args = (object)$args;
    398:
    399:     $args->section_name = $section_name;
    400:     $args->form = $this;
    401:
    402:     if ( ! isset( $args->fields ) )
    403:       $args->fields = array();
    404:
    405:     if ( ! isset( $args->section_title ) )
    406:       $args->section_title = false;
    407:
    408:     if ( ! isset( $args->section_handler ) )
    409:       $args->section_handler = false;
    410:
    411:     return $this->_sections[$section_name] = $args;
    412:   }
    413:
    414:   /**
    415:    * @param string        $field_name
    416:    * @param array         $args
    417:    * @return object
    418:    */
    419:   function add_field( $field_name, $args = array() ) {
    420:     if ( 0 == count( $this->_sections ) ) {
    421:       /**
    422:        * We don't have any sections, set them, to 'default' and register
    423:        */
    424:       $section_name = 'default';
    425:       $this->add_section( $section_name );
    426:     } else if ( ! isset( $args['section_name'] ) ) {
    427:       /**
    428:        * Get the name of the last section added to the sections array.
    429:        */
    430:       end( $this->_sections );
    431:       $section_name = key( $this->_sections );
    432:     } else {
    433:       /**
    434:        * If was passed, grab it.
    435:        */
    436:       $section_name = $args['section_name'];
    437:     }
    438:     /**
    439:      * Luke, I am your father!
    440:      * (or for those who don't get the reference, assign reference to $args['form'] so it will know it's form.)
    441:      */
    442:     $args['form'] = $this;
    443:     $args['plugin'] = $this->plugin;
    444:     $args['section'] = $this->get_section( $section_name );
    445:     $field = new Sidecar_Field( $field_name, $args );
    446:     $this->_fields[$field_name] = &$field;
    447:     $this->_sections[$section_name]->fields[$field_name] = &$field;
    448:
    449:     $this->get_settings()->register_setting( $field_name );
    450:
    451:   }
    452:
    453:   /**
    454:    * Get an array of new setting values (empty string; '').
    455:    *
    456:    * Override in subclass to add more specific setting defaults.
    457:    * @return array
    458:    *
    459:    * @todo Cache new settings in a setting so that loading from front end does...
    460:    * @todo ...require setting the admin form and traversing through these fields.
    461:    */
    462:   function get_default_settings_values() {
    463:     if ( ! isset( $this->_default_settings_values ) ) {
    464:       $default_settings_values = array();
    465:       foreach( $this->get_fields() as $field ) {
    466:         $default_settings_values[$field->field_name] = isset( $field->field_default ) ? $field->field_default : '';
    467:       }
    468:       $this->_default_settings_values = $default_settings_values;
    469:     }
    470:         return $this->_default_settings_values;
    471:     }
    472:
    473:   /**
    474:    * @return array
    475:    */
    476:   function get_empty_field_values() {
    477:     return $this->get_settings()->get_empty_field_values();
    478:   }
    479:
    480:   /**
    481:    * @return array
    482:    */
    483:   function get_settings_values() {
    484:     return $this->get_settings()->get_values();
    485:   }
    486:
    487:   /**
    488:    * @return Sidecar_Form_Settings
    489:    */
    490:   function get_settings() {
    491:     if ( ! isset( $this->_settings ) ) {
    492:       $this->_settings = $this->plugin->get_form_settings( $this->form_name );
    493:     }
    494:     return $this->_settings;
    495:   }
    496:
    497:   /**
    498:    * @param string $setting_name
    499:    * @return bool
    500:    */
    501:   function has_setting( $setting_name ) {
    502:     return $this->get_settings()->has_setting( $setting_name );
    503:   }
    504:
    505:   /**
    506:    * @param string $setting_name
    507:    * @return mixed
    508:    */
    509:   function get_setting( $setting_name ) {
    510:     return $this->get_settings()->get_setting( $setting_name );
    511:     }
    512:
    513:   /**
    514:    * @param string $setting_name
    515:    * @param mixed $value
    516:    * @return array
    517:    */
    518:   function update_settings_value( $setting_name, $value ) {
    519:     $this->get_settings()->update_settings_value( $setting_name, $value );
    520:   }
    521:
    522:   /**
    523:    * @param array $form_settings
    524:    * @return array
    525:    */
    526:   function update_settings( $form_settings ) {
    527:     $this->get_settings()->update_settings( $form_settings );
    528:   }
    529:   /**
    530:    * @param array $form_settings
    531:    * @return array
    532:    */
    533:   function update_settings_values( $form_settings ) {
    534:     $this->get_settings()->set_values( $form_settings );
    535:   }
    536:
    537:   /**
    538:    * @return array
    539:    */
    540:   function get_required_field_names() {
    541:     if ( ! isset( $this->_required_field_names ) ) {
    542:       $required_field_names = array();
    543:       $this->initialize();
    544:       $form_settings = $this->get_settings();
    545:       foreach( $this->get_fields() as $field_name => $field ) {
    546:         if ( $field->field_required && $form_settings->has_setting( $field_name ) ) {
    547:           $required_field_names[] = $field_name;
    548:         }
    549:       }
    550:       if ( method_exists( $this, 'filter_required_field_names' ) ) {
    551:         $required_field_names = $this->filter_required_field_names( $required_field_names, $settings );
    552:       }
    553:       $this->_required_field_names = $required_field_names;
    554:     }
    555:     return $this->_required_field_names;
    556:   }
    557:
    558:   /**
    559:    * Ensure their is a default value for every field w/o a matching array element.
    560:    *
    561:    * This is used to ensure forms POSTed back always have all fields. In cases of
    562:    * checkboxes or radio buttons they may not.
    563:    *
    564:    * @param array $field_values
    565:    *
    566:    * @return array
    567:    */
    568:   function ensure_default_values( $field_values ) {
    569:     foreach( $this->_fields as $field_name => $field ) {
    570:       if ( ! isset( $field_values[$field_name] ) ) {
    571:         $field_values[$field_name] = ! is_null( $field->field_default ) ? $field->field_default : false;
    572:       }
    573:     }
    574:     return $field_values;
    575:   }
    576:
    577: }
    578: